diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..99fb0ab34 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +backend/snapshots/* linguist-generated=true + diff --git a/.gitbook/assets/browserstack-logo.svg b/.gitbook/assets/browserstack-logo.svg new file mode 100644 index 000000000..195f64d2f --- /dev/null +++ b/.gitbook/assets/browserstack-logo.svg @@ -0,0 +1,90 @@ + + + + +Browserstack-logo-white + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitbook/assets/storybook-output.png b/.gitbook/assets/storybook-output.png new file mode 100644 index 000000000..2b157dd62 Binary files /dev/null and b/.gitbook/assets/storybook-output.png differ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 1fba3fa58..ef3b30be2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,11 +6,19 @@ title: 🚀 [Feature] --- ## :rocket: Feature - + + +### User Problem + + +### Implementation + ### Design & Layout - + +### Validation + ### Additional context - + diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..0458d1a5a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 30 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - bounty +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.gitignore b/.gitignore index 45effce57..928dae262 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ cypress.env.json **/coverage release/ +*~ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 70451cd79..f598594cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,20 +6,19 @@ addons: - libgconf-2-4 snaps: - docker - - chromium -before_install: +install: - yarn global add wait-on # Install Codecov - yarn install - - cp cypress.env.template.json cypress.env.json + - cp backend/.env.template backend/.env -install: +before_script: - docker-compose -f docker-compose.yml build --parallel - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml build # just tagging, just be quite fast - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml up -d - wait-on http://localhost:7474 - - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec neo4j db_setup + - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec backend yarn run db:migrate init script: - export CYPRESS_RETRIES=1 @@ -27,22 +26,18 @@ script: - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" # Backend - docker-compose exec backend yarn run lint - - docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage + - docker-compose exec backend yarn run test --ci --verbose=false --coverage - docker-compose exec backend yarn run db:seed - docker-compose exec backend yarn run db:reset - # ActivityPub cucumber testing temporarily disabled because it's too buggy - # - docker-compose exec backend yarn run test:cucumber --tags "not @wip" - # - docker-compose exec backend yarn run db:reset - # - docker-compose exec backend yarn run db:seed # Frontend - docker-compose exec webapp yarn run lint - docker-compose exec webapp yarn run test --ci --verbose=false --coverage - - docker-compose exec -d backend yarn run test:before:seeder # Fullstack - docker-compose down - docker-compose -f docker-compose.yml up -d - wait-on http://localhost:7474 - - yarn run cypress:run + - yarn run cypress:run --record + - yarn run cucumber # Coverage - yarn run codecov @@ -67,14 +62,14 @@ before_deploy: deploy: - provider: script - script: scripts/docker_push.sh + script: bash scripts/docker_push.sh on: branch: master - provider: script - script: scripts/deploy.sh + script: bash scripts/deploy.sh on: branch: master - provider: script - script: scripts/github_release.sh + script: bash scripts/github_release.sh on: branch: master diff --git a/.versionrc.json b/.versionrc.json new file mode 100644 index 000000000..25c298f45 --- /dev/null +++ b/.versionrc.json @@ -0,0 +1,7 @@ +{ + "bumpFiles": [ + "package.json", + "backend/package.json", + "webapp/package.json" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index e2a727871..8565bda8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,5 @@ "autoFix": true } ], - "editor.formatOnSave": true, - "eslint.autoFixOnSave": true + "editor.formatOnSave": false, } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..a67694579 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2068 @@ +### Changelog + +All notable changes to this project will be documented in this file. Dates are displayed in UTC. + +Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). + +#### [v0.2.2](https://github.com/Human-Connection/Human-Connection/compare/v0.2.1...v0.2.2) + +> 20 January 2020 + +- build(deps): bump metascraper-title from 5.10.3 to 5.10.5 in /backend [`#2835`](https://github.com/Human-Connection/Human-Connection/pull/2835) +- build(deps): bump metascraper-publisher in /backend [`#2836`](https://github.com/Human-Connection/Human-Connection/pull/2836) +- build(deps): bump metascraper-audio from 5.10.3 to 5.10.5 in /backend [`#2840`](https://github.com/Human-Connection/Human-Connection/pull/2840) +- build(deps): bump metascraper-author from 5.10.3 to 5.10.5 in /backend [`#2838`](https://github.com/Human-Connection/Human-Connection/pull/2838) +- build(deps): bump metascraper-url from 5.10.3 to 5.10.5 in /backend [`#2832`](https://github.com/Human-Connection/Human-Connection/pull/2832) +- build(deps): bump metascraper-lang from 5.10.3 to 5.10.5 in /backend [`#2831`](https://github.com/Human-Connection/Human-Connection/pull/2831) +- refactor(modules): Various import fixes [`#2802`](https://github.com/Human-Connection/Human-Connection/pull/2802) +- build(deps): bump metascraper-description from 5.10.3 to 5.10.5 in /backend [`#2839`](https://github.com/Human-Connection/Human-Connection/pull/2839) +- build(deps-dev): bump @storybook/addon-notes from 5.3.5 to 5.3.6 in /webapp [`#2834`](https://github.com/Human-Connection/Human-Connection/pull/2834) +- build(deps): bump metascraper-youtube from 5.10.3 to 5.10.5 in /backend [`#2833`](https://github.com/Human-Connection/Human-Connection/pull/2833) +- build(deps): bump metascraper from 5.10.3 to 5.10.5 in /backend [`#2830`](https://github.com/Human-Connection/Human-Connection/pull/2830) +- build(deps): bump metascraper-soundcloud from 5.10.3 to 5.10.5 in /backend [`#2829`](https://github.com/Human-Connection/Human-Connection/pull/2829) +- fix(translations): Remove duplicate and mistranslated item from code of conduct [`#2725`](https://github.com/Human-Connection/Human-Connection/pull/2725) +- build(deps-dev): bump @storybook/addon-a11y from 5.3.3 to 5.3.6 in /webapp [`#2820`](https://github.com/Human-Connection/Human-Connection/pull/2820) +- build(deps): bump metascraper from 5.10.2 to 5.10.3 in /backend [`#2808`](https://github.com/Human-Connection/Human-Connection/pull/2808) +- build(deps-dev): bump @storybook/vue from 5.3.3 to 5.3.6 in /webapp [`#2819`](https://github.com/Human-Connection/Human-Connection/pull/2819) +- build(deps): bump faker from `9fd8d7d` to `3b2fa4a` in /backend [`#2803`](https://github.com/Human-Connection/Human-Connection/pull/2803) +- build(deps-dev): bump faker from `9fd8d7d` to `3b2fa4a` [`#2804`](https://github.com/Human-Connection/Human-Connection/pull/2804) +- build(deps-dev): bump @storybook/addon-a11y in /webapp [`#2809`](https://github.com/Human-Connection/Human-Connection/pull/2809) +- build(deps): bump uuid from 3.3.3 to 3.4.0 in /backend [`#2810`](https://github.com/Human-Connection/Human-Connection/pull/2810) +- build(deps): bump metascraper-image from 5.9.5 to 5.10.3 in /backend [`#2811`](https://github.com/Human-Connection/Human-Connection/pull/2811) +- build(deps-dev): bump node-sass from 4.13.0 to 4.13.1 in /webapp [`#2812`](https://github.com/Human-Connection/Human-Connection/pull/2812) +- build(deps): bump metascraper-audio from 5.9.5 to 5.10.3 in /backend [`#2813`](https://github.com/Human-Connection/Human-Connection/pull/2813) +- build(deps): bump metascraper-soundcloud in /backend [`#2815`](https://github.com/Human-Connection/Human-Connection/pull/2815) +- build(deps-dev): bump @storybook/addon-notes in /webapp [`#2816`](https://github.com/Human-Connection/Human-Connection/pull/2816) +- build(deps-dev): bump @storybook/addon-actions from 5.3.3 to 5.3.5 in /webapp [`#2807`](https://github.com/Human-Connection/Human-Connection/pull/2807) +- build(deps): bump metascraper-description from 5.9.5 to 5.10.3 in /backend [`#2806`](https://github.com/Human-Connection/Human-Connection/pull/2806) +- build(deps): bump mustache from 3.2.1 to 4.0.0 in /backend [`#2805`](https://github.com/Human-Connection/Human-Connection/pull/2805) +- 🍰 feat(webapp): Display deployed version in footer [`#2728`](https://github.com/Human-Connection/Human-Connection/pull/2728) +- fix: cypress breaks locally in login step [`#2776`](https://github.com/Human-Connection/Human-Connection/pull/2776) +- build(deps-dev): bump @vue/test-utils from 1.0.0-beta.29 to 1.0.0-beta.30 in /webapp [`#2378`](https://github.com/Human-Connection/Human-Connection/pull/2378) +- build(deps): bump metascraper-youtube from 5.9.5 to 5.10.3 in /backend [`#2794`](https://github.com/Human-Connection/Human-Connection/pull/2794) +- build(deps): bump metascraper-video from 5.9.5 to 5.10.3 in /backend [`#2795`](https://github.com/Human-Connection/Human-Connection/pull/2795) +- build(deps): bump metascraper-logo from 5.9.5 to 5.10.3 in /backend [`#2796`](https://github.com/Human-Connection/Human-Connection/pull/2796) +- refactor(styleguide): improve emotion buttons and header responsiveness [`#2582`](https://github.com/Human-Connection/Human-Connection/pull/2582) +- build(deps): bump metascraper-url from 5.9.5 to 5.10.3 in /backend [`#2793`](https://github.com/Human-Connection/Human-Connection/pull/2793) +- build(deps): bump metascraper-author from 5.9.5 to 5.10.3 in /backend [`#2789`](https://github.com/Human-Connection/Human-Connection/pull/2789) +- build(deps): bump metascraper-lang from 5.9.5 to 5.10.3 in /backend [`#2790`](https://github.com/Human-Connection/Human-Connection/pull/2790) +- build(deps): bump metascraper-publisher from 5.9.5 to 5.10.3 in /backend [`#2792`](https://github.com/Human-Connection/Human-Connection/pull/2792) +- build(deps): bump metascraper-title from 5.9.5 to 5.10.3 in /backend [`#2791`](https://github.com/Human-Connection/Human-Connection/pull/2791) +- build(deps): bump @sentry/node from 5.11.0 to 5.11.1 in /backend [`#2788`](https://github.com/Human-Connection/Human-Connection/pull/2788) +- build(deps): bump metascraper-date from 5.9.5 to 5.10.3 in /backend [`#2787`](https://github.com/Human-Connection/Human-Connection/pull/2787) +- build(deps-dev): bump @babel/node from 7.8.0 to 7.8.3 in /backend [`#2754`](https://github.com/Human-Connection/Human-Connection/pull/2754) +- refactor(styleguide): migrate and redesign buttons [`#2562`](https://github.com/Human-Connection/Human-Connection/pull/2562) +- build(deps-dev): bump @babel/core from 7.8.0 to 7.8.3 in /backend [`#2760`](https://github.com/Human-Connection/Human-Connection/pull/2760) +- build(deps-dev): bump @storybook/addon-actions from 5.3.2 to 5.3.3 in /webapp [`#2782`](https://github.com/Human-Connection/Human-Connection/pull/2782) +- build(deps-dev): bump sass-loader from 8.0.0 to 8.0.2 in /webapp [`#2781`](https://github.com/Human-Connection/Human-Connection/pull/2781) +- build(deps-dev): bump @babel/plugin-syntax-dynamic-import from 7.8.0 to 7.8.3 in /webapp [`#2780`](https://github.com/Human-Connection/Human-Connection/pull/2780) +- build(deps-dev): bump @storybook/addon-a11y from 5.3.2 to 5.3.3 in /webapp [`#2779`](https://github.com/Human-Connection/Human-Connection/pull/2779) +- build(deps): bump metascraper from 5.9.5 to 5.10.2 in /backend [`#2778`](https://github.com/Human-Connection/Human-Connection/pull/2778) +- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.3 in /webapp [`#2767`](https://github.com/Human-Connection/Human-Connection/pull/2767) +- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.8.0 to 7.8.3 in /backend [`#2757`](https://github.com/Human-Connection/Human-Connection/pull/2757) +- build(deps-dev): bump @storybook/vue from 5.3.1 to 5.3.3 in /webapp [`#2772`](https://github.com/Human-Connection/Human-Connection/pull/2772) +- build(deps-dev): bump @babel/preset-env from 7.8.2 to 7.8.3 [`#2758`](https://github.com/Human-Connection/Human-Connection/pull/2758) +- build(deps-dev): bump eslint-plugin-import from 2.19.1 to 2.20.0 in /webapp [`#2748`](https://github.com/Human-Connection/Human-Connection/pull/2748) +- build(deps-dev): bump @storybook/addon-notes from 5.3.1 to 5.3.3 in /webapp [`#2771`](https://github.com/Human-Connection/Human-Connection/pull/2771) +- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.3 in /webapp [`#2769`](https://github.com/Human-Connection/Human-Connection/pull/2769) +- build(deps-dev): bump @babel/register from 7.8.0 to 7.8.3 [`#2764`](https://github.com/Human-Connection/Human-Connection/pull/2764) +- build(deps-dev): bump @babel/preset-env from 7.8.2 to 7.8.3 in /backend [`#2755`](https://github.com/Human-Connection/Human-Connection/pull/2755) +- build(deps-dev): bump eslint-plugin-jest from 23.3.0 to 23.6.0 in /webapp [`#2768`](https://github.com/Human-Connection/Human-Connection/pull/2768) +- build(deps-dev): bump @babel/cli from 7.8.0 to 7.8.3 in /backend [`#2763`](https://github.com/Human-Connection/Human-Connection/pull/2763) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.19.0 to 2.0.1 [`#2761`](https://github.com/Human-Connection/Human-Connection/pull/2761) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.8 to 5.3.2 in /webapp [`#2759`](https://github.com/Human-Connection/Human-Connection/pull/2759) +- build(deps-dev): bump @babel/core from 7.8.0 to 7.8.3 [`#2756`](https://github.com/Human-Connection/Human-Connection/pull/2756) +- build(deps-dev): bump @babel/register from 7.8.0 to 7.8.3 in /backend [`#2753`](https://github.com/Human-Connection/Human-Connection/pull/2753) +- build(deps): [security] bump serialize-javascript from 2.1.0 to 2.1.2 in /webapp [`#2752`](https://github.com/Human-Connection/Human-Connection/pull/2752) +- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.0 in /backend [`#2743`](https://github.com/Human-Connection/Human-Connection/pull/2743) +- build(deps-dev): bump @storybook/addon-actions from 5.2.8 to 5.3.2 in /webapp [`#2751`](https://github.com/Human-Connection/Human-Connection/pull/2751) +- build(deps-dev): bump @babel/register from 7.7.7 to 7.8.0 in /backend [`#2735`](https://github.com/Human-Connection/Human-Connection/pull/2735) +- build(deps-dev): bump @babel/plugin-syntax-dynamic-import from 7.7.4 to 7.8.0 in /webapp [`#2746`](https://github.com/Human-Connection/Human-Connection/pull/2746) +- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.2 in /backend [`#2739`](https://github.com/Human-Connection/Human-Connection/pull/2739) +- build(deps-dev): bump @babel/cli from 7.7.7 to 7.8.0 in /backend [`#2744`](https://github.com/Human-Connection/Human-Connection/pull/2744) +- Issues marked as bounty never become stale [`#2726`](https://github.com/Human-Connection/Human-Connection/pull/2726) +- build(deps-dev): bump css-loader from 3.4.1 to 3.4.2 in /webapp [`#2747`](https://github.com/Human-Connection/Human-Connection/pull/2747) +- build(deps-dev): bump @storybook/addon-notes from 5.2.8 to 5.3.1 in /webapp [`#2742`](https://github.com/Human-Connection/Human-Connection/pull/2742) +- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.7.4 to 7.8.0 in /backend [`#2741`](https://github.com/Human-Connection/Human-Connection/pull/2741) +- build(deps-dev): bump eslint-plugin-import from 2.19.1 to 2.20.0 in /backend [`#2737`](https://github.com/Human-Connection/Human-Connection/pull/2737) +- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.2 [`#2732`](https://github.com/Human-Connection/Human-Connection/pull/2732) +- build(deps): bump @nuxtjs/axios from 5.9.2 to 5.9.3 in /webapp [`#2740`](https://github.com/Human-Connection/Human-Connection/pull/2740) +- build(deps-dev): bump @storybook/vue from 5.2.8 to 5.3.1 in /webapp [`#2738`](https://github.com/Human-Connection/Human-Connection/pull/2738) +- build(deps-dev): bump cypress from 3.8.1 to 3.8.2 [`#2734`](https://github.com/Human-Connection/Human-Connection/pull/2734) +- build(deps-dev): bump @babel/node from 7.7.7 to 7.8.0 in /backend [`#2733`](https://github.com/Human-Connection/Human-Connection/pull/2733) +- build(deps-dev): bump eslint-plugin-jest from 23.3.0 to 23.6.0 in /backend [`#2731`](https://github.com/Human-Connection/Human-Connection/pull/2731) +- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.0 [`#2730`](https://github.com/Human-Connection/Human-Connection/pull/2730) +- build(deps-dev): bump @babel/register from 7.7.7 to 7.8.0 [`#2729`](https://github.com/Human-Connection/Human-Connection/pull/2729) +- build(deps): bump nuxt from 2.10.2 to 2.11.0 in /webapp [`#2552`](https://github.com/Human-Connection/Human-Connection/pull/2552) +- Update yarn.lock after dependabot update [`#2724`](https://github.com/Human-Connection/Human-Connection/pull/2724) +- build(deps): bump @nuxtjs/axios from 5.8.0 to 5.9.2 in /webapp [`#2657`](https://github.com/Human-Connection/Human-Connection/pull/2657) +- Update to version 0.2.1 [`#2722`](https://github.com/Human-Connection/Human-Connection/pull/2722) +- refactor(modules): Various import fixes [`#2773`](https://github.com/Human-Connection/Human-Connection/issues/2773) [`#2774`](https://github.com/Human-Connection/Human-Connection/issues/2774) +- feat(webapp): Display deployed version in footer [`#1831`](https://github.com/Human-Connection/Human-Connection/issues/1831) +- fix #2229 [`#2229`](https://github.com/Human-Connection/Human-Connection/issues/2229) +- build(deps-dev): bump @storybook/addon-actions in /webapp [`d0124bf`](https://github.com/Human-Connection/Human-Connection/commit/d0124bf2b4b4a641c9af76d6d2f7b5aa075ade90) +- refactor and use base-button in SearchableInput [`fcbe612`](https://github.com/Human-Connection/Human-Connection/commit/fcbe6125f35c0dd23e2ba1ae63f539f5ef5990ea) +- Update `vue-test-utils` and follow updated docs [`8c29ad9`](https://github.com/Human-Connection/Human-Connection/commit/8c29ad947b72fbaa173d070221cdf35b7ab6aaa5) + +#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1) + +> 10 January 2020 + +- 🍰 Search For Users [`#2262`](https://github.com/Human-Connection/Human-Connection/pull/2262) +- Use node LTS in production [`#2713`](https://github.com/Human-Connection/Human-Connection/pull/2713) +- build(deps): bump apollo-server from 2.9.15 to 2.9.16 in /backend [`#2718`](https://github.com/Human-Connection/Human-Connection/pull/2718) +- build(deps): bump neo4j-graphql-js from 2.11.4 to 2.11.5 in /backend [`#2715`](https://github.com/Human-Connection/Human-Connection/pull/2715) +- build(deps-dev): bump apollo-server-testing from 2.9.15 to 2.9.16 in /backend [`#2720`](https://github.com/Human-Connection/Human-Connection/pull/2720) +- build(deps): bump @hapi/joi from 17.0.0 to 17.0.2 in /backend [`#2719`](https://github.com/Human-Connection/Human-Connection/pull/2719) +- build(deps): bump apollo-server-express from 2.9.15 to 2.9.16 in /backend [`#2717`](https://github.com/Human-Connection/Human-Connection/pull/2717) +- build(deps): bump metascraper-url from 5.8.13 to 5.9.5 in /backend [`#2716`](https://github.com/Human-Connection/Human-Connection/pull/2716) +- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /backend [`#2706`](https://github.com/Human-Connection/Human-Connection/pull/2706) +- build(deps): bump metascraper-lang from 5.8.13 to 5.9.5 in /backend [`#2703`](https://github.com/Human-Connection/Human-Connection/pull/2703) +- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /webapp [`#2711`](https://github.com/Human-Connection/Human-Connection/pull/2711) +- build(deps): bump metascraper-logo from 5.8.13 to 5.9.5 in /backend [`#2697`](https://github.com/Human-Connection/Human-Connection/pull/2697) +- build(deps): bump metascraper-title from 5.8.13 to 5.9.5 in /backend [`#2694`](https://github.com/Human-Connection/Human-Connection/pull/2694) +- build(deps): bump metascraper-description from 5.8.15 to 5.9.5 in /backend [`#2690`](https://github.com/Human-Connection/Human-Connection/pull/2690) +- build(deps): bump node from 13.5.0-alpine to 13.6.0-alpine in /webapp [`#2708`](https://github.com/Human-Connection/Human-Connection/pull/2708) +- build(deps): bump @sentry/node from 5.10.2 to 5.11.0 in /backend [`#2709`](https://github.com/Human-Connection/Human-Connection/pull/2709) +- build(deps): bump metascraper-audio from 5.8.13 to 5.9.5 in /backend [`#2707`](https://github.com/Human-Connection/Human-Connection/pull/2707) +- build(deps): bump metascraper-image from 5.9.4 to 5.9.5 in /backend [`#2705`](https://github.com/Human-Connection/Human-Connection/pull/2705) +- build(deps): bump metascraper-youtube from 5.8.13 to 5.9.5 in /backend [`#2704`](https://github.com/Human-Connection/Human-Connection/pull/2704) +- build(deps-dev): bump date-fns from 2.8.1 to 2.9.0 [`#2702`](https://github.com/Human-Connection/Human-Connection/pull/2702) +- build(deps): bump metascraper-soundcloud from 5.9.0 to 5.9.5 in /backend [`#2693`](https://github.com/Human-Connection/Human-Connection/pull/2693) +- build(deps): bump metascraper-date from 5.8.13 to 5.9.5 in /backend [`#2698`](https://github.com/Human-Connection/Human-Connection/pull/2698) +- build(deps): bump neo4j-graphql-js from 2.11.3 to 2.11.4 in /backend [`#2696`](https://github.com/Human-Connection/Human-Connection/pull/2696) +- build(deps): bump metascraper-video from 5.8.13 to 5.9.5 in /backend [`#2695`](https://github.com/Human-Connection/Human-Connection/pull/2695) +- build(deps): bump metascraper-publisher from 5.8.13 to 5.9.5 in /backend [`#2692`](https://github.com/Human-Connection/Human-Connection/pull/2692) +- build(deps): bump metascraper-author from 5.8.13 to 5.9.5 in /backend [`#2691`](https://github.com/Human-Connection/Human-Connection/pull/2691) +- build(deps): bump metascraper from 5.9.4 to 5.9.5 in /backend [`#2689`](https://github.com/Human-Connection/Human-Connection/pull/2689) +- Changes Text For SignUp [`#2678`](https://github.com/Human-Connection/Human-Connection/pull/2678) +- Update de.json [`#2655`](https://github.com/Human-Connection/Human-Connection/pull/2655) +- build(deps): bump neode from 0.3.6 to 0.3.7 in /backend [`#2682`](https://github.com/Human-Connection/Human-Connection/pull/2682) +- Update neo4j-driver [`#2546`](https://github.com/Human-Connection/Human-Connection/pull/2546) +- build(deps): bump merge-graphql-schemas from 1.7.5 to 1.7.6 in /backend [`#2681`](https://github.com/Human-Connection/Human-Connection/pull/2681) +- build(deps): bump neo4j-graphql-js from 2.11.2 to 2.11.3 in /backend [`#2680`](https://github.com/Human-Connection/Human-Connection/pull/2680) +- build(deps-dev): bump neode from 0.3.6 to 0.3.7 [`#2679`](https://github.com/Human-Connection/Human-Connection/pull/2679) +- Parse xss before extracting mentions/hashtags [`#2674`](https://github.com/Human-Connection/Human-Connection/pull/2674) +- build(deps): bump metascraper-logo from 5.8.12 to 5.8.13 in /backend [`#2672`](https://github.com/Human-Connection/Human-Connection/pull/2672) +- build(deps): bump metascraper from 5.9.0 to 5.9.4 in /backend [`#2668`](https://github.com/Human-Connection/Human-Connection/pull/2668) +- build(deps-dev): bump eslint-plugin-jest from 23.2.0 to 23.3.0 in /webapp [`#2671`](https://github.com/Human-Connection/Human-Connection/pull/2671) +- build(deps-dev): bump css-loader from 3.4.0 to 3.4.1 in /webapp [`#2669`](https://github.com/Human-Connection/Human-Connection/pull/2669) +- build(deps): bump metascraper-image from 5.8.13 to 5.9.4 in /backend [`#2670`](https://github.com/Human-Connection/Human-Connection/pull/2670) +- build(deps-dev): bump apollo-server-testing from 2.9.14 to 2.9.15 in /backend [`#2667`](https://github.com/Human-Connection/Human-Connection/pull/2667) +- build(deps): bump neo4j-graphql-js from 2.11.0 to 2.11.2 in /backend [`#2666`](https://github.com/Human-Connection/Human-Connection/pull/2666) +- build(deps): bump metascraper-title from 5.8.12 to 5.8.13 in /backend [`#2665`](https://github.com/Human-Connection/Human-Connection/pull/2665) +- build(deps): bump @hapi/joi from 16.1.8 to 17.0.0 in /backend [`#2664`](https://github.com/Human-Connection/Human-Connection/pull/2664) +- build(deps-dev): bump cypress-file-upload from 3.5.1 to 3.5.3 [`#2663`](https://github.com/Human-Connection/Human-Connection/pull/2663) +- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.3.0 in /backend [`#2662`](https://github.com/Human-Connection/Human-Connection/pull/2662) +- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /webapp [`#2632`](https://github.com/Human-Connection/Human-Connection/pull/2632) +- build(deps-dev): bump slug from 2.0.0 to 2.1.0 [`#2647`](https://github.com/Human-Connection/Human-Connection/pull/2647) +- build(deps): bump merge-graphql-schemas from 1.7.3 to 1.7.5 in /backend [`#2648`](https://github.com/Human-Connection/Human-Connection/pull/2648) +- build(deps): bump metascraper-url from 5.8.12 to 5.8.13 in /backend [`#2637`](https://github.com/Human-Connection/Human-Connection/pull/2637) +- build(deps): bump metascraper-publisher from 5.8.12 to 5.8.13 in /backend [`#2636`](https://github.com/Human-Connection/Human-Connection/pull/2636) +- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.2.0 in /webapp [`#2642`](https://github.com/Human-Connection/Human-Connection/pull/2642) +- build(deps): bump graphql-shield from 7.0.5 to 7.0.7 in /backend [`#2649`](https://github.com/Human-Connection/Human-Connection/pull/2649) +- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /backend [`#2650`](https://github.com/Human-Connection/Human-Connection/pull/2650) +- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /backend [`#2651`](https://github.com/Human-Connection/Human-Connection/pull/2651) +- build(deps): bump slug from 2.0.0 to 2.1.0 in /backend [`#2652`](https://github.com/Human-Connection/Human-Connection/pull/2652) +- build(deps): bump metascraper from 5.8.12 to 5.9.0 in /backend [`#2654`](https://github.com/Human-Connection/Human-Connection/pull/2654) +- build(deps): bump metascraper-description from 5.8.12 to 5.8.15 in /backend [`#2653`](https://github.com/Human-Connection/Human-Connection/pull/2653) +- build(deps): bump metascraper-author from 5.8.12 to 5.8.13 in /backend [`#2616`](https://github.com/Human-Connection/Human-Connection/pull/2616) +- build(deps): bump metascraper-lang from 5.8.12 to 5.8.13 in /backend [`#2618`](https://github.com/Human-Connection/Human-Connection/pull/2618) +- build(deps): bump apollo-server from 2.9.13 to 2.9.15 in /backend [`#2634`](https://github.com/Human-Connection/Human-Connection/pull/2634) +- build(deps): bump metascraper-soundcloud from 5.8.15 to 5.9.0 in /backend [`#2638`](https://github.com/Human-Connection/Human-Connection/pull/2638) +- build(deps): bump metascraper-video from 5.8.12 to 5.8.13 in /backend [`#2639`](https://github.com/Human-Connection/Human-Connection/pull/2639) +- build(deps): bump mustache from 3.2.0 to 3.2.1 in /backend [`#2640`](https://github.com/Human-Connection/Human-Connection/pull/2640) +- build(deps): bump slug from 1.1.0 to 2.0.0 in /backend [`#2641`](https://github.com/Human-Connection/Human-Connection/pull/2641) +- build(deps-dev): bump @vue/cli-shared-utils from 4.1.1 to 4.1.2 in /webapp [`#2643`](https://github.com/Human-Connection/Human-Connection/pull/2643) +- build(deps-dev): bump eslint-plugin-vue from 6.1.1 to 6.1.2 in /webapp [`#2644`](https://github.com/Human-Connection/Human-Connection/pull/2644) +- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /webapp [`#2645`](https://github.com/Human-Connection/Human-Connection/pull/2645) +- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /webapp [`#2579`](https://github.com/Human-Connection/Human-Connection/pull/2579) +- build(deps): bump metascraper-soundcloud from 5.8.12 to 5.8.15 in /backend [`#2630`](https://github.com/Human-Connection/Human-Connection/pull/2630) +- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /webapp [`#2617`](https://github.com/Human-Connection/Human-Connection/pull/2617) +- build(deps): bump metascraper-date from 5.8.12 to 5.8.13 in /backend [`#2615`](https://github.com/Human-Connection/Human-Connection/pull/2615) +- build(deps): bump metascraper-image from 5.8.12 to 5.8.13 in /backend [`#2614`](https://github.com/Human-Connection/Human-Connection/pull/2614) +- build(deps): bump metascraper-youtube from 5.8.12 to 5.8.13 in /backend [`#2612`](https://github.com/Human-Connection/Human-Connection/pull/2612) +- build(deps): bump metascraper-audio from 5.8.12 to 5.8.13 in /backend [`#2610`](https://github.com/Human-Connection/Human-Connection/pull/2610) +- 🍰 Added Language Tag For Posts [`#2627`](https://github.com/Human-Connection/Human-Connection/pull/2627) +- build(deps-dev): bump cypress-plugin-retries from 1.5.0 to 1.5.2 [`#2609`](https://github.com/Human-Connection/Human-Connection/pull/2609) +- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /backend [`#2613`](https://github.com/Human-Connection/Human-Connection/pull/2613) +- remove accidently created ru.json in wrong place [`#2606`](https://github.com/Human-Connection/Human-Connection/pull/2606) +- build(deps): bump neo4j from 3.5.13-enterprise to 3.5.14-enterprise in /neo4j [`#2620`](https://github.com/Human-Connection/Human-Connection/pull/2620) +- Fixes 2603 [`#2619`](https://github.com/Human-Connection/Human-Connection/pull/2619) +- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /webapp [`#2581`](https://github.com/Human-Connection/Human-Connection/pull/2581) +- build(deps-dev): bump slug from 1.1.0 to 2.0.0 [`#2621`](https://github.com/Human-Connection/Human-Connection/pull/2621) +- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /webapp [`#2624`](https://github.com/Human-Connection/Human-Connection/pull/2624) +- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /backend [`#2625`](https://github.com/Human-Connection/Human-Connection/pull/2625) +- build(deps-dev): bump cypress from 3.8.0 to 3.8.1 [`#2626`](https://github.com/Human-Connection/Human-Connection/pull/2626) +- build(deps-dev): bump eslint-plugin-vue from 6.0.1 to 6.1.1 in /webapp [`#2633`](https://github.com/Human-Connection/Human-Connection/pull/2633) +- build(deps-dev): bump @babel/register from 7.7.4 to 7.7.7 [`#2571`](https://github.com/Human-Connection/Human-Connection/pull/2571) +- build(deps): bump neo4j-graphql-js from 2.10.2 to 2.11.0 in /backend [`#2600`](https://github.com/Human-Connection/Human-Connection/pull/2600) +- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /backend [`#2590`](https://github.com/Human-Connection/Human-Connection/pull/2590) +- build(deps): bump metascraper-url from 5.8.7 to 5.8.12 in /backend [`#2599`](https://github.com/Human-Connection/Human-Connection/pull/2599) +- build(deps): bump metascraper-lang from 5.8.10 to 5.8.12 in /backend [`#2598`](https://github.com/Human-Connection/Human-Connection/pull/2598) +- build(deps): bump metascraper-audio from 5.8.10 to 5.8.12 in /backend [`#2596`](https://github.com/Human-Connection/Human-Connection/pull/2596) +- build(deps): bump node from 13.4.0-alpine to 13.5.0-alpine in /webapp [`#2595`](https://github.com/Human-Connection/Human-Connection/pull/2595) +- build(deps-dev): bump storybook-design-token from 0.4.1 to 0.5.0 in /webapp [`#2594`](https://github.com/Human-Connection/Human-Connection/pull/2594) +- build(deps): bump graphql-shield from 7.0.4 to 7.0.5 in /backend [`#2593`](https://github.com/Human-Connection/Human-Connection/pull/2593) +- build(deps): bump metascraper-publisher from 5.8.7 to 5.8.12 in /backend [`#2592`](https://github.com/Human-Connection/Human-Connection/pull/2592) +- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /backend [`#2568`](https://github.com/Human-Connection/Human-Connection/pull/2568) +- Fix imageAspectRatio set to null UpdatePost [`#2588`](https://github.com/Human-Connection/Human-Connection/pull/2588) +- fixes #2659 [`#2659`](https://github.com/Human-Connection/Human-Connection/issues/2659) +- build(deps-dev): bump storybook-design-token in /webapp [`88d39c4`](https://github.com/Human-Connection/Human-Connection/commit/88d39c4a427cb86527b06201f3f5e96d53ac09a0) +- manage button states and color schemes with mixin [`1b9249c`](https://github.com/Human-Connection/Human-Connection/commit/1b9249c685e34eb2e94b31ee0ec22421c6aa6a73) +- Specs for Searches [`bc3aa51`](https://github.com/Human-Connection/Human-Connection/commit/bc3aa519d0e7a6e0242ecd37d611fd1a3df385d0) + +#### [v0.2.0](https://github.com/Human-Connection/Human-Connection/compare/v0.1.13...v0.2.0) + +> 19 December 2019 + +- Update to version 0.2.0 [`#2584`](https://github.com/Human-Connection/Human-Connection/pull/2584) +- build(deps): bump metascraper-image from 5.8.10 to 5.8.12 in /backend [`#2556`](https://github.com/Human-Connection/Human-Connection/pull/2556) +- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 [`#2569`](https://github.com/Human-Connection/Human-Connection/pull/2569) +- build(deps-dev): bump @babel/cli from 7.7.5 to 7.7.7 in /backend [`#2576`](https://github.com/Human-Connection/Human-Connection/pull/2576) +- Lokalise: Translations update [`#2563`](https://github.com/Human-Connection/Human-Connection/pull/2563) +- build(deps-dev): bump style-resources-loader from 1.3.2 to 1.3.3 in /webapp [`#2580`](https://github.com/Human-Connection/Human-Connection/pull/2580) +- build(deps): bump node from 13.3.0-alpine to 13.4.0-alpine in /webapp [`#2577`](https://github.com/Human-Connection/Human-Connection/pull/2577) +- build(deps): bump metascraper-title from 5.8.10 to 5.8.12 in /backend [`#2575`](https://github.com/Human-Connection/Human-Connection/pull/2575) +- build(deps-dev): bump apollo-server-testing from 2.9.13 to 2.9.14 in /backend [`#2574`](https://github.com/Human-Connection/Human-Connection/pull/2574) +- build(deps): bump mustache from 3.1.0 to 3.2.0 in /backend [`#2572`](https://github.com/Human-Connection/Human-Connection/pull/2572) +- Blur Images [`#2351`](https://github.com/Human-Connection/Human-Connection/pull/2351) +- build(deps-dev): bump @babel/node from 7.7.4 to 7.7.7 in /backend [`#2570`](https://github.com/Human-Connection/Human-Connection/pull/2570) +- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 [`#2567`](https://github.com/Human-Connection/Human-Connection/pull/2567) +- build(deps): bump metascraper-description from 5.8.10 to 5.8.12 in /backend [`#2566`](https://github.com/Human-Connection/Human-Connection/pull/2566) +- Add back layout changes/update db_manipulation [`#2544`](https://github.com/Human-Connection/Human-Connection/pull/2544) +- build(deps): bump metascraper-soundcloud from 5.8.10 to 5.8.12 in /backend [`#2560`](https://github.com/Human-Connection/Human-Connection/pull/2560) +- build(deps): bump metascraper-author from 5.8.7 to 5.8.12 in /backend [`#2559`](https://github.com/Human-Connection/Human-Connection/pull/2559) +- build(deps): bump metascraper from 5.8.9 to 5.8.12 in /backend [`#2558`](https://github.com/Human-Connection/Human-Connection/pull/2558) +- build(deps): bump metascraper-youtube from 5.8.9 to 5.8.12 in /backend [`#2547`](https://github.com/Human-Connection/Human-Connection/pull/2547) +- build(deps): bump metascraper-video from 5.8.10 to 5.8.12 in /backend [`#2557`](https://github.com/Human-Connection/Human-Connection/pull/2557) +- build(deps): bump metascraper-date from 5.8.7 to 5.8.12 in /backend [`#2555`](https://github.com/Human-Connection/Human-Connection/pull/2555) +- build(deps): bump metascraper-logo from 5.8.10 to 5.8.12 in /backend [`#2554`](https://github.com/Human-Connection/Human-Connection/pull/2554) +- build(deps): bump apollo-server-express from 2.9.13 to 2.9.14 in /backend [`#2551`](https://github.com/Human-Connection/Human-Connection/pull/2551) +- build(deps-dev): bump css-loader from 3.3.2 to 3.4.0 in /webapp [`#2550`](https://github.com/Human-Connection/Human-Connection/pull/2550) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.18.0 to 1.19.0 [`#2548`](https://github.com/Human-Connection/Human-Connection/pull/2548) +- Lokalise: Translations update [`#2545`](https://github.com/Human-Connection/Human-Connection/pull/2545) +- build(deps): bump metascraper-youtube from 5.8.9 to 5.8.10 in /backend [`#2522`](https://github.com/Human-Connection/Human-Connection/pull/2522) +- build(deps): bump metascraper-title from 5.8.7 to 5.8.10 in /backend [`#2525`](https://github.com/Human-Connection/Human-Connection/pull/2525) +- build(deps): bump metascraper-lang from 5.8.9 to 5.8.10 in /backend [`#2531`](https://github.com/Human-Connection/Human-Connection/pull/2531) +- build(deps): bump tiptap-extensions from 1.28.5 to 1.28.6 in /webapp [`#2535`](https://github.com/Human-Connection/Human-Connection/pull/2535) +- Fix maintenance service/LocaleSwitch import [`#2542`](https://github.com/Human-Connection/Human-Connection/pull/2542) +- build(deps): bump apollo-client from 2.6.4 to 2.6.8 in /webapp [`#2523`](https://github.com/Human-Connection/Human-Connection/pull/2523) +- build(deps): bump stack-utils from 1.0.2 to 2.0.1 in /webapp [`#2521`](https://github.com/Human-Connection/Human-Connection/pull/2521) +- build(deps): bump metascraper-soundcloud from 5.8.9 to 5.8.10 in /backend [`#2520`](https://github.com/Human-Connection/Human-Connection/pull/2520) +- Update neode [`#2539`](https://github.com/Human-Connection/Human-Connection/pull/2539) +- build(deps-dev): bump eslint-plugin-prettier from 3.1.1 to 3.1.2 in /webapp [`#2519`](https://github.com/Human-Connection/Human-Connection/pull/2519) +- build(deps): bump apollo-cache-inmemory from 1.6.3 to 1.6.5 in /webapp [`#2527`](https://github.com/Human-Connection/Human-Connection/pull/2527) +- build(deps): bump neo4j-graphql-js from 2.10.1 to 2.10.2 in /backend [`#2530`](https://github.com/Human-Connection/Human-Connection/pull/2530) +- build(deps): bump metascraper-image from 5.8.7 to 5.8.10 in /backend [`#2532`](https://github.com/Human-Connection/Human-Connection/pull/2532) +- build(deps): bump apollo-cache-inmemory from 1.6.3 to 1.6.5 in /backend [`#2534`](https://github.com/Human-Connection/Human-Connection/pull/2534) +- build(deps): bump metascraper-video from 5.8.9 to 5.8.10 in /backend [`#2536`](https://github.com/Human-Connection/Human-Connection/pull/2536) +- build(deps): bump tiptap from 1.26.5 to 1.26.6 in /webapp [`#2537`](https://github.com/Human-Connection/Human-Connection/pull/2537) +- build(deps-dev): bump vue-loader from 15.7.2 to 15.8.3 in /webapp [`#2538`](https://github.com/Human-Connection/Human-Connection/pull/2538) +- Refactor: content menu [`#2512`](https://github.com/Human-Connection/Human-Connection/pull/2512) +- build(deps): bump metascraper-audio from 5.8.7 to 5.8.10 in /backend [`#2524`](https://github.com/Human-Connection/Human-Connection/pull/2524) +- build(deps): bump metascraper-description from 5.8.7 to 5.8.10 in /backend [`#2518`](https://github.com/Human-Connection/Human-Connection/pull/2518) +- build(deps-dev): bump eslint-plugin-prettier from 3.1.1 to 3.1.2 in /backend [`#2517`](https://github.com/Human-Connection/Human-Connection/pull/2517) +- build(deps): bump apollo-client from 2.6.4 to 2.6.8 in /backend [`#2516`](https://github.com/Human-Connection/Human-Connection/pull/2516) +- build(deps): bump metascraper-logo from 5.8.7 to 5.8.10 in /backend [`#2515`](https://github.com/Human-Connection/Human-Connection/pull/2515) +- Fix duplicate fragment `user` issue [`#2511`](https://github.com/Human-Connection/Human-Connection/pull/2511) +- fix: editor not visible in server-side-rendering [`#2513`](https://github.com/Human-Connection/Human-Connection/pull/2513) +- Update it.json [`#2507`](https://github.com/Human-Connection/Human-Connection/pull/2507) +- Fix: User.name is not non-nullable [`#2510`](https://github.com/Human-Connection/Human-Connection/pull/2510) +- Update to version 0.1.13 [`#2506`](https://github.com/Human-Connection/Human-Connection/pull/2506) +- Lokalise: update of webapp/locales/ru.json [`b70ff73`](https://github.com/Human-Connection/Human-Connection/commit/b70ff73bba98d28494c55ed12161288b1efa1516) +- Separate concerns in components [`d74d207`](https://github.com/Human-Connection/Human-Connection/commit/d74d2072ba41af6170d79d7dc2e24f9ebab15771) +- Fix failing component tests [`b79c292`](https://github.com/Human-Connection/Human-Connection/commit/b79c292ef4f76b307131566c24d849c2e35c8089) + +#### [v0.1.13](https://github.com/Human-Connection/Human-Connection/compare/v0.1.12...v0.1.13) + +> 13 December 2019 + +- Update de.json [`#2492`](https://github.com/Human-Connection/Human-Connection/pull/2492) +- Fix broken scroll behaviour on index and profile page [`#2487`](https://github.com/Human-Connection/Human-Connection/pull/2487) +- Lokalise: Translations update [`#2503`](https://github.com/Human-Connection/Human-Connection/pull/2503) +- build(deps): bump node from 13.1.0-alpine to 13.3.0-alpine in /webapp [`#2454`](https://github.com/Human-Connection/Human-Connection/pull/2454) +- Lokalise: Translations update [`#2485`](https://github.com/Human-Connection/Human-Connection/pull/2485) +- build(deps-dev): bump css-loader from 3.3.0 to 3.3.2 in /webapp [`#2505`](https://github.com/Human-Connection/Human-Connection/pull/2505) +- build(deps-dev): bump cypress from 3.7.0 to 3.8.0 [`#2504`](https://github.com/Human-Connection/Human-Connection/pull/2504) +- Favor transaction functions [`#2433`](https://github.com/Human-Connection/Human-Connection/pull/2433) +- build(deps): bump nodemailer from 6.4.1 to 6.4.2 in /backend [`#2500`](https://github.com/Human-Connection/Human-Connection/pull/2500) +- Update en.json [`#2491`](https://github.com/Human-Connection/Human-Connection/pull/2491) +- Update es.json [`#2493`](https://github.com/Human-Connection/Human-Connection/pull/2493) +- Update fr.json [`#2494`](https://github.com/Human-Connection/Human-Connection/pull/2494) +- Update it.json [`#2496`](https://github.com/Human-Connection/Human-Connection/pull/2496) +- build(deps-dev): bump nodemon from 2.0.1 to 2.0.2 in /backend [`#2499`](https://github.com/Human-Connection/Human-Connection/pull/2499) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc18 to 4.0.0-rc19 in /webapp [`#2498`](https://github.com/Human-Connection/Human-Connection/pull/2498) +- build(deps): bump neo4j-graphql-js from 2.10.0 to 2.10.1 in /backend [`#2497`](https://github.com/Human-Connection/Human-Connection/pull/2497) +- Fix docker manifest on Travis CI [`#2488`](https://github.com/Human-Connection/Human-Connection/pull/2488) +- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 [`#2453`](https://github.com/Human-Connection/Human-Connection/pull/2453) +- build(deps-dev): bump cypress-file-upload from 3.5.0 to 3.5.1 [`#2489`](https://github.com/Human-Connection/Human-Connection/pull/2489) +- build(deps): bump cookie-universal-nuxt from 2.0.19 to 2.1.0 in /webapp [`#2490`](https://github.com/Human-Connection/Human-Connection/pull/2490) +- Update to version 0.1.12 [`#2483`](https://github.com/Human-Connection/Human-Connection/pull/2483) +- Lokalise: update of locale/ru.json [`60b3035`](https://github.com/Human-Connection/Human-Connection/commit/60b3035a3d475cb481130c6fe94f2901711a4053) +- Fix search by adding result id [`ebc5cf3`](https://github.com/Human-Connection/Human-Connection/commit/ebc5cf392d92acf3a9e22c8967d02ea2cf6fd7fb) +- Write test/refactor tests/resolvers/middleware [`d375ebe`](https://github.com/Human-Connection/Human-Connection/commit/d375ebe7d90e3251b17f59ffba8fb1470923ebe8) + +#### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.11...v0.1.12) + +> 10 December 2019 + +- Show the comments again [`#2482`](https://github.com/Human-Connection/Human-Connection/pull/2482) +- Improve notification query performance by reducing db calls [`#2470`](https://github.com/Human-Connection/Human-Connection/pull/2470) +- Fix `Cannot read 'Post' of undefined` [`#2481`](https://github.com/Human-Connection/Human-Connection/pull/2481) +- Hope to fix our deployment with explicit call of `bash` [`#2480`](https://github.com/Human-Connection/Human-Connection/pull/2480) +- Revert layout changes image aspect ratio [`#2467`](https://github.com/Human-Connection/Human-Connection/pull/2467) +- Quick fix for null pointer error in User.vue [`#2472`](https://github.com/Human-Connection/Human-Connection/pull/2472) +- Checkbox 'no comercial + no political account' add to creat user account [`#2416`](https://github.com/Human-Connection/Human-Connection/pull/2416) +- Remove data-test attriubutes in non-dev env [`#2421`](https://github.com/Human-Connection/Human-Connection/pull/2421) +- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 [`#2452`](https://github.com/Human-Connection/Human-Connection/pull/2452) +- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 in /backend [`#2455`](https://github.com/Human-Connection/Human-Connection/pull/2455) +- build(deps-dev): bump @babel/cli from 7.7.4 to 7.7.5 in /backend [`#2458`](https://github.com/Human-Connection/Human-Connection/pull/2458) +- build(deps): bump @sentry/node from 5.10.1 to 5.10.2 in /backend [`#2473`](https://github.com/Human-Connection/Human-Connection/pull/2473) +- build(deps-dev): bump eslint-plugin-import from 2.18.2 to 2.19.1 in /backend [`#2474`](https://github.com/Human-Connection/Human-Connection/pull/2474) +- build(deps-dev): bump css-loader from 3.2.1 to 3.3.0 in /webapp [`#2475`](https://github.com/Human-Connection/Human-Connection/pull/2475) +- build(deps-dev): bump eslint-plugin-import from 2.18.2 to 2.19.1 in /webapp [`#2477`](https://github.com/Human-Connection/Human-Connection/pull/2477) +- Fix #2237, Comments 4 times as long before "show more" [`#2443`](https://github.com/Human-Connection/Human-Connection/pull/2443) +- Get rid of inconsistency with neode setup [`#2404`](https://github.com/Human-Connection/Human-Connection/pull/2404) +- Bump styleguide to version 0.5.22 [`#2468`](https://github.com/Human-Connection/Human-Connection/pull/2468) +- build(deps): bump nodemailer from 6.4.0 to 6.4.1 in /backend [`#2456`](https://github.com/Human-Connection/Human-Connection/pull/2456) +- build(deps-dev): bump eslint-loader from 3.0.2 to 3.0.3 in /webapp [`#2459`](https://github.com/Human-Connection/Human-Connection/pull/2459) +- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 in /backend [`#2460`](https://github.com/Human-Connection/Human-Connection/pull/2460) +- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 in /webapp [`#2461`](https://github.com/Human-Connection/Human-Connection/pull/2461) +- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 in /webapp [`#2463`](https://github.com/Human-Connection/Human-Connection/pull/2463) +- build(deps-dev): bump async-validator from 3.2.2 to 3.2.3 in /webapp [`#2464`](https://github.com/Human-Connection/Human-Connection/pull/2464) +- build(deps): bump styleguide from `808b3c5` to `7ef8340` [`#2465`](https://github.com/Human-Connection/Human-Connection/pull/2465) +- Paginate moderations page without losing filtering [`#2466`](https://github.com/Human-Connection/Human-Connection/pull/2466) +- Update it.json [`#2451`](https://github.com/Human-Connection/Human-Connection/pull/2451) +- build(deps): bump metascraper from 5.8.8 to 5.8.9 in /backend [`#2304`](https://github.com/Human-Connection/Human-Connection/pull/2304) +- build(deps): bump metascraper-video from 5.8.7 to 5.8.9 in /backend [`#2303`](https://github.com/Human-Connection/Human-Connection/pull/2303) +- build(deps): bump neo4j-graphql-js from 2.9.3 to 2.10.0 in /backend [`#2440`](https://github.com/Human-Connection/Human-Connection/pull/2440) +- Hide Donations Bar [`#2422`](https://github.com/Human-Connection/Human-Connection/pull/2422) +- build(deps): bump @sentry/node from 5.10.0 to 5.10.1 in /backend [`#2436`](https://github.com/Human-Connection/Human-Connection/pull/2436) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.17.0 to 1.18.0 [`#2437`](https://github.com/Human-Connection/Human-Connection/pull/2437) +- build(deps-dev): bump apollo-server-testing from 2.9.12 to 2.9.13 in /backend [`#2439`](https://github.com/Human-Connection/Human-Connection/pull/2439) +- build(deps): bump apollo-server from 2.9.12 to 2.9.13 in /backend [`#2441`](https://github.com/Human-Connection/Human-Connection/pull/2441) +- Eliminate database calls for reports query [`#2435`](https://github.com/Human-Connection/Human-Connection/pull/2435) +- Use babel-loader with vue-svg-loader [`#2430`](https://github.com/Human-Connection/Human-Connection/pull/2430) +- Remove disable from reports.disable Query [`#2432`](https://github.com/Human-Connection/Human-Connection/pull/2432) +- 2253 fix scroll layout issue [`#2317`](https://github.com/Human-Connection/Human-Connection/pull/2317) +- Update test description [`#2424`](https://github.com/Human-Connection/Human-Connection/pull/2424) +- Update yarn.lock after pulling in latest changes [`#2419`](https://github.com/Human-Connection/Human-Connection/pull/2419) +- Update privacy path [`#2417`](https://github.com/Human-Connection/Human-Connection/pull/2417) +- Add browserstack logo to attributions [`#2431`](https://github.com/Human-Connection/Human-Connection/pull/2431) +- build(deps): bump @sentry/node from 5.9.0 to 5.10.0 in /backend [`#2428`](https://github.com/Human-Connection/Human-Connection/pull/2428) +- build(deps): bump nodemailer from 6.3.1 to 6.4.0 in /backend [`#2427`](https://github.com/Human-Connection/Human-Connection/pull/2427) +- List and protocol moderation [`#1954`](https://github.com/Human-Connection/Human-Connection/pull/1954) +- fix: Re-enable webfinger feature [`#2335`](https://github.com/Human-Connection/Human-Connection/pull/2335) +- Close neo4j driver sessions [`#2402`](https://github.com/Human-Connection/Human-Connection/pull/2402) +- feat: swap user name<=>handle for discriminability [`#2385`](https://github.com/Human-Connection/Human-Connection/pull/2385) +- build(deps-dev): bump @storybook/vue from 5.2.6 to 5.2.8 in /webapp [`#2397`](https://github.com/Human-Connection/Human-Connection/pull/2397) +- build(deps-dev): bump @storybook/addon-actions from 5.2.6 to 5.2.8 in /webapp [`#2398`](https://github.com/Human-Connection/Human-Connection/pull/2398) +- Fix German translation of "Shouts" [`#2400`](https://github.com/Human-Connection/Human-Connection/pull/2400) +- build(deps): bump tiptap-extensions from 1.28.4 to 1.28.5 in /webapp [`#2407`](https://github.com/Human-Connection/Human-Connection/pull/2407) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.7 to 5.2.8 in /webapp [`#2406`](https://github.com/Human-Connection/Human-Connection/pull/2406) +- build(deps-dev): bump css-loader from 3.2.0 to 3.2.1 in /webapp [`#2405`](https://github.com/Human-Connection/Human-Connection/pull/2405) +- build(deps-dev): bump @storybook/addon-notes from 5.2.6 to 5.2.8 in /webapp [`#2399`](https://github.com/Human-Connection/Human-Connection/pull/2399) +- build(deps-dev): bump eslint from 6.7.1 to 6.7.2 in /webapp [`#2393`](https://github.com/Human-Connection/Human-Connection/pull/2393) +- build(deps-dev): bump @vue/cli-shared-utils from 4.0.5 to 4.1.1 in /webapp [`#2374`](https://github.com/Human-Connection/Human-Connection/pull/2374) +- build(deps-dev): bump eslint-plugin-jest from 23.0.5 to 23.1.1 in /webapp [`#2392`](https://github.com/Human-Connection/Human-Connection/pull/2392) +- Terms of use extended with dot - no commercial use [`#2316`](https://github.com/Human-Connection/Human-Connection/pull/2316) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.16.2 to 1.17.0 [`#2389`](https://github.com/Human-Connection/Human-Connection/pull/2389) +- Lokalise: Translations update [`#2380`](https://github.com/Human-Connection/Human-Connection/pull/2380) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.6 to 5.2.7 in /webapp [`#2391`](https://github.com/Human-Connection/Human-Connection/pull/2391) +- build(deps-dev): bump eslint-plugin-jest from 23.0.5 to 23.1.1 in /backend [`#2390`](https://github.com/Human-Connection/Human-Connection/pull/2390) +- build(deps-dev): bump eslint from 6.7.1 to 6.7.2 in /backend [`#2388`](https://github.com/Human-Connection/Human-Connection/pull/2388) +- build(deps-dev): bump @vue/server-test-utils from 1.0.0-beta.29 to 1.0.0-beta.30 in /webapp [`#2379`](https://github.com/Human-Connection/Human-Connection/pull/2379) +- build(deps): bump neo4j from 3.5.12-enterprise to 3.5.13-enterprise in /neo4j [`#2377`](https://github.com/Human-Connection/Human-Connection/pull/2377) +- build(deps-dev): bump @babel/cli from 7.7.0 to 7.7.4 in /backend [`#2366`](https://github.com/Human-Connection/Human-Connection/pull/2366) +- build(deps-dev): bump cypress-plugin-retries from 1.4.0 to 1.5.0 [`#2360`](https://github.com/Human-Connection/Human-Connection/pull/2360) +- No public registration in development so that backend test pass [`#2382`](https://github.com/Human-Connection/Human-Connection/pull/2382) +- Don't remove sub-addresses in emails [`#2375`](https://github.com/Human-Connection/Human-Connection/pull/2375) +- refactor: Remove obsolete code about invitation codes [`#2333`](https://github.com/Human-Connection/Human-Connection/pull/2333) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc17 to 4.0.0-rc18 in /webapp [`#2373`](https://github.com/Human-Connection/Human-Connection/pull/2373) +- build(deps): bump graphql-shield from 7.0.2 to 7.0.4 in /backend [`#2372`](https://github.com/Human-Connection/Human-Connection/pull/2372) +- build(deps-dev): bump cypress from 3.6.1 to 3.7.0 [`#2371`](https://github.com/Human-Connection/Human-Connection/pull/2371) +- build(deps-dev): bump @babel/core from 7.7.2 to 7.7.4 in /backend [`#2359`](https://github.com/Human-Connection/Human-Connection/pull/2359) +- build(deps): bump apollo-server from 2.9.11 to 2.9.12 in /backend [`#2357`](https://github.com/Human-Connection/Human-Connection/pull/2357) +- build(deps-dev): bump eslint-plugin-jest from 23.0.4 to 23.0.5 in /webapp [`#2369`](https://github.com/Human-Connection/Human-Connection/pull/2369) +- build(deps): bump @hapi/joi from 16.1.7 to 16.1.8 in /backend [`#2368`](https://github.com/Human-Connection/Human-Connection/pull/2368) +- build(deps-dev): bump eslint-plugin-jest from 23.0.4 to 23.0.5 in /backend [`#2365`](https://github.com/Human-Connection/Human-Connection/pull/2365) +- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.2.0 to 7.7.4 in /backend [`#2339`](https://github.com/Human-Connection/Human-Connection/pull/2339) +- refactor: Close session in isAuthor permission [`#2334`](https://github.com/Human-Connection/Human-Connection/pull/2334) +- build(deps): bump date-fns from 2.7.0 to 2.8.1 in /webapp [`#2323`](https://github.com/Human-Connection/Human-Connection/pull/2323) +- 1967 component tests content view [`#2169`](https://github.com/Human-Connection/Human-Connection/pull/2169) +- If an admin searches for a user by email, don't crash if no user can be found [`#2295`](https://github.com/Human-Connection/Human-Connection/pull/2295) +- Migrate styleguide icons [`#2288`](https://github.com/Human-Connection/Human-Connection/pull/2288) +- build(deps-dev): bump eslint from 6.6.0 to 6.7.1 in /backend [`#2358`](https://github.com/Human-Connection/Human-Connection/pull/2358) +- build(deps-dev): bump @babel/preset-env from 7.7.1 to 7.7.4 in /backend [`#2341`](https://github.com/Human-Connection/Human-Connection/pull/2341) +- build(deps-dev): bump @babel/core from 7.7.2 to 7.7.4 in /webapp [`#2340`](https://github.com/Human-Connection/Human-Connection/pull/2340) +- build(deps): bump date-fns from 2.7.0 to 2.8.1 in /backend [`#2322`](https://github.com/Human-Connection/Human-Connection/pull/2322) +- build(deps): bump validator from 12.0.0 to 12.1.0 in /webapp [`#2319`](https://github.com/Human-Connection/Human-Connection/pull/2319) +- Update to version 0.1.11 with bug fixes [`#2354`](https://github.com/Human-Connection/Human-Connection/pull/2354) +- Fix updating post by adding/changing image bug submits form [`#2350`](https://github.com/Human-Connection/Human-Connection/pull/2350) +- Add shoutedBy_some to _PostFilter [`#2353`](https://github.com/Human-Connection/Human-Connection/pull/2353) +- build(deps-dev): bump date-fns from 2.8.0 to 2.8.1 [`#2342`](https://github.com/Human-Connection/Human-Connection/pull/2342) +- build(deps-dev): bump @babel/node from 7.7.0 to 7.7.4 in /backend [`#2343`](https://github.com/Human-Connection/Human-Connection/pull/2343) +- build(deps): bump validator from 12.0.0 to 12.1.0 in /backend [`#2345`](https://github.com/Human-Connection/Human-Connection/pull/2345) +- build(deps-dev): bump @babel/preset-env from 7.7.1 to 7.7.4 in /webapp [`#2346`](https://github.com/Human-Connection/Human-Connection/pull/2346) +- build(deps-dev): bump eslint from 6.6.0 to 6.7.1 in /webapp [`#2347`](https://github.com/Human-Connection/Human-Connection/pull/2347) +- build(deps-dev): bump nodemon from 1.19.4 to 2.0.1 in /backend [`#2348`](https://github.com/Human-Connection/Human-Connection/pull/2348) +- Update to version 0.1.11 [`#2324`](https://github.com/Human-Connection/Human-Connection/pull/2324) +- Add LegacyEmbeds component to fix bug [`#2328`](https://github.com/Human-Connection/Human-Connection/pull/2328) +- Fix incorrect link to development environment [`#2309`](https://github.com/Human-Connection/Human-Connection/pull/2309) +- 2329 normalize emails in login form [`#2330`](https://github.com/Human-Connection/Human-Connection/pull/2330) +- Lokalise: Translations update [`#2327`](https://github.com/Human-Connection/Human-Connection/pull/2327) +- Changed translation must change test :( [`#2310`](https://github.com/Human-Connection/Human-Connection/pull/2310) +- Merge pull request #2443 from Human-Connection/2237-longer-comments [`#2237`](https://github.com/Human-Connection/Human-Connection/issues/2237) +- fix #2329: Normalize email on login in the backend [`#2329`](https://github.com/Human-Connection/Human-Connection/issues/2329) +- Lokalise: update of webapp/locales/ru.json [`3e52ee0`](https://github.com/Human-Connection/Human-Connection/commit/3e52ee090c88c357b796895370d126f8bb5529f0) +- Lokalise: update of webapp/locales/de.json [`d2b3396`](https://github.com/Human-Connection/Human-Connection/commit/d2b3396e9b44bac0e767ee970e083d1847426b26) +- Lokalise: update of webapp/locales/pt.json [`bcd9f0e`](https://github.com/Human-Connection/Human-Connection/commit/bcd9f0ec93cfab2661589d72a3b3f38455ec4d51) + +#### [v0.1.11](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.11) + +> 22 November 2019 + +- build(deps-dev): bump apollo-server-testing from 2.9.9 to 2.9.12 in /backend [`#2318`](https://github.com/Human-Connection/Human-Connection/pull/2318) +- build(deps-dev): bump fuse.js from 3.4.5 to 3.4.6 in /webapp [`#2314`](https://github.com/Human-Connection/Human-Connection/pull/2314) +- build(deps-dev): bump eslint-config-prettier from 6.6.0 to 6.7.0 in /webapp [`#2302`](https://github.com/Human-Connection/Human-Connection/pull/2302) +- build(deps): bump metascraper-lang from 5.8.7 to 5.8.9 in /backend [`#2301`](https://github.com/Human-Connection/Human-Connection/pull/2301) +- build(deps): bump metascraper-soundcloud from 5.8.7 to 5.8.9 in /backend [`#2298`](https://github.com/Human-Connection/Human-Connection/pull/2298) +- build(deps): bump metascraper-youtube from 5.8.7 to 5.8.9 in /backend [`#2297`](https://github.com/Human-Connection/Human-Connection/pull/2297) +- build(deps-dev): bump date-fns from 2.7.0 to 2.8.0 [`#2296`](https://github.com/Human-Connection/Human-Connection/pull/2296) +- build(deps): bump apollo-server from 2.9.9 to 2.9.11 in /backend [`#2311`](https://github.com/Human-Connection/Human-Connection/pull/2311) +- Fix Typo Of Issue 2230 [`#2293`](https://github.com/Human-Connection/Human-Connection/pull/2293) +- [Systems-Development-and-Frameworks] Clickable hashtags [`#2076`](https://github.com/Human-Connection/Human-Connection/pull/2076) +- build(deps): bump neo4j-graphql-js from 2.9.0 to 2.9.3 in /backend [`#2283`](https://github.com/Human-Connection/Human-Connection/pull/2283) +- build(deps-dev): bump eslint-plugin-jest from 23.0.3 to 23.0.4 in /backend [`#2285`](https://github.com/Human-Connection/Human-Connection/pull/2285) +- build(deps-dev): bump eslint-config-prettier from 6.5.0 to 6.7.0 in /backend [`#2286`](https://github.com/Human-Connection/Human-Connection/pull/2286) +- Lokalise: Translations update [`#2277`](https://github.com/Human-Connection/Human-Connection/pull/2277) +- build(deps-dev): bump @vue/eslint-config-prettier from 5.0.0 to 6.0.0 in /webapp [`#2266`](https://github.com/Human-Connection/Human-Connection/pull/2266) +- build(deps-dev): bump prettier from 1.18.2 to 1.19.1 in /backend [`#2281`](https://github.com/Human-Connection/Human-Connection/pull/2281) +- build(deps): bump apollo-server from 2.9.7 to 2.9.9 in /backend [`#2280`](https://github.com/Human-Connection/Human-Connection/pull/2280) +- build(deps-dev): bump apollo-server-testing from 2.9.7 to 2.9.9 in /backend [`#2279`](https://github.com/Human-Connection/Human-Connection/pull/2279) +- build(deps): bump @sentry/node from 5.8.0 to 5.9.0 in /backend [`#2278`](https://github.com/Human-Connection/Human-Connection/pull/2278) +- 🍰 2119-Fix Contribution consistent form input validation [`#2160`](https://github.com/Human-Connection/Human-Connection/pull/2160) +- build(deps): bump cookie-universal-nuxt from 2.0.18 to 2.0.19 in /webapp [`#2265`](https://github.com/Human-Connection/Human-Connection/pull/2265) +- build(deps): bump metascraper modules [`#2257`](https://github.com/Human-Connection/Human-Connection/pull/2257) +- build: configure stale bot [`#2273`](https://github.com/Human-Connection/Human-Connection/pull/2273) +- Explicitly define our schema, improve performance [`#2243`](https://github.com/Human-Connection/Human-Connection/pull/2243) +- build(deps): [security] bump https-proxy-agent from 2.2.1 to 2.2.4 [`#2274`](https://github.com/Human-Connection/Human-Connection/pull/2274) +- build: record and publish all cypress test results [`#2251`](https://github.com/Human-Connection/Human-Connection/pull/2251) +- Add Emacs Backup Files To Gitignore (*~) [`#2261`](https://github.com/Human-Connection/Human-Connection/pull/2261) +- build(deps-dev): bump eslint-config-prettier from 6.5.0 to 6.6.0 in /webapp [`#2267`](https://github.com/Human-Connection/Human-Connection/pull/2267) +- 2187 language does not change on edit [`#2200`](https://github.com/Human-Connection/Human-Connection/pull/2200) +- 1747 show shouts [`#2101`](https://github.com/Human-Connection/Human-Connection/pull/2101) +- build(deps): bump node from 13.0.1-alpine to 13.1.0-alpine in /webapp [`#2180`](https://github.com/Human-Connection/Human-Connection/pull/2180) +- Fix #2042 Back Link To Login Page [`#2078`](https://github.com/Human-Connection/Human-Connection/pull/2078) +- 2119 create post consistent form input validation improvements [`#2163`](https://github.com/Human-Connection/Human-Connection/pull/2163) +- Helper script to install all packages [`#2250`](https://github.com/Human-Connection/Human-Connection/pull/2250) +- refactor: remove redundant port configuration [`#2247`](https://github.com/Human-Connection/Human-Connection/pull/2247) +- build(deps-dev): bump eslint-config-standard from 12.0.0 to 14.1.0 in /webapp [`#2246`](https://github.com/Human-Connection/Human-Connection/pull/2246) +- build(deps-dev): bump cucumber from 6.0.3 to 6.0.5 in /backend [`#2236`](https://github.com/Human-Connection/Human-Connection/pull/2236) +- build: less verbose output on build server [`#2249`](https://github.com/Human-Connection/Human-Connection/pull/2249) +- Fix infinite scrolling out of control [`#2248`](https://github.com/Human-Connection/Human-Connection/pull/2248) +- 2106-unblock a user in the user settings [`#2110`](https://github.com/Human-Connection/Human-Connection/pull/2110) +- Fix 1936 [`#2241`](https://github.com/Human-Connection/Human-Connection/pull/2241) +- fix(editor): Fix hashtags not working after embeded content [`#2242`](https://github.com/Human-Connection/Human-Connection/pull/2242) +- build(deps-dev): bump prettier from 1.18.2 to 1.19.1 in /webapp [`#2185`](https://github.com/Human-Connection/Human-Connection/pull/2185) +- build(deps-dev): bump eslint-plugin-jest from 23.0.2 to 23.0.4 in /webapp [`#2245`](https://github.com/Human-Connection/Human-Connection/pull/2245) +- build(deps): remove deprecated debug flags [`#2219`](https://github.com/Human-Connection/Human-Connection/pull/2219) +- build(deps-dev): bump eslint-plugin-vue from 6.0.0 to 6.0.1 in /webapp [`#2235`](https://github.com/Human-Connection/Human-Connection/pull/2235) +- build(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /webapp [`#2205`](https://github.com/Human-Connection/Human-Connection/pull/2205) +- Add locale to undefined to null [`#2233`](https://github.com/Human-Connection/Human-Connection/pull/2233) +- Update to version 0.1.10 [`#2231`](https://github.com/Human-Connection/Human-Connection/pull/2231) +- Fix #2294 [`#2294`](https://github.com/Human-Connection/Human-Connection/issues/2294) +- Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042) +- Tell github-linguists to ignore snapshots [`978347b`](https://github.com/Human-Connection/Human-Connection/commit/978347ba7b5a6aa1bc915ada972ffffa2816d37c) +- Lokalise: update of webapp/locales/ru.json [`906e851`](https://github.com/Human-Connection/Human-Connection/commit/906e8518bf060134150187fb1574ac50ffd502f6) +- set up global localVue [`77f4810`](https://github.com/Human-Connection/Human-Connection/commit/77f4810ddc963386bc68d3e8a5e078ef4cf270b2) + +#### [v0.1.10](https://github.com/Human-Connection/Human-Connection/compare/v0.1.9...v0.1.10) + +> 13 November 2019 + +- Update contribution guidelines [`#2127`](https://github.com/Human-Connection/Human-Connection/pull/2127) +- fix: return `null` for missig translations [`#2218`](https://github.com/Human-Connection/Human-Connection/pull/2218) +- Add donation status and button [`#2194`](https://github.com/Human-Connection/Human-Connection/pull/2194) +- Added Empty Definitions For Missing Getters And Mutations [`#2197`](https://github.com/Human-Connection/Human-Connection/pull/2197) +- build(deps): bump @sentry/node from 5.7.1 to 5.8.0 in /backend [`#2209`](https://github.com/Human-Connection/Human-Connection/pull/2209) +- build(deps-dev): bump eslint-plugin-jest from 23.0.2 to 23.0.3 in /backend [`#2206`](https://github.com/Human-Connection/Human-Connection/pull/2206) +- build(deps-dev): bump vue-svg-loader from 0.14.0 to 0.15.0 in /webapp [`#2204`](https://github.com/Human-Connection/Human-Connection/pull/2204) +- build(deps-dev): bump async-validator from 3.2.1 to 3.2.2 in /webapp [`#2203`](https://github.com/Human-Connection/Human-Connection/pull/2203) +- Update deployment names in deploy script [`#2216`](https://github.com/Human-Connection/Human-Connection/pull/2216) +- Fix: Delete Block Button in MainPage PostCards [`#2193`](https://github.com/Human-Connection/Human-Connection/pull/2193) +- Update doctl to use default context [`#2199`](https://github.com/Human-Connection/Human-Connection/pull/2199) +- console.error in Hashtag.spec.js #2161 [`#2201`](https://github.com/Human-Connection/Human-Connection/pull/2201) +- build(deps-dev): bump @storybook/addon-actions from 5.2.5 to 5.2.6 in /webapp [`#2186`](https://github.com/Human-Connection/Human-Connection/pull/2186) +- build(deps-dev): bump style-resources-loader from 1.2.1 to 1.3.2 in /webapp [`#2188`](https://github.com/Human-Connection/Human-Connection/pull/2188) +- build(deps-dev): bump @storybook/vue from 5.2.5 to 5.2.6 in /webapp [`#2183`](https://github.com/Human-Connection/Human-Connection/pull/2183) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.5 to 5.2.6 in /webapp [`#2176`](https://github.com/Human-Connection/Human-Connection/pull/2176) +- build(deps-dev): bump cypress from 3.6.0 to 3.6.1 [`#2173`](https://github.com/Human-Connection/Human-Connection/pull/2173) +- build(deps): bump date-fns from 2.6.0 to 2.7.0 in /webapp [`#2164`](https://github.com/Human-Connection/Human-Connection/pull/2164) +- Update docs for deploying new server, env variables [`#2191`](https://github.com/Human-Connection/Human-Connection/pull/2191) +- Remove unintended comma [`#2192`](https://github.com/Human-Connection/Human-Connection/pull/2192) +- added Russian to locales [`#2111`](https://github.com/Human-Connection/Human-Connection/pull/2111) +- Add notifications page with All Notifications [`#1975`](https://github.com/Human-Connection/Human-Connection/pull/1975) +- 1931 - after successful login the saved language of the user is set [`#2073`](https://github.com/Human-Connection/Human-Connection/pull/2073) +- build(deps-dev): bump cypress-file-upload from 3.4.0 to 3.5.0 [`#2167`](https://github.com/Human-Connection/Human-Connection/pull/2167) +- build(deps): bump date-fns from 2.6.0 to 2.7.0 in /backend [`#2166`](https://github.com/Human-Connection/Human-Connection/pull/2166) +- build(deps-dev): bump date-fns from 2.6.0 to 2.7.0 [`#2165`](https://github.com/Human-Connection/Human-Connection/pull/2165) +- build(deps-dev): bump eslint-plugin-vue from 5.2.3 to 6.0.0 in /webapp [`#2156`](https://github.com/Human-Connection/Human-Connection/pull/2156) +- build(deps): bump merge-graphql-schemas from 1.7.2 to 1.7.3 in /backend [`#2155`](https://github.com/Human-Connection/Human-Connection/pull/2155) +- build(deps-dev): bump @babel/core from 7.6.4 to 7.7.2 in /backend [`#2154`](https://github.com/Human-Connection/Human-Connection/pull/2154) +- Add missing portuguese translation [`#1909`](https://github.com/Human-Connection/Human-Connection/pull/1909) +- Migrate design tokens [`#2159`](https://github.com/Human-Connection/Human-Connection/pull/2159) +- build(deps-dev): bump @babel/core from 7.7.0 to 7.7.2 in /webapp [`#2158`](https://github.com/Human-Connection/Human-Connection/pull/2158) +- build(deps-dev): bump vue-svg-loader from 0.12.0 to 0.14.0 in /webapp [`#2157`](https://github.com/Human-Connection/Human-Connection/pull/2157) +- Remove graphql-requests [`#2151`](https://github.com/Human-Connection/Human-Connection/pull/2151) +- close all open sessions [`#2148`](https://github.com/Human-Connection/Human-Connection/pull/2148) +- Implement refresh posts, fix duplicate posts bug [`#2126`](https://github.com/Human-Connection/Human-Connection/pull/2126) +- Fix: Email is Case-Sensitive [`#2118`](https://github.com/Human-Connection/Human-Connection/pull/2118) +- build(deps-dev): bump @babel/preset-env from 7.6.3 to 7.7.1 in /backend [`#2135`](https://github.com/Human-Connection/Human-Connection/pull/2135) +- build(deps): bump graphql-shield from 7.0.1 to 7.0.2 in /backend [`#2136`](https://github.com/Human-Connection/Human-Connection/pull/2136) +- build(deps-dev): bump @babel/node from 7.6.3 to 7.7.0 in /backend [`#2134`](https://github.com/Human-Connection/Human-Connection/pull/2134) +- build(deps-dev): bump @babel/core from 7.6.4 to 7.7.0 in /webapp [`#2132`](https://github.com/Human-Connection/Human-Connection/pull/2132) +- build(deps-dev): bump @babel/preset-env from 7.6.3 to 7.7.1 in /webapp [`#2133`](https://github.com/Human-Connection/Human-Connection/pull/2133) +- build(deps-dev): bump @babel/register from 7.6.2 to 7.7.0 in /backend [`#2131`](https://github.com/Human-Connection/Human-Connection/pull/2131) +- build(deps): bump graphql-middleware from 4.0.1 to 4.0.2 in /backend [`#2130`](https://github.com/Human-Connection/Human-Connection/pull/2130) +- build(deps-dev): bump @babel/cli from 7.6.4 to 7.7.0 in /backend [`#2129`](https://github.com/Human-Connection/Human-Connection/pull/2129) +- 1851 tags clickable [`#2091`](https://github.com/Human-Connection/Human-Connection/pull/2091) +- build(deps): bump graphql-shield from 7.0.0 to 7.0.1 in /backend [`#2123`](https://github.com/Human-Connection/Human-Connection/pull/2123) +- build(deps): bump merge-graphql-schemas from 1.7.0 to 1.7.2 in /backend [`#2121`](https://github.com/Human-Connection/Human-Connection/pull/2121) +- build(deps-dev): bump vue-loader from 15.7.1 to 15.7.2 in /webapp [`#2122`](https://github.com/Human-Connection/Human-Connection/pull/2122) +- build(deps-dev): bump async-validator from 3.2.0 to 3.2.1 in /webapp [`#2120`](https://github.com/Human-Connection/Human-Connection/pull/2120) +- 🍰 Add migration plan and frontend code guidelines to our docs [`#2075`](https://github.com/Human-Connection/Human-Connection/pull/2075) +- Update feature template [`#2116`](https://github.com/Human-Connection/Human-Connection/pull/2116) +- Update to version 0.1.9 [`#2114`](https://github.com/Human-Connection/Human-Connection/pull/2114) +- remove package-lock.json [`3cf3c31`](https://github.com/Human-Connection/Human-Connection/commit/3cf3c31808dc6ae59fb9c6ec33e9e178c5556438) +- add current file [`26c0d4d`](https://github.com/Human-Connection/Human-Connection/commit/26c0d4d83e4418a2378e05b66b6b47461f82735f) +- Finish portuguese translations [`15c671c`](https://github.com/Human-Connection/Human-Connection/commit/15c671c4a8aae86317896ca30601389504bce9e1) + +#### [v0.1.9](https://github.com/Human-Connection/Human-Connection/compare/v0.1.8...v0.1.9) + +> 4 November 2019 + +- Refactor and tidy up crop image implementation [`#1956`](https://github.com/Human-Connection/Human-Connection/pull/1956) +- 🍰 First Implementation Of Filtering Posts By Language [`#2059`](https://github.com/Human-Connection/Human-Connection/pull/2059) +- build(deps-dev): bump cypress from 3.5.0 to 3.6.0 [`#2105`](https://github.com/Human-Connection/Human-Connection/pull/2105) +- Fix: Poll Interval [`#2108`](https://github.com/Human-Connection/Human-Connection/pull/2108) +- build(deps-dev): bump cypress-plugin-retries from 1.3.0 to 1.4.0 [`#2104`](https://github.com/Human-Connection/Human-Connection/pull/2104) +- build(deps): bump metascraper-title from 5.7.14 to 5.7.17 in /backend [`#2082`](https://github.com/Human-Connection/Human-Connection/pull/2082) +- build(deps): bump metascraper-publisher from 5.7.14 to 5.7.17 in /backend [`#2098`](https://github.com/Human-Connection/Human-Connection/pull/2098) +- build(deps): bump metascraper-description from 5.7.14 to 5.7.17 in /backend [`#2096`](https://github.com/Human-Connection/Human-Connection/pull/2096) +- build(deps): bump metascraper-audio from 5.7.14 to 5.7.17 in /backend [`#2097`](https://github.com/Human-Connection/Human-Connection/pull/2097) +- build(deps): bump metascraper-url from 5.7.14 to 5.7.17 in /backend [`#2086`](https://github.com/Human-Connection/Human-Connection/pull/2086) +- build(deps): bump metascraper-image from 5.7.6 to 5.7.17 in /backend [`#2090`](https://github.com/Human-Connection/Human-Connection/pull/2090) +- deleted posts no longer displayed in user profile [`#2093`](https://github.com/Human-Connection/Human-Connection/pull/2093) +- Send only one notification for mention and comment [`#2062`](https://github.com/Human-Connection/Human-Connection/pull/2062) +- build(deps): bump metascraper-date from 5.7.14 to 5.7.17 in /backend [`#2089`](https://github.com/Human-Connection/Human-Connection/pull/2089) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc16 to 4.0.0-rc17 in /webapp [`#2088`](https://github.com/Human-Connection/Human-Connection/pull/2088) +- build(deps): bump metascraper-lang from 5.7.14 to 5.7.17 in /backend [`#2087`](https://github.com/Human-Connection/Human-Connection/pull/2087) +- build(deps): bump metascraper-video from 5.7.14 to 5.7.17 in /backend [`#2084`](https://github.com/Human-Connection/Human-Connection/pull/2084) +- build(deps): bump metascraper-soundcloud from 5.7.14 to 5.7.17 in /backend [`#2081`](https://github.com/Human-Connection/Human-Connection/pull/2081) +- build(deps-dev): bump auto-changelog from 1.16.1 to 1.16.2 [`#2085`](https://github.com/Human-Connection/Human-Connection/pull/2085) +- build(deps): bump metascraper-logo from 5.7.14 to 5.7.17 in /backend [`#2083`](https://github.com/Human-Connection/Human-Connection/pull/2083) +- build(deps): bump metascraper-youtube from 5.7.14 to 5.7.17 in /backend [`#2080`](https://github.com/Human-Connection/Human-Connection/pull/2080) +- build(deps): bump metascraper-author from 5.7.14 to 5.7.17 in /backend [`#2079`](https://github.com/Human-Connection/Human-Connection/pull/2079) +- build(deps): bump date-fns from 2.5.0 to 2.6.0 in /webapp [`#2007`](https://github.com/Human-Connection/Human-Connection/pull/2007) +- build(deps-dev): bump eslint from 6.5.1 to 6.6.0 in /backend [`#2071`](https://github.com/Human-Connection/Human-Connection/pull/2071) +- build(deps): bump node from 12.13.0-alpine to 13.0.1-alpine in /webapp [`#2019`](https://github.com/Human-Connection/Human-Connection/pull/2019) +- [FIX # 2058] Typo Fixed. Password -> Passwort [`#2060`](https://github.com/Human-Connection/Human-Connection/pull/2060) +- build(deps-dev): bump eslint-config-prettier from 6.4.0 to 6.5.0 in /backend [`#2064`](https://github.com/Human-Connection/Human-Connection/pull/2064) +- build(deps): bump date-fns from 2.5.1 to 2.6.0 in /backend [`#2010`](https://github.com/Human-Connection/Human-Connection/pull/2010) +- build(deps-dev): bump apollo-server-testing from 2.9.6 to 2.9.7 in /backend [`#1984`](https://github.com/Human-Connection/Human-Connection/pull/1984) +- build(deps): bump metascraper-date from 5.7.6 to 5.7.14 in /backend [`#2070`](https://github.com/Human-Connection/Human-Connection/pull/2070) +- build(deps): bump metascraper-video from 5.7.6 to 5.7.14 in /backend [`#2072`](https://github.com/Human-Connection/Human-Connection/pull/2072) +- build(deps-dev): bump eslint-plugin-jest from 22.20.0 to 23.0.2 in /backend [`#2069`](https://github.com/Human-Connection/Human-Connection/pull/2069) +- build(deps): bump metascraper-audio from 5.7.6 to 5.7.14 in /backend [`#2068`](https://github.com/Human-Connection/Human-Connection/pull/2068) +- build(deps-dev): bump eslint-plugin-jest from 23.0.0 to 23.0.2 in /webapp [`#2066`](https://github.com/Human-Connection/Human-Connection/pull/2066) +- build(deps-dev): bump cucumber from 6.0.2 to 6.0.3 in /backend [`#2065`](https://github.com/Human-Connection/Human-Connection/pull/2065) +- build(deps): bump metascraper-logo from 5.7.6 to 5.7.14 in /backend [`#2039`](https://github.com/Human-Connection/Human-Connection/pull/2039) +- build(deps): bump metascraper-url from 5.7.6 to 5.7.14 in /backend [`#2053`](https://github.com/Human-Connection/Human-Connection/pull/2053) +- build(deps): bump metascraper-youtube from 5.7.6 to 5.7.14 in /backend [`#2054`](https://github.com/Human-Connection/Human-Connection/pull/2054) +- build(deps): bump neo4j-graphql-js from 2.7.2 to 2.8.0 in /backend [`#2036`](https://github.com/Human-Connection/Human-Connection/pull/2036) +- build(deps): bump metascraper-soundcloud from 5.7.7 to 5.7.14 in /backend [`#2052`](https://github.com/Human-Connection/Human-Connection/pull/2052) +- build(deps): bump metascraper-author from 5.7.6 to 5.7.14 in /backend [`#2055`](https://github.com/Human-Connection/Human-Connection/pull/2055) +- build(deps-dev): bump eslint-plugin-jest from 22.20.0 to 23.0.0 in /webapp [`#2051`](https://github.com/Human-Connection/Human-Connection/pull/2051) +- build(deps): bump metascraper-title from 5.7.6 to 5.7.14 in /backend [`#2050`](https://github.com/Human-Connection/Human-Connection/pull/2050) +- build(deps-dev): bump eslint-config-prettier from 6.4.0 to 6.5.0 in /webapp [`#2049`](https://github.com/Human-Connection/Human-Connection/pull/2049) +- build(deps): bump metascraper-description from 5.7.6 to 5.7.14 in /backend [`#2038`](https://github.com/Human-Connection/Human-Connection/pull/2038) +- build(deps-dev): bump node-sass from 4.12.0 to 4.13.0 in /webapp [`#2037`](https://github.com/Human-Connection/Human-Connection/pull/2037) +- build(deps): bump metascraper-publisher from 5.7.6 to 5.7.14 in /backend [`#2033`](https://github.com/Human-Connection/Human-Connection/pull/2033) +- build(deps): bump apollo-server from 2.9.6 to 2.9.7 in /backend [`#2004`](https://github.com/Human-Connection/Human-Connection/pull/2004) +- fix #1993 [`#2043`](https://github.com/Human-Connection/Human-Connection/pull/2043) +- Update to version 0.1.8 [`#2032`](https://github.com/Human-Connection/Human-Connection/pull/2032) +- build(deps): bump graphql-shield from 6.1.0 to 7.0.0 in /backend [`#2035`](https://github.com/Human-Connection/Human-Connection/pull/2035) +- build(deps): bump metascraper-lang from 5.7.6 to 5.7.14 in /backend [`#2034`](https://github.com/Human-Connection/Human-Connection/pull/2034) +- change Changes & History to Changes [`#2030`](https://github.com/Human-Connection/Human-Connection/pull/2030) +- chnage Änderungen & Verlauf zu Änderungen [`#2029`](https://github.com/Human-Connection/Human-Connection/pull/2029) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc15 to 4.0.0-rc16 in /webapp [`#1990`](https://github.com/Human-Connection/Human-Connection/pull/1990) +- Fix #2042 Back Link To Login Page [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042) +- Merge pull request #2043 from Human-Connection/fix-1993 [`#1993`](https://github.com/Human-Connection/Human-Connection/issues/1993) +- fix #1993 [`#1993`](https://github.com/Human-Connection/Human-Connection/issues/1993) +- Prepare backend for next implementation step [`7b32243`](https://github.com/Human-Connection/Human-Connection/commit/7b3224327e67a2895e4bc15b8987b13c6f57f015) +- first implementation [`aeae72f`](https://github.com/Human-Connection/Human-Connection/commit/aeae72f6918861aa2a4c64d0b32c847d9e857e93) +- build(deps-dev): bump eslint-plugin-jest in /backend [`6c1bd53`](https://github.com/Human-Connection/Human-Connection/commit/6c1bd535ac482eb0a05d21e227a476800717a19e) + +#### [v0.1.8](https://github.com/Human-Connection/Human-Connection/compare/v0.1.7...v0.1.8) + +> 24 October 2019 + +- add FAQ _blank-href in Footer [`#2028`](https://github.com/Human-Connection/Human-Connection/pull/2028) +- fix: Don't attempt to save locale if not authenticated [`#2025`](https://github.com/Human-Connection/Human-Connection/pull/2025) +- 🍰 New terms and conditions version 0.0.3 [`#2027`](https://github.com/Human-Connection/Human-Connection/pull/2027) +- Remove duplicate pinned post record [`#2023`](https://github.com/Human-Connection/Human-Connection/pull/2023) +- build(deps-dev): bump cypress from 3.4.1 to 3.5.0 [`#2018`](https://github.com/Human-Connection/Human-Connection/pull/2018) +- docs: add missing SUMMARY entry for online backups [`#2024`](https://github.com/Human-Connection/Human-Connection/pull/2024) +- build(deps): bump @nuxtjs/axios from 5.6.0 to 5.8.0 in /webapp [`#2021`](https://github.com/Human-Connection/Human-Connection/pull/2021) +- Locale save by change local switch [`#1991`](https://github.com/Human-Connection/Human-Connection/pull/1991) +- build(deps-dev): bump @storybook/addon-actions from 5.2.4 to 5.2.5 in /webapp [`#1983`](https://github.com/Human-Connection/Human-Connection/pull/1983) +- Hide shout count on user profile unless my profile [`#2016`](https://github.com/Human-Connection/Human-Connection/pull/2016) +- new terms and conditions [`#2017`](https://github.com/Human-Connection/Human-Connection/pull/2017) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.4 to 5.2.5 in /webapp [`#1989`](https://github.com/Human-Connection/Human-Connection/pull/1989) +- build(deps-dev): bump @vue/cli-shared-utils from 4.0.4 to 4.0.5 in /webapp [`#2002`](https://github.com/Human-Connection/Human-Connection/pull/2002) +- Update to version 0.1.7 [`#2015`](https://github.com/Human-Connection/Human-Connection/pull/2015) +- build(deps): bump @nuxtjs/apollo in /webapp [`26c21b5`](https://github.com/Human-Connection/Human-Connection/commit/26c21b5b76c96206d98ff6bbfdbd1ca973ffcd4f) +- Finish redesign of moderators report list [`15d28aa`](https://github.com/Human-Connection/Human-Connection/commit/15d28aa8ef84788aa640aac67838380bfacf63b7) +- build(deps-dev): bump @storybook/addon-actions in /webapp [`7e95d37`](https://github.com/Human-Connection/Human-Connection/commit/7e95d376a311a5ede6351d577d30e25aea9cb65d) + +#### [v0.1.7](https://github.com/Human-Connection/Human-Connection/compare/v0.1.6...v0.1.7) + +> 23 October 2019 + +- Update fetchMore functionality on profile page [`#2012`](https://github.com/Human-Connection/Human-Connection/pull/2012) +- build(deps-dev): bump date-fns from 2.5.1 to 2.6.0 [`#2001`](https://github.com/Human-Connection/Human-Connection/pull/2001) +- build(deps): bump faker from `10bfb9f` to `9fd8d7d` in /backend [`#2000`](https://github.com/Human-Connection/Human-Connection/pull/2000) +- build(deps): bump helmet from 3.21.1 to 3.21.2 in /backend [`#1986`](https://github.com/Human-Connection/Human-Connection/pull/1986) +- build(deps-dev): bump eslint-plugin-jest from 22.19.0 to 22.20.0 in /webapp [`#1985`](https://github.com/Human-Connection/Human-Connection/pull/1985) +- build(deps): bump apollo-server-express from 2.9.6 to 2.9.7 in /backend [`#1982`](https://github.com/Human-Connection/Human-Connection/pull/1982) +- 1997 fix performance issue with pinned post [`#1998`](https://github.com/Human-Connection/Human-Connection/pull/1998) +- Update to version 0.1.6 [`#1996`](https://github.com/Human-Connection/Human-Connection/pull/1996) +- fix: performance issue with ordering [`2a9e182`](https://github.com/Human-Connection/Human-Connection/commit/2a9e1826498e7b6f49b3ba548d42311272070249) +- build(deps-dev): bump @vue/cli-shared-utils in /webapp [`a5d993c`](https://github.com/Human-Connection/Human-Connection/commit/a5d993c761b2f92c3f44b6f83592ea4c1d822606) +- Fix block user workflow [`44e5437`](https://github.com/Human-Connection/Human-Connection/commit/44e54372c4148fafae1095d172d1a52a87b3b1b2) + +#### [v0.1.6](https://github.com/Human-Connection/Human-Connection/compare/v0.1.5...v0.1.6) + +> 22 October 2019 + +- 🍰 Fix - maintaining sorting after navigation [`#1872`](https://github.com/Human-Connection/Human-Connection/pull/1872) +- Add storybook stories for our university students [`#1906`](https://github.com/Human-Connection/Human-Connection/pull/1906) +- build(deps): bump nuxt from 2.10.1 to 2.10.2 in /webapp [`#1987`](https://github.com/Human-Connection/Human-Connection/pull/1987) +- build(deps-dev): bump @storybook/vue from 5.2.4 to 5.2.5 in /webapp [`#1981`](https://github.com/Human-Connection/Human-Connection/pull/1981) +- build(deps-dev): bump eslint-plugin-jest from 22.17.0 to 22.20.0 in /backend [`#1978`](https://github.com/Human-Connection/Human-Connection/pull/1978) +- build(deps): bump node from 12.12.0-alpine to 12.13.0-alpine in /webapp [`#1977`](https://github.com/Human-Connection/Human-Connection/pull/1977) +- build(deps-dev): bump date-fns from 2.5.0 to 2.5.1 [`#1962`](https://github.com/Human-Connection/Human-Connection/pull/1962) +- build(deps): bump faker from `10bfb9f` to `9fd8d7d` in /backend [`#1961`](https://github.com/Human-Connection/Human-Connection/pull/1961) +- build(deps-dev): bump async-validator from 3.1.0 to 3.2.0 in /webapp [`#1920`](https://github.com/Human-Connection/Human-Connection/pull/1920) +- build(deps): bump @sentry/node from 5.7.0 to 5.7.1 in /backend [`#1914`](https://github.com/Human-Connection/Human-Connection/pull/1914) +- Hide new CommentForm while editing a comment [`#1973`](https://github.com/Human-Connection/Human-Connection/pull/1973) +- 🍰 Translate texts for pinning a post to German [`#1972`](https://github.com/Human-Connection/Human-Connection/pull/1972) +- Allow admins to pin a post [`#1840`](https://github.com/Human-Connection/Human-Connection/pull/1840) +- fix: Only one ellipse is displayed [`#1968`](https://github.com/Human-Connection/Human-Connection/pull/1968) +- Refactor reports resolver spec [`#1957`](https://github.com/Human-Connection/Human-Connection/pull/1957) +- Added createdAt date for follow and shout [`#1853`](https://github.com/Human-Connection/Human-Connection/pull/1853) +- build(deps-dev): bump @vue/cli-shared-utils from 3.12.0 to 4.0.4 in /webapp [`#1965`](https://github.com/Human-Connection/Human-Connection/pull/1965) +- build(deps): bump neo4j from 3.5.11-enterprise to 3.5.12-enterprise in /neo4j [`#1963`](https://github.com/Human-Connection/Human-Connection/pull/1963) +- build(deps-dev): bump cypress-file-upload from 3.3.4 to 3.4.0 [`#1960`](https://github.com/Human-Connection/Human-Connection/pull/1960) +- build(deps): bump date-fns from 2.5.0 to 2.5.1 in /backend [`#1959`](https://github.com/Human-Connection/Human-Connection/pull/1959) +- build(deps-dev): bump faker from `10bfb9f` to `9fd8d7d` [`#1958`](https://github.com/Human-Connection/Human-Connection/pull/1958) +- build(deps): bump tiptap-extensions from 1.28.3 to 1.28.4 in /webapp [`#1946`](https://github.com/Human-Connection/Human-Connection/pull/1946) +- build(deps): bump metascraper-soundcloud from 5.7.6 to 5.7.7 in /backend [`#1943`](https://github.com/Human-Connection/Human-Connection/pull/1943) +- fix: console warnings during frontend tests [`#1942`](https://github.com/Human-Connection/Human-Connection/pull/1942) +- Language will be saved in the database of the user during registration [`#1927`](https://github.com/Human-Connection/Human-Connection/pull/1927) +- Update to version 0.1.5 [`#1951`](https://github.com/Human-Connection/Human-Connection/pull/1951) +- Refactor report resolver spec [`cf5042d`](https://github.com/Human-Connection/Human-Connection/commit/cf5042d9a426a80cea2d726068be44de11cf7f50) +- Refactor tests for querying reported resources [`4e42017`](https://github.com/Human-Connection/Human-Connection/commit/4e42017afaa97fa87ec726a5bbd1605cca911375) +- Return pinnedAt date from pinPost resolver/clean up [`be0c804`](https://github.com/Human-Connection/Human-Connection/commit/be0c8044e87e211f2578df151d9d2d11795a135f) + +#### [v0.1.5](https://github.com/Human-Connection/Human-Connection/compare/v0.1.4...v0.1.5) + +> 17 October 2019 + +- build(deps-dev): bump dotenv from 8.1.0 to 8.2.0 [`#1917`](https://github.com/Human-Connection/Human-Connection/pull/1917) +- Fix vue errors for Contribution form [`#1941`](https://github.com/Human-Connection/Human-Connection/pull/1941) +- Follow @alina-beck and @Tirokk PR suggestions [`#1940`](https://github.com/Human-Connection/Human-Connection/pull/1940) +- 🍰 Add Cypher statement for ordering [`#1926`](https://github.com/Human-Connection/Human-Connection/pull/1926) +- Improved comment truncation [`#1925`](https://github.com/Human-Connection/Human-Connection/pull/1925) +- fix: typo in German translation [`#1911`](https://github.com/Human-Connection/Human-Connection/pull/1911) +- build(deps): bump styleguide from `808b3c5` to `d46fc15` [`#1923`](https://github.com/Human-Connection/Human-Connection/pull/1923) +- build(deps): bump date-fns from 2.4.1 to 2.5.0 in /webapp [`#1921`](https://github.com/Human-Connection/Human-Connection/pull/1921) +- build(deps): bump date-fns from 2.4.1 to 2.5.0 in /backend [`#1915`](https://github.com/Human-Connection/Human-Connection/pull/1915) +- Maintain filename for cropped images [`#1935`](https://github.com/Human-Connection/Human-Connection/pull/1935) +- build(deps-dev): bump date-fns from 2.4.1 to 2.5.0 [`#1919`](https://github.com/Human-Connection/Human-Connection/pull/1919) +- build(deps-dev): bump nodemon from 1.19.3 to 1.19.4 in /backend [`#1916`](https://github.com/Human-Connection/Human-Connection/pull/1916) +- build(deps-dev): bump neode from 0.3.3 to 0.3.6 [`#1918`](https://github.com/Human-Connection/Human-Connection/pull/1918) +- Highlight and expand linked comment [`#1903`](https://github.com/Human-Connection/Human-Connection/pull/1903) +- Add missing translations for Title placeholder [`#1910`](https://github.com/Human-Connection/Human-Connection/pull/1910) +- 🍰 Reporting with specific information [`#1797`](https://github.com/Human-Connection/Human-Connection/pull/1797) +- build(deps): bump dotenv from 8.1.0 to 8.2.0 in /backend [`#1912`](https://github.com/Human-Connection/Human-Connection/pull/1912) +- Confirm privacy policy minimum age at registration [`#1907`](https://github.com/Human-Connection/Human-Connection/pull/1907) +- refactor: improve locale imports [`#1904`](https://github.com/Human-Connection/Human-Connection/pull/1904) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.3 to 5.2.4 in /webapp [`#1899`](https://github.com/Human-Connection/Human-Connection/pull/1899) +- build(deps-dev): bump eslint-plugin-jest from 22.17.0 to 22.19.0 in /webapp [`#1889`](https://github.com/Human-Connection/Human-Connection/pull/1889) +- build(deps-dev): bump eslint-plugin-jest from 22.17.0 to 22.19.0 in /backend [`#1888`](https://github.com/Human-Connection/Human-Connection/pull/1888) +- build(deps): bump node from 12.11.1-alpine to 12.12.0-alpine in /webapp [`#1894`](https://github.com/Human-Connection/Human-Connection/pull/1894) +- build(deps-dev): bump @storybook/vue from 5.2.3 to 5.2.4 in /webapp [`#1897`](https://github.com/Human-Connection/Human-Connection/pull/1897) +- build(deps): bump node from 12.11.1-alpine to 12.12.0-alpine in /backend [`#1895`](https://github.com/Human-Connection/Human-Connection/pull/1895) +- build(deps): bump @nuxtjs/sentry from 3.0.0 to 3.0.1 in /webapp [`#1896`](https://github.com/Human-Connection/Human-Connection/pull/1896) +- build(deps-dev): bump @storybook/addon-actions from 5.2.3 to 5.2.4 in /webapp [`#1898`](https://github.com/Human-Connection/Human-Connection/pull/1898) +- 🍰 Refactor Database for Reporting with specific information [`#1878`](https://github.com/Human-Connection/Human-Connection/pull/1878) +- build(deps): bump nuxt from 2.10.0 to 2.10.1 in /webapp [`#1890`](https://github.com/Human-Connection/Human-Connection/pull/1890) +- build(deps-dev): bump @babel/cli from 7.6.3 to 7.6.4 in /backend [`#1868`](https://github.com/Human-Connection/Human-Connection/pull/1868) +- build(deps): bump apollo-server from 2.9.5 to 2.9.6 in /backend [`#1864`](https://github.com/Human-Connection/Human-Connection/pull/1864) +- build(deps-dev): bump apollo-server-testing from 2.9.5 to 2.9.6 in /backend [`#1870`](https://github.com/Human-Connection/Human-Connection/pull/1870) +- build(deps-dev): bump @babel/core from 7.6.3 to 7.6.4 in /webapp [`#1867`](https://github.com/Human-Connection/Human-Connection/pull/1867) +- build(deps-dev): bump core-js from 2.6.9 to 2.6.10 in /webapp [`#1892`](https://github.com/Human-Connection/Human-Connection/pull/1892) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc14 to 4.0.0-rc15 in /webapp [`#1891`](https://github.com/Human-Connection/Human-Connection/pull/1891) +- build(deps): bump @sentry/node from 5.6.2 to 5.7.0 in /backend [`#1871`](https://github.com/Human-Connection/Human-Connection/pull/1871) +- build(deps-dev): bump @vue/cli-shared-utils from 3.11.0 to 3.12.0 in /webapp [`#1869`](https://github.com/Human-Connection/Human-Connection/pull/1869) +- build(deps): bump apollo-server-express from 2.9.5 to 2.9.6 in /backend [`#1866`](https://github.com/Human-Connection/Human-Connection/pull/1866) +- build(deps-dev): bump @babel/core from 7.6.2 to 7.6.4 in /backend [`#1865`](https://github.com/Human-Connection/Human-Connection/pull/1865) +- 🍰 Implement basic image cropping solution [`#1666`](https://github.com/Human-Connection/Human-Connection/pull/1666) +- 1773 refactor rewards spec [`#1884`](https://github.com/Human-Connection/Human-Connection/pull/1884) +- fix: search for email case-insensitively [`#1863`](https://github.com/Human-Connection/Human-Connection/pull/1863) +- Fix translation error in login page [`#1885`](https://github.com/Human-Connection/Human-Connection/pull/1885) +- fix: security vulnerability with yarn resolutions [`#1862`](https://github.com/Human-Connection/Human-Connection/pull/1862) +- Fix embeds settings page [`#1877`](https://github.com/Human-Connection/Human-Connection/pull/1877) +- update version to 0.1.4 [`#1860`](https://github.com/Human-Connection/Human-Connection/pull/1860) +- Update profile posts apollo query [`#1805`](https://github.com/Human-Connection/Human-Connection/pull/1805) +- Add Hall of Fame to README [`#1859`](https://github.com/Human-Connection/Human-Connection/pull/1859) +- Implement public registration [`#1814`](https://github.com/Human-Connection/Human-Connection/pull/1814) +- Refactor embed settings page [`#1861`](https://github.com/Human-Connection/Human-Connection/pull/1861) +- fixed lint errors [`f73ff99`](https://github.com/Human-Connection/Human-Connection/commit/f73ff995e18240192904693416a866fc7a8ddb7a) +- Start adding missing portuguese translation [`33eb000`](https://github.com/Human-Connection/Human-Connection/commit/33eb000ee33e5aa513083450f0a00abd7240efb0) +- refactor: restructure translations and components [`bb5d581`](https://github.com/Human-Connection/Human-Connection/commit/bb5d581906b5e6e723966c3dc687c7f309356841) + +#### [v0.1.4](https://github.com/Human-Connection/Human-Connection/compare/v0.1.3...v0.1.4) + +> 10 October 2019 + +- Save user setting to show embed code II [`#1852`](https://github.com/Human-Connection/Human-Connection/pull/1852) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.16.1 to 1.16.2 [`#1855`](https://github.com/Human-Connection/Human-Connection/pull/1855) +- build(deps): bump nodemailer from 6.3.0 to 6.3.1 in /backend [`#1854`](https://github.com/Human-Connection/Human-Connection/pull/1854) +- build(deps-dev): bump @babel/preset-env from 7.6.2 to 7.6.3 in /webapp [`#1849`](https://github.com/Human-Connection/Human-Connection/pull/1849) +- build(deps-dev): bump @storybook/addon-a11y from 5.2.1 to 5.2.3 in /webapp [`#1836`](https://github.com/Human-Connection/Human-Connection/pull/1836) +- build(deps-dev): bump @babel/core from 7.6.2 to 7.6.3 in /webapp [`#1848`](https://github.com/Human-Connection/Human-Connection/pull/1848) +- build(deps): bump node from 12.11.0-alpine to 12.11.1-alpine in /backend [`#1837`](https://github.com/Human-Connection/Human-Connection/pull/1837) +- build(deps): bump styleguide from `808b3c5` to `d46fc15` [`#1839`](https://github.com/Human-Connection/Human-Connection/pull/1839) +- build(deps-dev): bump @babel/cli from 7.6.2 to 7.6.3 in /backend [`#1847`](https://github.com/Human-Connection/Human-Connection/pull/1847) +- build(deps-dev): bump @storybook/addon-actions from 5.2.1 to 5.2.3 in /webapp [`#1838`](https://github.com/Human-Connection/Human-Connection/pull/1838) +- build(deps-dev): bump @babel/preset-env from 7.6.2 to 7.6.3 in /backend [`#1846`](https://github.com/Human-Connection/Human-Connection/pull/1846) +- Title character increased from 64 to 100 [`#1850`](https://github.com/Human-Connection/Human-Connection/pull/1850) +- build(deps-dev): bump @babel/node from 7.6.2 to 7.6.3 in /backend [`#1843`](https://github.com/Human-Connection/Human-Connection/pull/1843) +- build(deps): bump node from 12.11.0-alpine to 12.11.1-alpine in /webapp [`#1835`](https://github.com/Human-Connection/Human-Connection/pull/1835) +- build(deps-dev): bump @storybook/vue from 5.2.1 to 5.2.3 in /webapp [`#1834`](https://github.com/Human-Connection/Human-Connection/pull/1834) +- build(deps-dev): bump cucumber from 6.0.1 to 6.0.2 in /backend [`#1833`](https://github.com/Human-Connection/Human-Connection/pull/1833) +- build(deps): bump tiptap-extensions from 1.28.0 to 1.28.3 in /webapp [`#1822`](https://github.com/Human-Connection/Human-Connection/pull/1822) +- Fix typo in email template [`#1829`](https://github.com/Human-Connection/Human-Connection/pull/1829) +- build(deps-dev): bump apollo-server-testing from 2.9.4 to 2.9.5 in /backend [`#1823`](https://github.com/Human-Connection/Human-Connection/pull/1823) +- build(deps-dev): bump cucumber from 5.1.0 to 6.0.1 in /backend [`#1827`](https://github.com/Human-Connection/Human-Connection/pull/1827) +- build(deps): bump node from 12.10.0-alpine to 12.11.0-alpine in /backend [`#1826`](https://github.com/Human-Connection/Human-Connection/pull/1826) +- Display user email for administrators [`#1808`](https://github.com/Human-Connection/Human-Connection/pull/1808) +- build(deps): bump tiptap from 1.26.0 to 1.26.3 in /webapp [`#1821`](https://github.com/Human-Connection/Human-Connection/pull/1821) +- build(deps): bump apollo-server from 2.9.4 to 2.9.5 in /backend [`#1825`](https://github.com/Human-Connection/Human-Connection/pull/1825) +- build(deps): bump node from 12.10.0-alpine to 12.11.0-alpine in /webapp [`#1824`](https://github.com/Human-Connection/Human-Connection/pull/1824) +- build(deps-dev): bump eslint-config-prettier from 6.3.0 to 6.4.0 in /webapp [`#1819`](https://github.com/Human-Connection/Human-Connection/pull/1819) +- build(deps-dev): bump eslint-config-prettier from 6.3.0 to 6.4.0 in /backend [`#1818`](https://github.com/Human-Connection/Human-Connection/pull/1818) +- build(deps): bump @hapi/joi from 16.1.5 to 16.1.7 in /backend [`#1817`](https://github.com/Human-Connection/Human-Connection/pull/1817) +- build(deps-dev): bump cypress-cucumber-preprocessor from 1.16.0 to 1.16.1 [`#1816`](https://github.com/Human-Connection/Human-Connection/pull/1816) +- Update to 0.1.3 [`#1813`](https://github.com/Human-Connection/Human-Connection/pull/1813) +- Extract logic to EmbedComponent to separate concerns [`da1df27`](https://github.com/Human-Connection/Human-Connection/commit/da1df277991015bd7c48a1fc551244b4288a75f3) +- Fix lint, update tests [`bced698`](https://github.com/Human-Connection/Human-Connection/commit/bced6983ea1f51736e989eab6a41166723a6a6ca) +- add test embeds and links [`7cc139e`](https://github.com/Human-Connection/Human-Connection/commit/7cc139e879ac7ea912e82ea7eff14f7b67eddb4a) + +#### [v0.1.3](https://github.com/Human-Connection/Human-Connection/compare/v0.1.2...v0.1.3) + +> 4 October 2019 + +- Show that a Post/Comment has been edited [`#1807`](https://github.com/Human-Connection/Human-Connection/pull/1807) +- Max aspect ratio of 1:1 for Post index page [`#1796`](https://github.com/Human-Connection/Human-Connection/pull/1796) +- fixes#1305 lastActiveAt [`#1809`](https://github.com/Human-Connection/Human-Connection/pull/1809) +- Remove language toggle from email template [`#1798`](https://github.com/Human-Connection/Human-Connection/pull/1798) +- build(deps): bump nuxt from 2.9.2 to 2.10.0 in /webapp [`#1804`](https://github.com/Human-Connection/Human-Connection/pull/1804) +- build(deps): bump cross-env from 6.0.2 to 6.0.3 in /backend [`#1801`](https://github.com/Human-Connection/Human-Connection/pull/1801) +- fix invites count calculation for admin dashboard [`#1806`](https://github.com/Human-Connection/Human-Connection/pull/1806) +- build(deps-dev): bump cross-env from 6.0.2 to 6.0.3 [`#1800`](https://github.com/Human-Connection/Human-Connection/pull/1800) +- build(deps): bump cross-env from 6.0.2 to 6.0.3 in /webapp [`#1803`](https://github.com/Human-Connection/Human-Connection/pull/1803) +- build(deps): bump @hapi/joi from 16.1.4 to 16.1.5 in /backend [`#1802`](https://github.com/Human-Connection/Human-Connection/pull/1802) +- Update neo4j to Enterprise edition [`#1787`](https://github.com/Human-Connection/Human-Connection/pull/1787) +- Update to 0.1.2 [`#1786`](https://github.com/Human-Connection/Human-Connection/pull/1786) +- Update orderBy, remove >=, more realistic seed data [`3b5e39c`](https://github.com/Human-Connection/Human-Connection/commit/3b5e39c54dafc2e701eb3ce1e830737a9b4bcf82) +- Set hasMore to false when returned Posts are equal to pageSize [`6f1c5e3`](https://github.com/Human-Connection/Human-Connection/commit/6f1c5e3efa3b77e72172592a0b5e4ea52158e642) +- refactor: use named slot for additional text [`3912b21`](https://github.com/Human-Connection/Human-Connection/commit/3912b21ea2f24e2e25682060b7166d1511442e6e) + +#### [v0.1.2](https://github.com/Human-Connection/Human-Connection/compare/v0.1.1...v0.1.2) + +> 2 October 2019 + +- 407 change your email address [`#1711`](https://github.com/Human-Connection/Human-Connection/pull/1711) +- build(deps): bump cross-env from 6.0.0 to 6.0.2 in /backend [`#1794`](https://github.com/Human-Connection/Human-Connection/pull/1794) +- build(deps-dev): bump cross-env from 6.0.0 to 6.0.2 [`#1793`](https://github.com/Human-Connection/Human-Connection/pull/1793) +- Update Post query for blockedByUsers [`#1788`](https://github.com/Human-Connection/Human-Connection/pull/1788) +- build(deps): bump metascraper-description from 5.7.5 to 5.7.6 in /backend [`#1792`](https://github.com/Human-Connection/Human-Connection/pull/1792) +- build(deps): bump cross-env from 6.0.0 to 6.0.2 in /webapp [`#1791`](https://github.com/Human-Connection/Human-Connection/pull/1791) +- build(deps): bump @nuxtjs/apollo from 4.0.0-rc13.1 to 4.0.0-rc14 in /webapp [`#1790`](https://github.com/Human-Connection/Human-Connection/pull/1790) +- Bump metascraper-audio from 5.7.5 to 5.7.6 in /backend [`#1779`](https://github.com/Human-Connection/Human-Connection/pull/1779) +- fix the bug with scrolling post comments into view [`#1701`](https://github.com/Human-Connection/Human-Connection/pull/1701) +- Bump metascraper-publisher from 5.7.4 to 5.7.6 in /backend [`#1778`](https://github.com/Human-Connection/Human-Connection/pull/1778) +- Bump eslint from 6.4.0 to 6.5.1 in /backend [`#1780`](https://github.com/Human-Connection/Human-Connection/pull/1780) +- Bump metascraper-url from 5.7.5 to 5.7.6 in /backend [`#1782`](https://github.com/Human-Connection/Human-Connection/pull/1782) +- Bump metascraper-logo from 5.7.5 to 5.7.6 in /backend [`#1783`](https://github.com/Human-Connection/Human-Connection/pull/1783) +- Bump graphql-middleware from 3.0.5 to 4.0.1 in /backend [`#1781`](https://github.com/Human-Connection/Human-Connection/pull/1781) +- Bump metascraper-youtube from 5.7.5 to 5.7.6 in /backend [`#1777`](https://github.com/Human-Connection/Human-Connection/pull/1777) +- Bump @babel/preset-env from 7.6.0 to 7.6.2 in /backend [`#1776`](https://github.com/Human-Connection/Human-Connection/pull/1776) +- Bump metascraper-soundcloud from 5.7.4 to 5.7.6 in /backend [`#1775`](https://github.com/Human-Connection/Human-Connection/pull/1775) +- Bump metascraper-author from 5.7.4 to 5.7.6 in /backend [`#1774`](https://github.com/Human-Connection/Human-Connection/pull/1774) +- Bump eslint-loader from 3.0.1 to 3.0.2 in /webapp [`#1760`](https://github.com/Human-Connection/Human-Connection/pull/1760) +- Bump tiptap from 1.25.0 to 1.26.0 in /webapp [`#1765`](https://github.com/Human-Connection/Human-Connection/pull/1765) +- Bump metascraper-lang from 5.7.4 to 5.7.6 in /backend [`#1766`](https://github.com/Human-Connection/Human-Connection/pull/1766) +- Bump date-fns from 2.4.0 to 2.4.1 in /webapp [`#1768`](https://github.com/Human-Connection/Human-Connection/pull/1768) +- Bump metascraper-date from 5.7.4 to 5.7.6 in /backend [`#1771`](https://github.com/Human-Connection/Human-Connection/pull/1771) +- fix: Github's security vulnerability warning [`#1751`](https://github.com/Human-Connection/Human-Connection/pull/1751) +- 🍰 Notifications self update and refactoring [`#1658`](https://github.com/Human-Connection/Human-Connection/pull/1658) +- Bump @babel/node from 7.6.1 to 7.6.2 in /backend [`#1725`](https://github.com/Human-Connection/Human-Connection/pull/1725) +- fix email middleware transport config [`#1757`](https://github.com/Human-Connection/Human-Connection/pull/1757) +- Bump nodemon from 1.19.2 to 1.19.3 in /backend [`#1758`](https://github.com/Human-Connection/Human-Connection/pull/1758) +- Bump metascraper-title from 5.7.5 to 5.7.6 in /backend [`#1759`](https://github.com/Human-Connection/Human-Connection/pull/1759) +- Bump metascraper-image from 5.7.5 to 5.7.6 in /backend [`#1761`](https://github.com/Human-Connection/Human-Connection/pull/1761) +- Bump apollo-server from 2.9.3 to 2.9.4 in /backend [`#1762`](https://github.com/Human-Connection/Human-Connection/pull/1762) +- Bump metascraper-video from 5.7.5 to 5.7.6 in /backend [`#1764`](https://github.com/Human-Connection/Human-Connection/pull/1764) +- Bump graphql-middleware-sentry from 3.2.0 to 3.2.1 in /backend [`#1767`](https://github.com/Human-Connection/Human-Connection/pull/1767) +- Bump date-fns from 2.3.0 to 2.4.1 in /backend [`#1769`](https://github.com/Human-Connection/Human-Connection/pull/1769) +- Bump tiptap-extensions from 1.27.0 to 1.28.0 in /webapp [`#1770`](https://github.com/Human-Connection/Human-Connection/pull/1770) +- Fix failing test [`#1772`](https://github.com/Human-Connection/Human-Connection/pull/1772) +- docs: moves storybook into webapp/README.md [`#1755`](https://github.com/Human-Connection/Human-Connection/pull/1755) +- 1273 fix post page nav suggestions [`#1756`](https://github.com/Human-Connection/Human-Connection/pull/1756) +- Bump @babel/core from 7.6.0 to 7.6.2 in /backend [`#1728`](https://github.com/Human-Connection/Human-Connection/pull/1728) +- [WIP]1706 refactor shout spec [`#1712`](https://github.com/Human-Connection/Human-Connection/pull/1712) +- Bump cypress-file-upload from 3.3.3 to 3.3.4 [`#1671`](https://github.com/Human-Connection/Human-Connection/pull/1671) +- Bump @hapi/joi from 16.1.2 to 16.1.4 in /backend [`#1673`](https://github.com/Human-Connection/Human-Connection/pull/1673) +- Bump metascraper-description from 5.7.4 to 5.7.5 in /backend [`#1700`](https://github.com/Human-Connection/Human-Connection/pull/1700) +- Bump @babel/cli from 7.6.0 to 7.6.2 in /backend [`#1713`](https://github.com/Human-Connection/Human-Connection/pull/1713) +- Bump apollo-server-testing from 2.9.3 to 2.9.4 in /backend [`#1717`](https://github.com/Human-Connection/Human-Connection/pull/1717) +- Bump date-fns from 2.2.1 to 2.4.0 in /webapp [`#1752`](https://github.com/Human-Connection/Human-Connection/pull/1752) +- Bump graphql from 14.5.7 to 14.5.8 in /webapp [`#1729`](https://github.com/Human-Connection/Human-Connection/pull/1729) +- Bump mustache from 3.0.3 to 3.1.0 in /backend [`#1655`](https://github.com/Human-Connection/Human-Connection/pull/1655) +- 🍰 Try to fix VSCode format works against ESLint [`#1749`](https://github.com/Human-Connection/Human-Connection/pull/1749) +- Configure docker to work with storybook [`#1688`](https://github.com/Human-Connection/Human-Connection/pull/1688) +- update neo4j docker-compose config [`#1750`](https://github.com/Human-Connection/Human-Connection/pull/1750) +- wrap email templates in standard layout to minimize duplicate code [`2b490e0`](https://github.com/Human-Connection/Human-Connection/commit/2b490e00d7874e9b15c5a09649daafac9c47bdf0) +- build(deps): bump @nuxtjs/apollo in /webapp [`4648080`](https://github.com/Human-Connection/Human-Connection/commit/4648080a74fa6df60d6bb9b34d1db5030a9d4124) +- Write and refactor backend test which are supposed to fail at first [`6ad9dc2`](https://github.com/Human-Connection/Human-Connection/commit/6ad9dc27e937eb263914846c073172906aa661e1) + +#### [v0.1.1](https://github.com/Human-Connection/Human-Connection/compare/v0.1.0...v0.1.1) + +> 27 September 2019 + +- Update to 0.1.1 [`#1734`](https://github.com/Human-Connection/Human-Connection/pull/1734) +- Bump neo4j from 3.5.9 to 3.5.11 in /neo4j [`#1739`](https://github.com/Human-Connection/Human-Connection/pull/1739) +- Bump graphql from 14.5.7 to 14.5.8 in /backend [`#1738`](https://github.com/Human-Connection/Human-Connection/pull/1738) +- Bump metascraper-youtube from 5.7.4 to 5.7.5 in /backend [`#1737`](https://github.com/Human-Connection/Human-Connection/pull/1737) +- Fix bug UpdateComment, Fix styling on Comment [`#1719`](https://github.com/Human-Connection/Human-Connection/pull/1719) +- Update maintenance page email to support@... [`#1732`](https://github.com/Human-Connection/Human-Connection/pull/1732) +- Bump @babel/register from 7.6.0 to 7.6.2 in /backend [`#1730`](https://github.com/Human-Connection/Human-Connection/pull/1730) +- Bump eslint-loader from 3.0.0 to 3.0.1 in /webapp [`#1727`](https://github.com/Human-Connection/Human-Connection/pull/1727) +- Bump apollo-server-express from 2.9.3 to 2.9.4 in /backend [`#1726`](https://github.com/Human-Connection/Human-Connection/pull/1726) +- Bump metascraper-title from 5.7.4 to 5.7.5 in /backend [`#1715`](https://github.com/Human-Connection/Human-Connection/pull/1715) +- Bump graphql from 14.5.6 to 14.5.7 in /webapp [`#1676`](https://github.com/Human-Connection/Human-Connection/pull/1676) +- Add Comment story, add spacing above user info [`#1685`](https://github.com/Human-Connection/Human-Connection/pull/1685) +- Bump date-fns from 2.2.1 to 2.3.0 in /backend [`#1714`](https://github.com/Human-Connection/Human-Connection/pull/1714) +- Bump metascraper-logo from 5.7.4 to 5.7.5 in /backend [`#1698`](https://github.com/Human-Connection/Human-Connection/pull/1698) +- Bump @babel/preset-env from 7.6.0 to 7.6.2 in /webapp [`#1694`](https://github.com/Human-Connection/Human-Connection/pull/1694) +- Bump graphql from 14.5.6 to 14.5.7 in /backend [`#1675`](https://github.com/Human-Connection/Human-Connection/pull/1675) +- Remove repetitive labels from emote button [`#1702`](https://github.com/Human-Connection/Human-Connection/pull/1702) +- Bump metascraper-video from 5.7.4 to 5.7.5 in /backend [`#1697`](https://github.com/Human-Connection/Human-Connection/pull/1697) +- Bump @babel/core from 7.6.0 to 7.6.2 in /webapp [`#1696`](https://github.com/Human-Connection/Human-Connection/pull/1696) +- Bump metascraper-image from 5.7.4 to 5.7.5 in /backend [`#1695`](https://github.com/Human-Connection/Human-Connection/pull/1695) +- Bump metascraper-audio from 5.7.4 to 5.7.5 in /backend [`#1693`](https://github.com/Human-Connection/Human-Connection/pull/1693) +- Bump metascraper-url from 5.7.4 to 5.7.5 in /backend [`#1692`](https://github.com/Human-Connection/Human-Connection/pull/1692) +- Remove sleep icon from comments list [`#1689`](https://github.com/Human-Connection/Human-Connection/pull/1689) +- Bugfix create user page - missing submit buttons [`#1690`](https://github.com/Human-Connection/Human-Connection/pull/1690) +- Fix create account page has no logo, localisation [`#1681`](https://github.com/Human-Connection/Human-Connection/pull/1681) +- Fix intermittent backend specs [`#1679`](https://github.com/Human-Connection/Human-Connection/pull/1679) +- Improve comments output [`#1678`](https://github.com/Human-Connection/Human-Connection/pull/1678) +- Bump codecov from 3.6.0 to 3.6.1 [`#1670`](https://github.com/Human-Connection/Human-Connection/pull/1670) +- Bump helmet from 3.21.0 to 3.21.1 in /backend [`#1672`](https://github.com/Human-Connection/Human-Connection/pull/1672) +- Bump cookie-universal-nuxt from 2.0.17 to 2.0.18 in /webapp [`#1674`](https://github.com/Human-Connection/Human-Connection/pull/1674) +- Fix intermittent failing test [`#1677`](https://github.com/Human-Connection/Human-Connection/pull/1677) +- Change your own slug [`#1651`](https://github.com/Human-Connection/Human-Connection/pull/1651) +- Make Human Connection a Progressive Web App [`#1668`](https://github.com/Human-Connection/Human-Connection/pull/1668) +- Remove contentExcerpt from comments [`#1667`](https://github.com/Human-Connection/Human-Connection/pull/1667) +- Fix bug where short comments scrub links [`#1649`](https://github.com/Human-Connection/Human-Connection/pull/1649) +- 1612 refactor moderator spec [`#1620`](https://github.com/Human-Connection/Human-Connection/pull/1620) +- Bump codecov from 3.5.0 to 3.6.0 [`#1652`](https://github.com/Human-Connection/Human-Connection/pull/1652) +- Bump @nuxtjs/apollo from 4.0.0-rc13 to 4.0.0-rc13.1 in /webapp [`#1653`](https://github.com/Human-Connection/Human-Connection/pull/1653) +- Bump @hapi/joi from 16.1.1 to 16.1.2 in /backend [`#1654`](https://github.com/Human-Connection/Human-Connection/pull/1654) +- Remove follow type enum [`#1660`](https://github.com/Human-Connection/Human-Connection/pull/1660) +- Point the changelog to Github [`#1646`](https://github.com/Human-Connection/Human-Connection/pull/1646) +- Provider list approval hard cut [`#1647`](https://github.com/Human-Connection/Human-Connection/pull/1647) +- Improve follow/ufollow mutation [`#1596`](https://github.com/Human-Connection/Human-Connection/pull/1596) +- Fix styling issue in comments list [`#1648`](https://github.com/Human-Connection/Human-Connection/pull/1648) +- Bump eslint-plugin-prettier from 3.1.0 to 3.1.1 in /webapp [`#1643`](https://github.com/Human-Connection/Human-Connection/pull/1643) +- Bump eslint-plugin-prettier from 3.1.0 to 3.1.1 in /backend [`#1642`](https://github.com/Human-Connection/Human-Connection/pull/1642) +- Configure emails [`#1599`](https://github.com/Human-Connection/Human-Connection/pull/1599) +- Fix #1639 - No limits for post length [`#1641`](https://github.com/Human-Connection/Human-Connection/pull/1641) +- Exclude broken maintenance-worker docker image [`#1635`](https://github.com/Human-Connection/Human-Connection/pull/1635) +- 1486 make slug more usable [`#1640`](https://github.com/Human-Connection/Human-Connection/pull/1640) +- Bump metascraper-author from 5.6.6 to 5.7.4 in /backend [`#1610`](https://github.com/Human-Connection/Human-Connection/pull/1610) +- Bump @hapi/joi from 16.0.1 to 16.1.1 in /backend [`#1622`](https://github.com/Human-Connection/Human-Connection/pull/1622) +- Bump @storybook/addon-actions from 5.2.0 to 5.2.1 in /webapp [`#1625`](https://github.com/Human-Connection/Human-Connection/pull/1625) +- Update de.json [`#1636`](https://github.com/Human-Connection/Human-Connection/pull/1636) +- Bump cross-env from 5.2.1 to 6.0.0 [`#1621`](https://github.com/Human-Connection/Human-Connection/pull/1621) +- Bump @storybook/vue from 5.2.0 to 5.2.1 in /webapp [`#1624`](https://github.com/Human-Connection/Human-Connection/pull/1624) +- Fix disappearing embeds on comments [`#1618`](https://github.com/Human-Connection/Human-Connection/pull/1618) +- Fix bug where about must not be empty string [`#1630`](https://github.com/Human-Connection/Human-Connection/pull/1630) +- links_to_imprint_and_privacy_policy_changed_to_human-connection.org [`#1615`](https://github.com/Human-Connection/Human-Connection/pull/1615) +- Bump @storybook/addon-a11y from 5.2.0 to 5.2.1 in /webapp [`#1627`](https://github.com/Human-Connection/Human-Connection/pull/1627) +- Bump cross-env from 5.2.1 to 6.0.0 in /backend [`#1623`](https://github.com/Human-Connection/Human-Connection/pull/1623) +- Fix #1631 [`#1631`](https://github.com/Human-Connection/Human-Connection/issues/1631) +- Fix #1650 [`#1650`](https://github.com/Human-Connection/Human-Connection/issues/1650) +- Merge pull request #1641 from Human-Connection/1639_no_limits_for_post_length [`#1639`](https://github.com/Human-Connection/Human-Connection/issues/1639) +- Fix #1639 [`#1639`](https://github.com/Human-Connection/Human-Connection/issues/1639) +- Add slug to User component, fix #1486 [`#1486`](https://github.com/Human-Connection/Human-Connection/issues/1486) +- Test generating changelog [`a15d033`](https://github.com/Human-Connection/Human-Connection/commit/a15d0330f797d989feef5d73da38e168839c26c7) +- Run with tag 0.1.0 [`c634ad2`](https://github.com/Human-Connection/Human-Connection/commit/c634ad264bd99dd1a87a86f870d7877aa751dc38) +- zwischenspeichern [`e4c7c11`](https://github.com/Human-Connection/Human-Connection/commit/e4c7c1125da6f8fa259241b4d3838b1e7b1e24a2) + +#### v0.1.0 + +> 18 September 2019 + +- Bump cross-env from 5.2.1 to 6.0.0 in /webapp [`#1626`](https://github.com/Human-Connection/Human-Connection/pull/1626) +- Don't create accounts for unverified users [`#1619`](https://github.com/Human-Connection/Human-Connection/pull/1619) +- Bump metascraper-title from 5.7.0 to 5.7.4 in /backend [`#1611`](https://github.com/Human-Connection/Human-Connection/pull/1611) +- Resend registration mail if user has not yet registered [`#1617`](https://github.com/Human-Connection/Human-Connection/pull/1617) +- Bump graphql from 14.5.4 to 14.5.6 in /backend [`#1609`](https://github.com/Human-Connection/Human-Connection/pull/1609) +- Bump eslint from 6.3.0 to 6.4.0 in /backend [`#1608`](https://github.com/Human-Connection/Human-Connection/pull/1608) +- Bump @nuxtjs/apollo from 4.0.0-rc12 to 4.0.0-rc13 in /webapp [`#1607`](https://github.com/Human-Connection/Human-Connection/pull/1607) +- Bump metascraper-publisher from 5.6.6 to 5.7.4 in /backend [`#1606`](https://github.com/Human-Connection/Human-Connection/pull/1606) +- Bump date-fns from 2.1.0 to 2.2.1 in /backend [`#1605`](https://github.com/Human-Connection/Human-Connection/pull/1605) +- Bump metascraper-audio from 5.6.6 to 5.7.4 in /backend [`#1604`](https://github.com/Human-Connection/Human-Connection/pull/1604) +- Add CodeTriage helpers badge [`#1603`](https://github.com/Human-Connection/Human-Connection/pull/1603) +- 🍰 Make hashtag links URL safe [`#1571`](https://github.com/Human-Connection/Human-Connection/pull/1571) +- Relase without any artifacts [`#1601`](https://github.com/Human-Connection/Human-Connection/pull/1601) +- Bump metascraper-image from 5.6.6 to 5.7.4 in /backend [`#1582`](https://github.com/Human-Connection/Human-Connection/pull/1582) +- Bump @storybook/addon-a11y from 5.1.11 to 5.2.0 in /webapp [`#1587`](https://github.com/Human-Connection/Human-Connection/pull/1587) +- Bump styleguide from `793556a` to `808b3c5` [`#1574`](https://github.com/Human-Connection/Human-Connection/pull/1574) +- Bump metascraper-soundcloud from 5.6.7 to 5.7.4 in /backend [`#1578`](https://github.com/Human-Connection/Human-Connection/pull/1578) +- Bump metascraper-video from 5.6.6 to 5.7.4 in /backend [`#1580`](https://github.com/Human-Connection/Human-Connection/pull/1580) +- Bump metascraper-date from 5.7.0 to 5.7.4 in /backend [`#1584`](https://github.com/Human-Connection/Human-Connection/pull/1584) +- Bump metascraper-lang from 5.6.6 to 5.7.4 in /backend [`#1585`](https://github.com/Human-Connection/Human-Connection/pull/1585) +- Bump @storybook/addon-actions from 5.1.11 to 5.2.0 in /webapp [`#1589`](https://github.com/Human-Connection/Human-Connection/pull/1589) +- Bump graphql from 14.5.4 to 14.5.6 in /webapp [`#1590`](https://github.com/Human-Connection/Human-Connection/pull/1590) +- Fix docker image tags [`#1600`](https://github.com/Human-Connection/Human-Connection/pull/1600) +- Remove deleted/disabled/createdAt from Comment mutations [`#1595`](https://github.com/Human-Connection/Human-Connection/pull/1595) +- Fix bug where Post.createdAt is sometimes null [`#1572`](https://github.com/Human-Connection/Human-Connection/pull/1572) +- Fix missing images on staging [`#1594`](https://github.com/Human-Connection/Human-Connection/pull/1594) +- 🍰 Missing translations [`#1593`](https://github.com/Human-Connection/Human-Connection/pull/1593) +- Fix deployment script for docker images [`#1592`](https://github.com/Human-Connection/Human-Connection/pull/1592) +- Bump @hapi/joi from 15.1.1 to 16.0.1 in /backend [`#1591`](https://github.com/Human-Connection/Human-Connection/pull/1591) +- Bump @nuxtjs/dotenv from 1.4.0 to 1.4.1 in /webapp [`#1588`](https://github.com/Human-Connection/Human-Connection/pull/1588) +- Bump @storybook/vue from 5.1.11 to 5.2.0 in /webapp [`#1586`](https://github.com/Human-Connection/Human-Connection/pull/1586) +- Bump metascraper-description from 5.6.6 to 5.7.4 in /backend [`#1581`](https://github.com/Human-Connection/Human-Connection/pull/1581) +- Bump metascraper-logo from 5.6.6 to 5.7.4 in /backend [`#1579`](https://github.com/Human-Connection/Human-Connection/pull/1579) +- Bump metascraper-youtube from 5.6.7 to 5.7.4 in /backend [`#1577`](https://github.com/Human-Connection/Human-Connection/pull/1577) +- Bump metascraper-url from 5.6.6 to 5.7.4 in /backend [`#1576`](https://github.com/Human-Connection/Human-Connection/pull/1576) +- Bump date-fns from 2.1.0 to 2.2.1 in /webapp [`#1564`](https://github.com/Human-Connection/Human-Connection/pull/1564) +- Bump metascraper-date from 5.6.6 to 5.7.0 in /backend [`#1562`](https://github.com/Human-Connection/Human-Connection/pull/1562) +- Bump metascraper-title from 5.6.6 to 5.7.0 in /backend [`#1561`](https://github.com/Human-Connection/Human-Connection/pull/1561) +- Fix import to add createdAt and updatedAt for comments [`#1573`](https://github.com/Human-Connection/Human-Connection/pull/1573) +- Use automatic Github releases as a CHANGELOG [`#1560`](https://github.com/Human-Connection/Human-Connection/pull/1560) +- Add styleguide as submodule [`#1559`](https://github.com/Human-Connection/Human-Connection/pull/1559) +- Push all docker images at VERSION to dockerhub [`#1558`](https://github.com/Human-Connection/Human-Connection/pull/1558) +- II Add Date to Terms and Conditions #1535 [`#1556`](https://github.com/Human-Connection/Human-Connection/pull/1556) +- 🍰 [Helper] Message for backend tests failed by missing constraints [`#1553`](https://github.com/Human-Connection/Human-Connection/pull/1553) +- Various changes to build pipeline [`#1543`](https://github.com/Human-Connection/Human-Connection/pull/1543) +- Fix navbar styling [`#1557`](https://github.com/Human-Connection/Human-Connection/pull/1557) +- 🍰 Fixes a create and update comment problem in the comments list [`#1537`](https://github.com/Human-Connection/Human-Connection/pull/1537) +- Fix show less link not working reliably [`#1548`](https://github.com/Human-Connection/Human-Connection/pull/1548) +- Bump cypress-plugin-retries from 1.2.2 to 1.3.0 [`#1551`](https://github.com/Human-Connection/Human-Connection/pull/1551) +- fix(header-alignment): Fix logo and locale position. [`#1283`](https://github.com/Human-Connection/Human-Connection/pull/1283) +- Better maintenance page [`#1517`](https://github.com/Human-Connection/Human-Connection/pull/1517) +- Test filter by followed by [`#1542`](https://github.com/Human-Connection/Human-Connection/pull/1542) +- [WIP] 🍰 Update follower count on follow/unfollow [`#1533`](https://github.com/Human-Connection/Human-Connection/pull/1533) +- Remove nodemon, use `nuxt` cli tools [`#1516`](https://github.com/Human-Connection/Human-Connection/pull/1516) +- Bump eslint-config-prettier from 6.2.0 to 6.3.0 in /webapp [`#1546`](https://github.com/Human-Connection/Human-Connection/pull/1546) +- Bump eslint-config-prettier from 6.2.0 to 6.3.0 in /backend [`#1545`](https://github.com/Human-Connection/Human-Connection/pull/1545) +- Refactor email middleware [`#1512`](https://github.com/Human-Connection/Human-Connection/pull/1512) +- Bugfix: `about me` not saved on signup [`#1513`](https://github.com/Human-Connection/Human-Connection/pull/1513) +- Filter posts by emotions [`#1490`](https://github.com/Human-Connection/Human-Connection/pull/1490) +- Bump neode from 0.3.2 to 0.3.3 in /backend [`#1531`](https://github.com/Human-Connection/Human-Connection/pull/1531) +- Bump apollo-link-http from 1.5.15 to 1.5.16 in /backend [`#1528`](https://github.com/Human-Connection/Human-Connection/pull/1528) +- Bump @babel/cli from 7.5.5 to 7.6.0 in /backend [`#1526`](https://github.com/Human-Connection/Human-Connection/pull/1526) +- Bump @babel/preset-env from 7.5.5 to 7.6.0 in /backend [`#1523`](https://github.com/Human-Connection/Human-Connection/pull/1523) +- Bump @babel/node from 7.5.5 to 7.6.1 in /backend [`#1522`](https://github.com/Human-Connection/Human-Connection/pull/1522) +- Remove obsolete code [`#1540`](https://github.com/Human-Connection/Human-Connection/pull/1540) +- Bump @human-connection/styleguide from 0.5.19 to 0.5.21 in /webapp [`#1541`](https://github.com/Human-Connection/Human-Connection/pull/1541) +- Bump @babel/preset-env from 7.5.5 to 7.6.0 in /webapp [`#1532`](https://github.com/Human-Connection/Human-Connection/pull/1532) +- Bump @babel/core from 7.5.5 to 7.6.0 in /backend [`#1530`](https://github.com/Human-Connection/Human-Connection/pull/1530) +- Bump vue-jest from 3.0.4 to 3.0.5 in /webapp [`#1529`](https://github.com/Human-Connection/Human-Connection/pull/1529) +- Bump date-fns from 2.0.1 to 2.1.0 in /webapp [`#1527`](https://github.com/Human-Connection/Human-Connection/pull/1527) +- Bump @babel/core from 7.5.5 to 7.6.0 in /webapp [`#1525`](https://github.com/Human-Connection/Human-Connection/pull/1525) +- Bump graphql-shield from 6.0.6 to 6.1.0 in /backend [`#1524`](https://github.com/Human-Connection/Human-Connection/pull/1524) +- Bump @babel/register from 7.5.5 to 7.6.0 in /backend [`#1520`](https://github.com/Human-Connection/Human-Connection/pull/1520) +- Bump neode from 0.3.2 to 0.3.3 [`#1521`](https://github.com/Human-Connection/Human-Connection/pull/1521) +- Bump apollo-link-context from 1.0.18 to 1.0.19 in /backend [`#1519`](https://github.com/Human-Connection/Human-Connection/pull/1519) +- Bump date-fns from 2.0.1 to 2.1.0 in /backend [`#1518`](https://github.com/Human-Connection/Human-Connection/pull/1518) +- [WIP] Sorting dropdown is aligned with post cards [`#1539`](https://github.com/Human-Connection/Human-Connection/pull/1539) +- Avatar should be centered on profile page [`#1514`](https://github.com/Human-Connection/Human-Connection/pull/1514) +- Fix test by setting value of the mocked hashtag [`#1536`](https://github.com/Human-Connection/Human-Connection/pull/1536) +- Add console log for erros geolocation [`#1534`](https://github.com/Human-Connection/Human-Connection/pull/1534) +- Fix #1505 remove html [`#1508`](https://github.com/Human-Connection/Human-Connection/pull/1508) +- 1506 Anonymize deleted user also for moderators [`#1510`](https://github.com/Human-Connection/Human-Connection/pull/1510) +- Refactor registration.spec.js [`#1507`](https://github.com/Human-Connection/Human-Connection/pull/1507) +- Implement better statistics resolver [`#1493`](https://github.com/Human-Connection/Human-Connection/pull/1493) +- Fix leftover of #1479 [`#1504`](https://github.com/Human-Connection/Human-Connection/pull/1504) +- Fix preview image craziness [`#1509`](https://github.com/Human-Connection/Human-Connection/pull/1509) +- 1487 fix admin page [`#1492`](https://github.com/Human-Connection/Human-Connection/pull/1492) +- Bump neo4j from 3.5.8 to 3.5.9 in /neo4j [`#1497`](https://github.com/Human-Connection/Human-Connection/pull/1497) +- Bump node from 12.9-alpine to 12.10.0-alpine in /backend [`#1498`](https://github.com/Human-Connection/Human-Connection/pull/1498) +- Bump node from 12.9-alpine to 12.10.0-alpine in /webapp [`#1499`](https://github.com/Human-Connection/Human-Connection/pull/1499) +- Bump eslint-plugin-jest from 22.16.0 to 22.17.0 in /backend [`#1495`](https://github.com/Human-Connection/Human-Connection/pull/1495) +- Bump eslint-plugin-jest from 22.16.0 to 22.17.0 in /webapp [`#1496`](https://github.com/Human-Connection/Human-Connection/pull/1496) +- Fix #1394 [`#1479`](https://github.com/Human-Connection/Human-Connection/pull/1479) +- Update maintenace worker image [`#1484`](https://github.com/Human-Connection/Human-Connection/pull/1484) +- Bump eslint-plugin-node from 9.2.0 to 10.0.0 in /webapp [`#1481`](https://github.com/Human-Connection/Human-Connection/pull/1481) +- Bump helmet from 3.20.1 to 3.21.0 in /backend [`#1482`](https://github.com/Human-Connection/Human-Connection/pull/1482) +- Bump eslint-plugin-node from 9.2.0 to 10.0.0 in /backend [`#1483`](https://github.com/Human-Connection/Human-Connection/pull/1483) +- Update export syntax, update maintenance worker/neo4j config [`#1473`](https://github.com/Human-Connection/Human-Connection/pull/1473) +- 1393 fix show less bugginess [`#1476`](https://github.com/Human-Connection/Human-Connection/pull/1476) +- Remove a lot of unused code, reduce graphql schema [`#1480`](https://github.com/Human-Connection/Human-Connection/pull/1480) +- Remove slow sorting options [`#1478`](https://github.com/Human-Connection/Human-Connection/pull/1478) +- If TAC version is missing, return `null` [`#1477`](https://github.com/Human-Connection/Human-Connection/pull/1477) +- Bump metascraper-soundcloud from 5.6.6 to 5.6.7 in /backend [`#1446`](https://github.com/Human-Connection/Human-Connection/pull/1446) +- Bump metascraper-youtube from 5.6.6 to 5.6.7 in /backend [`#1447`](https://github.com/Human-Connection/Human-Connection/pull/1447) +- Cursor fixes [`#1474`](https://github.com/Human-Connection/Human-Connection/pull/1474) +- Check if user has agreed to the current terms and conditions [`#1334`](https://github.com/Human-Connection/Human-Connection/pull/1334) +- Fix related posts section in 'More Info' [`#1475`](https://github.com/Human-Connection/Human-Connection/pull/1475) +- 🍰 Correct the import of tags from the Alpha and refactor editor hashtags [`#1399`](https://github.com/Human-Connection/Human-Connection/pull/1399) +- Bump eslint-config-prettier from 6.1.0 to 6.2.0 in /webapp [`#1461`](https://github.com/Human-Connection/Human-Connection/pull/1461) +- Bump nodemon from 1.19.1 to 1.19.2 in /webapp [`#1458`](https://github.com/Human-Connection/Human-Connection/pull/1458) +- Bump nodemon from 1.19.1 to 1.19.2 in /backend [`#1459`](https://github.com/Human-Connection/Human-Connection/pull/1459) +- Bump eslint-config-prettier from 6.1.0 to 6.2.0 in /backend [`#1460`](https://github.com/Human-Connection/Human-Connection/pull/1460) +- Encode the bearer token instead of hard-coding it [`#1464`](https://github.com/Human-Connection/Human-Connection/pull/1464) +- Bump cross-env from 5.2.0 to 5.2.1 in /webapp [`#1444`](https://github.com/Human-Connection/Human-Connection/pull/1444) +- Bump apollo-server-testing from 2.9.1 to 2.9.3 in /backend [`#1448`](https://github.com/Human-Connection/Human-Connection/pull/1448) +- Bump cross-env from 5.2.0 to 5.2.1 in /backend [`#1450`](https://github.com/Human-Connection/Human-Connection/pull/1450) +- Bump cross-env from 5.2.0 to 5.2.1 [`#1442`](https://github.com/Human-Connection/Human-Connection/pull/1442) +- 1414 Implement soft delete [`#1440`](https://github.com/Human-Connection/Human-Connection/pull/1440) +- Use hyphens to separate words properly [`#1452`](https://github.com/Human-Connection/Human-Connection/pull/1452) +- Disable scrolling behind popover [`#1453`](https://github.com/Human-Connection/Human-Connection/pull/1453) +- Limit suggestions list to 15, add component tests [`#1451`](https://github.com/Human-Connection/Human-Connection/pull/1451) +- Bump apollo-server from 2.9.1 to 2.9.3 in /backend [`#1445`](https://github.com/Human-Connection/Human-Connection/pull/1445) +- Bump eslint from 6.2.2 to 6.3.0 in /backend [`#1443`](https://github.com/Human-Connection/Human-Connection/pull/1443) +- Move Neo4j configuration in configmap [`#1439`](https://github.com/Human-Connection/Human-Connection/pull/1439) +- Bump neo4j-driver from 1.7.5 to 1.7.6 in /backend [`#1433`](https://github.com/Human-Connection/Human-Connection/pull/1433) +- Bump eslint-plugin-node from 9.1.0 to 9.2.0 in /backend [`#1430`](https://github.com/Human-Connection/Human-Connection/pull/1430) +- 1414 Bugfix: Delete notifications if connected post, comment or user is deleted [`#1426`](https://github.com/Human-Connection/Human-Connection/pull/1426) +- Bump graphql from 14.5.3 to 14.5.4 in /backend [`#1428`](https://github.com/Human-Connection/Human-Connection/pull/1428) +- Bump neo4j-driver from 1.7.5 to 1.7.6 [`#1429`](https://github.com/Human-Connection/Human-Connection/pull/1429) +- Bump eslint-plugin-node from 9.1.0 to 9.2.0 in /webapp [`#1431`](https://github.com/Human-Connection/Human-Connection/pull/1431) +- Check there are ids in the badIds array [`#1397`](https://github.com/Human-Connection/Human-Connection/pull/1397) +- Fix database import, pagination and counters on user profile page [`#1351`](https://github.com/Human-Connection/Human-Connection/pull/1351) +- Bump eslint-plugin-jest from 22.15.2 to 22.16.0 in /backend [`#1432`](https://github.com/Human-Connection/Human-Connection/pull/1432) +- Bump sass-loader from 7.3.1 to 8.0.0 in /webapp [`#1434`](https://github.com/Human-Connection/Human-Connection/pull/1434) +- Bump eslint-plugin-jest from 22.15.2 to 22.16.0 in /webapp [`#1435`](https://github.com/Human-Connection/Human-Connection/pull/1435) +- Bump vue-izitoast from 1.2.0 to 1.2.1 in /webapp [`#1436`](https://github.com/Human-Connection/Human-Connection/pull/1436) +- Bump graphql from 14.5.3 to 14.5.4 in /webapp [`#1437`](https://github.com/Human-Connection/Human-Connection/pull/1437) +- Bump cypress-cucumber-preprocessor from 1.15.1 to 1.16.0 [`#1417`](https://github.com/Human-Connection/Human-Connection/pull/1417) +- Bump nuxt-dropzone from 1.0.3 to 1.0.4 in /webapp [`#1423`](https://github.com/Human-Connection/Human-Connection/pull/1423) +- Bump apollo-server from 2.9.0 to 2.9.1 in /backend [`#1424`](https://github.com/Human-Connection/Human-Connection/pull/1424) +- Bump helmet from 3.20.0 to 3.20.1 in /backend [`#1416`](https://github.com/Human-Connection/Human-Connection/pull/1416) +- Bump nuxt from 2.9.1 to 2.9.2 in /webapp [`#1421`](https://github.com/Human-Connection/Human-Connection/pull/1421) +- Bump eslint-config-standard from 14.0.1 to 14.1.0 in /backend [`#1420`](https://github.com/Human-Connection/Human-Connection/pull/1420) +- Bump neo4j-graphql-js from 2.7.1 to 2.7.2 in /backend [`#1419`](https://github.com/Human-Connection/Human-Connection/pull/1419) +- Bump apollo-server-testing from 2.9.0 to 2.9.1 in /backend [`#1418`](https://github.com/Human-Connection/Human-Connection/pull/1418) +- Fix Editor Bugs [`#1406`](https://github.com/Human-Connection/Human-Connection/pull/1406) +- Bump eslint from 6.2.1 to 6.2.2 in /backend [`#1388`](https://github.com/Human-Connection/Human-Connection/pull/1388) +- Update the apollo cache [`#1400`](https://github.com/Human-Connection/Human-Connection/pull/1400) +- Increase nginx client max body size, split size legacy migration [`#1407`](https://github.com/Human-Connection/Human-Connection/pull/1407) +- Bump cypress-cucumber-preprocessor from 1.15.0 to 1.15.1 [`#1410`](https://github.com/Human-Connection/Human-Connection/pull/1410) +- Simplify cypress testing on docker [`#1411`](https://github.com/Human-Connection/Human-Connection/pull/1411) +- [Security] Bump mixin-deep from 1.3.1 to 1.3.2 [`#1409`](https://github.com/Human-Connection/Human-Connection/pull/1409) +- [Security] Bump mixin-deep from 1.3.1 to 1.3.2 in /webapp [`#1408`](https://github.com/Human-Connection/Human-Connection/pull/1408) +- Update to use <client-only> instead of <no-ssr> [`#1398`](https://github.com/Human-Connection/Human-Connection/pull/1398) +- 🍰 1062 notification about comment on post [`#1270`](https://github.com/Human-Connection/Human-Connection/pull/1270) +- Bump graphql from 14.5.0 to 14.5.3 in /backend [`#1385`](https://github.com/Human-Connection/Human-Connection/pull/1385) +- Bump date-fns from 2.0.0 to 2.0.1 in /webapp [`#1381`](https://github.com/Human-Connection/Human-Connection/pull/1381) +- Add memory limits to neo4j deployment [`#1392`](https://github.com/Human-Connection/Human-Connection/pull/1392) +- Bump @nuxtjs/apollo from 4.0.0-rc11 to 4.0.0-rc12 in /webapp [`#1404`](https://github.com/Human-Connection/Human-Connection/pull/1404) +- Bump graphql-shield from 6.0.5 to 6.0.6 in /backend [`#1403`](https://github.com/Human-Connection/Human-Connection/pull/1403) +- Bump cypress-cucumber-preprocessor from 1.14.1 to 1.15.0 [`#1402`](https://github.com/Human-Connection/Human-Connection/pull/1402) +- Bump babel-eslint from 10.0.2 to 10.0.3 in /backend [`#1389`](https://github.com/Human-Connection/Human-Connection/pull/1389) +- Bump eslint-loader from 2.2.1 to 3.0.0 in /webapp [`#1386`](https://github.com/Human-Connection/Human-Connection/pull/1386) +- Bump apollo-server from 2.8.2 to 2.9.0 in /backend [`#1384`](https://github.com/Human-Connection/Human-Connection/pull/1384) +- Bump date-fns from 2.0.0 to 2.0.1 in /backend [`#1382`](https://github.com/Human-Connection/Human-Connection/pull/1382) +- [Security] Bump eslint-utils from 1.3.1 to 1.4.2 in /webapp [`#1401`](https://github.com/Human-Connection/Human-Connection/pull/1401) +- Fix infinite-loading infinitely loading [`#1376`](https://github.com/Human-Connection/Human-Connection/pull/1376) +- Bump apollo-server-testing from 2.8.2 to 2.9.0 in /backend [`#1387`](https://github.com/Human-Connection/Human-Connection/pull/1387) +- Bump babel-eslint from 10.0.2 to 10.0.3 in /webapp [`#1383`](https://github.com/Human-Connection/Human-Connection/pull/1383) +- Bump apollo-server-express from 2.8.2 to 2.9.0 in /backend [`#1380`](https://github.com/Human-Connection/Human-Connection/pull/1380) +- Bump graphql from 14.5.0 to 14.5.3 in /webapp [`#1379`](https://github.com/Human-Connection/Human-Connection/pull/1379) +- Simplify docker entrypoint of neo4j image [`#1377`](https://github.com/Human-Connection/Human-Connection/pull/1377) +- Fix flickering create-button [`#1375`](https://github.com/Human-Connection/Human-Connection/pull/1375) +- Fix "cannot read property id of null" in staging [`#1371`](https://github.com/Human-Connection/Human-Connection/pull/1371) +- 🍰 454 Lokalise unlocalised texts and fixes texts [`#1372`](https://github.com/Human-Connection/Human-Connection/pull/1372) +- Fix UpdatePost resolver/validations [`#1368`](https://github.com/Human-Connection/Human-Connection/pull/1368) +- Use ES6 syntax to avoid repetition [`#1370`](https://github.com/Human-Connection/Human-Connection/pull/1370) +- Bump eslint-config-standard from 14.0.0 to 14.0.1 in /backend [`#1356`](https://github.com/Human-Connection/Human-Connection/pull/1356) +- Bump graphql from 14.4.2 to 14.5.0 in /webapp [`#1358`](https://github.com/Human-Connection/Human-Connection/pull/1358) +- Update vue-izitoast after they released v1.2.0! [`#1366`](https://github.com/Human-Connection/Human-Connection/pull/1366) +- Bump cypress-cucumber-preprocessor from 1.14.0 to 1.14.1 [`#1354`](https://github.com/Human-Connection/Human-Connection/pull/1354) +- Implement visual feedback if filter is active [`#1338`](https://github.com/Human-Connection/Human-Connection/pull/1338) +- Update transitive dependencies [`#1348`](https://github.com/Human-Connection/Human-Connection/pull/1348) +- Fix post count always showing 0 [`#1350`](https://github.com/Human-Connection/Human-Connection/pull/1350) +- Fix expiration dates of JWT and cookie [`#1349`](https://github.com/Human-Connection/Human-Connection/pull/1349) +- Bump cypress-cucumber-preprocessor from 1.13.1 to 1.14.0 [`#1340`](https://github.com/Human-Connection/Human-Connection/pull/1340) +- Bump eslint-plugin-jest from 22.15.1 to 22.15.2 in /webapp [`#1343`](https://github.com/Human-Connection/Human-Connection/pull/1343) +- Update backend/yarn.lock` [`#1346`](https://github.com/Human-Connection/Human-Connection/pull/1346) +- Bump node from 12.8-alpine to 12.9-alpine in /backend [`#1344`](https://github.com/Human-Connection/Human-Connection/pull/1344) +- Bump node from 12.8-alpine to 12.9-alpine in /webapp [`#1345`](https://github.com/Human-Connection/Human-Connection/pull/1345) +- Bump @vue/cli-shared-utils from 3.10.0 to 3.11.0 in /webapp [`#1342`](https://github.com/Human-Connection/Human-Connection/pull/1342) +- Bump eslint-plugin-jest from 22.15.1 to 22.15.2 in /backend [`#1341`](https://github.com/Human-Connection/Human-Connection/pull/1341) +- Visual feedback if filters lead to 0 results [`#1339`](https://github.com/Human-Connection/Human-Connection/pull/1339) +- Bump nuxt from 2.8.1 to 2.9.1 in /webapp [`#1321`](https://github.com/Human-Connection/Human-Connection/pull/1321) +- Bump tiptap-extensions from 1.26.2 to 1.27.0 in /webapp [`#1323`](https://github.com/Human-Connection/Human-Connection/pull/1323) +- Bump tiptap from 1.24.2 to 1.25.0 in /webapp [`#1327`](https://github.com/Human-Connection/Human-Connection/pull/1327) +- Validate that a post cannot be created without categories/too many categories [`#1268`](https://github.com/Human-Connection/Human-Connection/pull/1268) +- Bump date-fns from 2.0.0-beta.5 to 2.0.0 in /backend [`#1320`](https://github.com/Human-Connection/Human-Connection/pull/1320) +- Bump @nuxtjs/axios from 5.5.4 to 5.6.0 in /webapp [`#1329`](https://github.com/Human-Connection/Human-Connection/pull/1329) +- Bump nuxt-dropzone from 1.0.2 to 1.0.3 in /webapp [`#1330`](https://github.com/Human-Connection/Human-Connection/pull/1330) +- Bump apollo-server-testing from 2.8.1 to 2.8.2 in /backend [`#1328`](https://github.com/Human-Connection/Human-Connection/pull/1328) +- Fix vue warnings for MasonryGrid/MasonGridItem [`#1336`](https://github.com/Human-Connection/Human-Connection/pull/1336) +- Bump graphql-shield from 6.0.4 to 6.0.5 in /backend [`#1319`](https://github.com/Human-Connection/Human-Connection/pull/1319) +- Bump sass-loader from 7.2.0 to 7.3.1 in /webapp [`#1318`](https://github.com/Human-Connection/Human-Connection/pull/1318) +- Bump @sentry/node from 5.6.1 to 5.6.2 in /backend [`#1326`](https://github.com/Human-Connection/Human-Connection/pull/1326) +- Bump apollo-server from 2.8.1 to 2.8.2 in /backend [`#1322`](https://github.com/Human-Connection/Human-Connection/pull/1322) +- Bump date-fns from 2.0.0-beta.5 to 2.0.0 in /webapp [`#1325`](https://github.com/Human-Connection/Human-Connection/pull/1325) +- Bump eslint from 6.2.0 to 6.2.1 in /backend [`#1324`](https://github.com/Human-Connection/Human-Connection/pull/1324) +- Bump @nuxtjs/apollo from 4.0.0-rc9 to 4.0.0-rc11 in /webapp [`#1309`](https://github.com/Human-Connection/Human-Connection/pull/1309) +- Fix #1308 [`#1314`](https://github.com/Human-Connection/Human-Connection/pull/1314) +- Fix #1315 [`#1316`](https://github.com/Human-Connection/Human-Connection/pull/1316) +- Add masonry layout grid [`#1256`](https://github.com/Human-Connection/Human-Connection/pull/1256) +- 272 add error reporting [`#1281`](https://github.com/Human-Connection/Human-Connection/pull/1281) +- Bump eslint-config-standard from 13.0.1 to 14.0.0 in /backend [`#1298`](https://github.com/Human-Connection/Human-Connection/pull/1298) +- Bump uuid from 3.3.2 to 3.3.3 in /backend [`#1302`](https://github.com/Human-Connection/Human-Connection/pull/1302) +- Configure dbms.security.procedures [`#1295`](https://github.com/Human-Connection/Human-Connection/pull/1295) +- 🍰 Send out notifications on CreateComment [`#1018`](https://github.com/Human-Connection/Human-Connection/pull/1018) +- Bump neode from 0.3.1 to 0.3.2 in /backend [`#1297`](https://github.com/Human-Connection/Human-Connection/pull/1297) +- Bump eslint-config-prettier from 6.0.0 to 6.1.0 in /webapp [`#1299`](https://github.com/Human-Connection/Human-Connection/pull/1299) +- Bump eslint-config-prettier from 6.0.0 to 6.1.0 in /backend [`#1304`](https://github.com/Human-Connection/Human-Connection/pull/1304) +- Bump eslint-plugin-standard from 4.0.0 to 4.0.1 in /webapp [`#1301`](https://github.com/Human-Connection/Human-Connection/pull/1301) +- Bump eslint-plugin-standard from 4.0.0 to 4.0.1 in /backend [`#1300`](https://github.com/Human-Connection/Human-Connection/pull/1300) +- Bump neode from 0.3.1 to 0.3.2 [`#1296`](https://github.com/Human-Connection/Human-Connection/pull/1296) +- Bump babel-jest from 24.8.0 to 24.9.0 in /backend [`#1284`](https://github.com/Human-Connection/Human-Connection/pull/1284) +- Bump dotenv from 8.0.0 to 8.1.0 [`#1285`](https://github.com/Human-Connection/Human-Connection/pull/1285) +- Bump jest from 24.8.0 to 24.9.0 in /webapp [`#1287`](https://github.com/Human-Connection/Human-Connection/pull/1287) +- Bump eslint from 6.1.0 to 6.2.0 in /backend [`#1288`](https://github.com/Human-Connection/Human-Connection/pull/1288) +- Downgrade @nuxtjs/apollo [`#1294`](https://github.com/Human-Connection/Human-Connection/pull/1294) +- Bump neo4j-graphql-js from 2.7.0 to 2.7.1 in /backend [`#1289`](https://github.com/Human-Connection/Human-Connection/pull/1289) +- Bump date-fns from 2.0.0-beta.4 to 2.0.0-beta.5 in /backend [`#1286`](https://github.com/Human-Connection/Human-Connection/pull/1286) +- Bump date-fns from 2.0.0-beta.4 to 2.0.0-beta.5 in /webapp [`#1290`](https://github.com/Human-Connection/Human-Connection/pull/1290) +- Bump dotenv from 8.0.0 to 8.1.0 in /backend [`#1291`](https://github.com/Human-Connection/Human-Connection/pull/1291) +- Bump babel-jest from 24.8.0 to 24.9.0 in /webapp [`#1292`](https://github.com/Human-Connection/Human-Connection/pull/1292) +- Bump jest from 24.8.0 to 24.9.0 in /backend [`#1293`](https://github.com/Human-Connection/Human-Connection/pull/1293) +- Get rid of property warnings [`#1282`](https://github.com/Human-Connection/Human-Connection/pull/1282) +- Refactor graphql queries [`#1280`](https://github.com/Human-Connection/Human-Connection/pull/1280) +- Fix hashtag db import [`#1278`](https://github.com/Human-Connection/Human-Connection/pull/1278) +- Fix `Cannot read property countUser of null` [`#1277`](https://github.com/Human-Connection/Human-Connection/pull/1277) +- Bump @hapi/joi from 15.1.0 to 15.1.1 in /backend [`#1279`](https://github.com/Human-Connection/Human-Connection/pull/1279) +- Fix user mention menu on production data [`#1271`](https://github.com/Human-Connection/Human-Connection/pull/1271) +- Bump @human-connection/styleguide from 0.5.18 to 0.5.19 in /webapp [`#1274`](https://github.com/Human-Connection/Human-Connection/pull/1274) +- 845 load more [`#1176`](https://github.com/Human-Connection/Human-Connection/pull/1176) +- Bump @human-connection/styleguide from 0.5.17 to 0.5.18 in /webapp [`#1269`](https://github.com/Human-Connection/Human-Connection/pull/1269) +- Bump graphql-middleware from 3.0.3 to 3.0.5 in /backend [`#1265`](https://github.com/Human-Connection/Human-Connection/pull/1265) +- Bump tiptap-extensions from 1.26.1 to 1.26.2 in /webapp [`#1258`](https://github.com/Human-Connection/Human-Connection/pull/1258) +- Bump vue-izitoast from `c246fd7` to `ba6b03e` in /webapp [`#1260`](https://github.com/Human-Connection/Human-Connection/pull/1260) +- Bump neo4j-graphql-js from 2.6.3 to 2.7.0 in /backend [`#1257`](https://github.com/Human-Connection/Human-Connection/pull/1257) +- Bump tiptap from 1.24.1 to 1.24.2 in /webapp [`#1259`](https://github.com/Human-Connection/Human-Connection/pull/1259) +- 🍰 Registration confirmation of terms of use added II [`#1224`](https://github.com/Human-Connection/Human-Connection/pull/1224) +- Localise categories [`#1261`](https://github.com/Human-Connection/Human-Connection/pull/1261) +- 🍰 1054 blocked users [`#1209`](https://github.com/Human-Connection/Human-Connection/pull/1209) +- 🍰 454 Lokalise / translate all unlocalised Texts / Translations / Languages [`#1244`](https://github.com/Human-Connection/Human-Connection/pull/1244) +- Bump @storybook/vue from 5.1.10 to 5.1.11 in /webapp [`#1251`](https://github.com/Human-Connection/Human-Connection/pull/1251) +- Bump @storybook/addon-a11y from 5.1.10 to 5.1.11 in /webapp [`#1250`](https://github.com/Human-Connection/Human-Connection/pull/1250) +- Maintain consistent naming/punctuation conventions [`#1255`](https://github.com/Human-Connection/Human-Connection/pull/1255) +- Bump eslint-plugin-jest from 22.15.0 to 22.15.1 in /webapp [`#1253`](https://github.com/Human-Connection/Human-Connection/pull/1253) +- I had enough of `--ignore-engines` [`#1232`](https://github.com/Human-Connection/Human-Connection/pull/1232) +- Bump @storybook/addon-actions from 5.1.10 to 5.1.11 in /webapp [`#1249`](https://github.com/Human-Connection/Human-Connection/pull/1249) +- Bump eslint-plugin-jest from 22.15.0 to 22.15.1 in /backend [`#1248`](https://github.com/Human-Connection/Human-Connection/pull/1248) +- Bump apollo-cache-inmemory from 1.6.2 to 1.6.3 in /backend [`#1234`](https://github.com/Human-Connection/Human-Connection/pull/1234) +- Bump apollo-cache-inmemory from 1.6.2 to 1.6.3 in /webapp [`#1237`](https://github.com/Human-Connection/Human-Connection/pull/1237) +- Bump cypress-cucumber-preprocessor from 1.13.0 to 1.13.1 [`#1235`](https://github.com/Human-Connection/Human-Connection/pull/1235) +- Bump sass-loader from 7.1.0 to 7.2.0 in /webapp [`#1238`](https://github.com/Human-Connection/Human-Connection/pull/1238) +- Bump apollo-client from 2.6.3 to 2.6.4 in /backend [`#1236`](https://github.com/Human-Connection/Human-Connection/pull/1236) +- Bump apollo-client from 2.6.3 to 2.6.4 in /webapp [`#1239`](https://github.com/Human-Connection/Human-Connection/pull/1239) +- 🍰 [Bugfix] Fix filter menu and refactor default layout [`#1233`](https://github.com/Human-Connection/Human-Connection/pull/1233) +- Bump vue-sweetalert-icons from 4.1.0 to 4.2.0 in /webapp [`#1225`](https://github.com/Human-Connection/Human-Connection/pull/1225) +- Bump node from 12.7-alpine to 12.8-alpine in /webapp [`#1227`](https://github.com/Human-Connection/Human-Connection/pull/1227) +- Bump node from 12.7-alpine to 12.8-alpine in /backend [`#1228`](https://github.com/Human-Connection/Human-Connection/pull/1228) +- Move filter by followers functionality to filter dropdown [`#1169`](https://github.com/Human-Connection/Human-Connection/pull/1169) +- Remove obsolete "MOCKS" [`#1223`](https://github.com/Human-Connection/Human-Connection/pull/1223) +- Emotions on posts [`#1133`](https://github.com/Human-Connection/Human-Connection/pull/1133) +- Bump vue-sweetalert-icons from 4.0.0 to 4.1.0 in /webapp [`#1212`](https://github.com/Human-Connection/Human-Connection/pull/1212) +- Bump eslint-plugin-jest from 22.14.1 to 22.15.0 in /webapp [`#1215`](https://github.com/Human-Connection/Human-Connection/pull/1215) +- Fix expired discord link [`#1220`](https://github.com/Human-Connection/Human-Connection/pull/1220) +- Bump tiptap-extensions from 1.26.0 to 1.26.1 in /webapp [`#1210`](https://github.com/Human-Connection/Human-Connection/pull/1210) +- Stub sweetalert-icon in tests which use [`#1217`](https://github.com/Human-Connection/Human-Connection/pull/1217) +- Add data-mention-id to seeds mentions [`#1218`](https://github.com/Human-Connection/Human-Connection/pull/1218) +- Bump @nuxtjs/style-resources from 0.1.2 to 1.0.0 in /webapp [`#1213`](https://github.com/Human-Connection/Human-Connection/pull/1213) +- Bump eslint-plugin-jest from 22.14.1 to 22.15.0 in /backend [`#1211`](https://github.com/Human-Connection/Human-Connection/pull/1211) +- Fix depreciation warnings [`#1171`](https://github.com/Human-Connection/Human-Connection/pull/1171) +- Add static page for data privacy [`#1174`](https://github.com/Human-Connection/Human-Connection/pull/1174) +- Bump date-fns from 2.0.0-beta.3 to 2.0.0-beta.4 in /webapp [`#1194`](https://github.com/Human-Connection/Human-Connection/pull/1194) +- Bump date-fns from 2.0.0-beta.3 to 2.0.0-beta.4 in /backend [`#1195`](https://github.com/Human-Connection/Human-Connection/pull/1195) +- Bump vue-sweetalert-icons from 3.2.0 to 3.2.1 in /webapp [`#791`](https://github.com/Human-Connection/Human-Connection/pull/791) +- Bump @nuxtjs/dotenv from 1.3.0 to 1.4.0 in /webapp [`#1196`](https://github.com/Human-Connection/Human-Connection/pull/1196) +- Bump neode from 0.3.0 to 0.3.1 in /backend [`#1193`](https://github.com/Human-Connection/Human-Connection/pull/1193) +- Storybook [`#952`](https://github.com/Human-Connection/Human-Connection/pull/952) +- Bump vuex-i18n from 1.13.0 to 1.13.1 in /webapp [`#1192`](https://github.com/Human-Connection/Human-Connection/pull/1192) +- Bump neode from 0.3.0 to 0.3.1 [`#1191`](https://github.com/Human-Connection/Human-Connection/pull/1191) +- Fix terms and conditions template, english and german [`#1185`](https://github.com/Human-Connection/Human-Connection/pull/1185) +- edit layout default for guests [`#1175`](https://github.com/Human-Connection/Human-Connection/pull/1175) +- add page code-of-conduct, english and german [`#1190`](https://github.com/Human-Connection/Human-Connection/pull/1190) +- changes and history template, english, german [`#1184`](https://github.com/Human-Connection/Human-Connection/pull/1184) +- Bump metascraper-lang from 5.6.3 to 5.6.5 in /backend [`#1182`](https://github.com/Human-Connection/Human-Connection/pull/1182) +- Bump @vue/cli-shared-utils from 3.9.0 to 3.10.0 in /webapp [`#1181`](https://github.com/Human-Connection/Human-Connection/pull/1181) +- Bump metascraper-author from 5.6.3 to 5.6.5 in /backend [`#1180`](https://github.com/Human-Connection/Human-Connection/pull/1180) +- Bump @nuxtjs/apollo from 4.0.0-rc9 to 4.0.0-rc10 in /webapp [`#1179`](https://github.com/Human-Connection/Human-Connection/pull/1179) +- Bump metascraper-title from 5.6.3 to 5.6.5 in /backend [`#1178`](https://github.com/Human-Connection/Human-Connection/pull/1178) +- Bump metascraper-image from 5.6.3 to 5.6.5 in /backend [`#1177`](https://github.com/Human-Connection/Human-Connection/pull/1177) +- Bump neode from 0.2.16 to 0.3.0 in /backend [`#1152`](https://github.com/Human-Connection/Human-Connection/pull/1152) +- seed first and then reset [`#1170`](https://github.com/Human-Connection/Human-Connection/pull/1170) +- Bump metascraper-video from 5.6.3 to 5.6.5 in /backend [`#1159`](https://github.com/Human-Connection/Human-Connection/pull/1159) +- Bump tiptap from 1.21.0 to 1.24.0 in /webapp [`#1147`](https://github.com/Human-Connection/Human-Connection/pull/1147) +- Bump metascraper-date from 5.6.3 to 5.6.5 in /backend [`#1160`](https://github.com/Human-Connection/Human-Connection/pull/1160) +- Bump metascraper-publisher from 5.6.3 to 5.6.5 in /backend [`#1163`](https://github.com/Human-Connection/Human-Connection/pull/1163) +- Bump tippy.js from 4.3.4 to 4.3.5 in /webapp [`#1161`](https://github.com/Human-Connection/Human-Connection/pull/1161) +- Bump cypress-file-upload from 3.3.2 to 3.3.3 [`#1158`](https://github.com/Human-Connection/Human-Connection/pull/1158) +- Bump metascraper-description from 5.6.3 to 5.6.5 in /backend [`#1162`](https://github.com/Human-Connection/Human-Connection/pull/1162) +- Bump metascraper-soundcloud from 5.6.3 to 5.6.5 in /backend [`#1164`](https://github.com/Human-Connection/Human-Connection/pull/1164) +- Bump metascraper-url from 5.6.3 to 5.6.5 in /backend [`#1165`](https://github.com/Human-Connection/Human-Connection/pull/1165) +- Bump metascraper-audio from 5.6.3 to 5.6.5 in /backend [`#1166`](https://github.com/Human-Connection/Human-Connection/pull/1166) +- Bump metascraper-logo from 5.6.3 to 5.6.5 in /backend [`#1167`](https://github.com/Human-Connection/Human-Connection/pull/1167) +- Bump eslint-config-standard from 12.0.0 to 13.0.1 in /backend [`#1032`](https://github.com/Human-Connection/Human-Connection/pull/1032) +- Bump neo4j-driver from 1.7.4 to 1.7.5 in /backend [`#677`](https://github.com/Human-Connection/Human-Connection/pull/677) +- Bump @nuxtjs/apollo from 4.0.0-rc4.2 to 4.0.0-rc5 in /webapp [`#632`](https://github.com/Human-Connection/Human-Connection/pull/632) +- Bump neo4j from 3.5.5 to 3.5.8 in /neo4j [`#1117`](https://github.com/Human-Connection/Human-Connection/pull/1117) +- Bump cypress from 3.4.0 to 3.4.1 [`#1135`](https://github.com/Human-Connection/Human-Connection/pull/1135) +- Update metascraper-youtube [`#1156`](https://github.com/Human-Connection/Human-Connection/pull/1156) +- Bump neode from 0.2.18 to 0.3.0 [`#1145`](https://github.com/Human-Connection/Human-Connection/pull/1145) +- Bump apollo-server from 2.8.0 to 2.8.1 in /backend [`#1149`](https://github.com/Human-Connection/Human-Connection/pull/1149) +- Bump apollo-server-testing from 2.8.0 to 2.8.1 in /backend [`#1146`](https://github.com/Human-Connection/Human-Connection/pull/1146) +- Bump eslint-plugin-jest from 22.14.0 to 22.14.1 in /backend [`#1148`](https://github.com/Human-Connection/Human-Connection/pull/1148) +- Bump eslint-plugin-jest from 22.14.0 to 22.14.1 in /webapp [`#1150`](https://github.com/Human-Connection/Human-Connection/pull/1150) +- Bump merge-graphql-schemas from 1.6.1 to 1.7.0 in /backend [`#1151`](https://github.com/Human-Connection/Human-Connection/pull/1151) +- Bump apollo-server-express from 2.8.0 to 2.8.1 in /backend [`#1154`](https://github.com/Human-Connection/Human-Connection/pull/1154) +- Use a link's username, open separate window on click [`#1155`](https://github.com/Human-Connection/Human-Connection/pull/1155) +- Refactor social media backend [`#1139`](https://github.com/Human-Connection/Human-Connection/pull/1139) +- HcContributionForm submit is disabled by default [`#977`](https://github.com/Human-Connection/Human-Connection/pull/977) +- HcContributionForm submit is disabled by default [`#977`](https://github.com/Human-Connection/Human-Connection/pull/977) +- 375 sorting (II) [`#1063`](https://github.com/Human-Connection/Human-Connection/pull/1063) +- Fix console.errors during `yarn run test` [`#1141`](https://github.com/Human-Connection/Human-Connection/pull/1141) +- Bump vuex-i18n from 1.11.0 to 1.13.0 in /webapp [`#1144`](https://github.com/Human-Connection/Human-Connection/pull/1144) +- Bump apollo-server-testing from 2.7.2 to 2.8.0 in /backend [`#1136`](https://github.com/Human-Connection/Human-Connection/pull/1136) +- Bump apollo-server from 2.7.2 to 2.8.0 in /backend [`#1137`](https://github.com/Human-Connection/Human-Connection/pull/1137) +- Bump graphql-middleware from 3.0.2 to 3.0.3 in /backend [`#1138`](https://github.com/Human-Connection/Human-Connection/pull/1138) +- 🍰 Refine social media [`#1053`](https://github.com/Human-Connection/Human-Connection/pull/1053) +- Bump apollo-server from 2.7.0 to 2.7.2 in /backend [`#1128`](https://github.com/Human-Connection/Human-Connection/pull/1128) +- Bump apollo-server-testing from 2.7.0 to 2.7.2 in /backend [`#1127`](https://github.com/Human-Connection/Human-Connection/pull/1127) +- Add details to contribution form [`#936`](https://github.com/Human-Connection/Human-Connection/pull/936) +- Maintenance service to support maintenance mode [`#956`](https://github.com/Human-Connection/Human-Connection/pull/956) +- Gitignore ssh directory [`#1132`](https://github.com/Human-Connection/Human-Connection/pull/1132) +- Bump cookie-universal-nuxt from 2.0.16 to 2.0.17 in /webapp [`#1131`](https://github.com/Human-Connection/Human-Connection/pull/1131) +- Bump eslint-plugin-jest from 22.13.6 to 22.14.0 in /backend [`#1130`](https://github.com/Human-Connection/Human-Connection/pull/1130) +- Bump eslint-plugin-jest from 22.13.6 to 22.14.0 in /webapp [`#1129`](https://github.com/Human-Connection/Human-Connection/pull/1129) +- Bump apollo-server-express from 2.7.0 to 2.7.2 in /backend [`#1126`](https://github.com/Human-Connection/Human-Connection/pull/1126) +- Bump node from 12.6-alpine to 12.7-alpine in /backend [`#1118`](https://github.com/Human-Connection/Human-Connection/pull/1118) +- Bump node from 12.6-alpine to 12.7-alpine in /webapp [`#1116`](https://github.com/Human-Connection/Human-Connection/pull/1116) +- Give more space to neo4j-data [`#1115`](https://github.com/Human-Connection/Human-Connection/pull/1115) +- Bump metascraper-video from 4.10.2 to 5.6.3 in /backend [`#1114`](https://github.com/Human-Connection/Human-Connection/pull/1114) +- Bump helmet from 3.19.0 to 3.20.0 in /backend [`#1113`](https://github.com/Human-Connection/Human-Connection/pull/1113) +- Bump metascraper-image from 4.10.2 to 5.6.3 in /backend [`#1102`](https://github.com/Human-Connection/Human-Connection/pull/1102) +- Bump date-fns from 2.0.0-beta.2 to 2.0.0-beta.3 in /webapp [`#1111`](https://github.com/Human-Connection/Human-Connection/pull/1111) +- Bump date-fns from 2.0.0-beta.1 to 2.0.0-beta.3 in /backend [`#1106`](https://github.com/Human-Connection/Human-Connection/pull/1106) +- Bump cypress-cucumber-preprocessor from 1.12.0 to 1.13.0 [`#1108`](https://github.com/Human-Connection/Human-Connection/pull/1108) +- Bump metascraper-title from 4.10.2 to 5.6.3 in /backend [`#1105`](https://github.com/Human-Connection/Human-Connection/pull/1105) +- Bump metascraper-author from 4.10.2 to 5.6.3 in /backend [`#1103`](https://github.com/Human-Connection/Human-Connection/pull/1103) +- Bump metascraper-lang from 4.10.2 to 5.6.3 in /backend [`#1101`](https://github.com/Human-Connection/Human-Connection/pull/1101) +- Bump eslint-plugin-jest from 22.13.0 to 22.13.6 in /backend [`#1100`](https://github.com/Human-Connection/Human-Connection/pull/1100) +- Bump metascraper-publisher from 4.10.2 to 5.6.3 in /backend [`#1104`](https://github.com/Human-Connection/Human-Connection/pull/1104) +- Bump metascraper-date from 4.10.2 to 5.6.3 in /backend [`#1099`](https://github.com/Human-Connection/Human-Connection/pull/1099) +- Bump @vue/eslint-config-prettier from 4.0.1 to 5.0.0 in /webapp [`#1098`](https://github.com/Human-Connection/Human-Connection/pull/1098) +- Bump eslint-plugin-jest from 22.11.1 to 22.13.6 in /webapp [`#1097`](https://github.com/Human-Connection/Human-Connection/pull/1097) +- Bump eslint from 6.0.1 to 6.1.0 in /backend [`#1091`](https://github.com/Human-Connection/Human-Connection/pull/1091) +- Bump @babel/cli from 7.5.0 to 7.5.5 in /backend [`#1089`](https://github.com/Human-Connection/Human-Connection/pull/1089) +- Bump eslint-plugin-import from 2.18.0 to 2.18.2 in /backend [`#1094`](https://github.com/Human-Connection/Human-Connection/pull/1094) +- Editor embeds merge in nitro embed [`#960`](https://github.com/Human-Connection/Human-Connection/pull/960) +- Bump @babel/core from 7.5.4 to 7.5.5 in /backend [`#1095`](https://github.com/Human-Connection/Human-Connection/pull/1095) +- Bump eslint-plugin-import from 2.18.0 to 2.18.2 in /webapp [`#1093`](https://github.com/Human-Connection/Human-Connection/pull/1093) +- Bump eslint-plugin-jest from 22.11.1 to 22.13.0 in /backend [`#1085`](https://github.com/Human-Connection/Human-Connection/pull/1085) +- Bump neode from 0.2.16 to 0.2.18 [`#1084`](https://github.com/Human-Connection/Human-Connection/pull/1084) +- Bump merge-graphql-schemas from 1.5.8 to 1.6.1 in /backend [`#1083`](https://github.com/Human-Connection/Human-Connection/pull/1083) +- Bump @babel/preset-env from 7.5.4 to 7.5.5 in /backend [`#1073`](https://github.com/Human-Connection/Human-Connection/pull/1073) +- Bump @babel/preset-env from 7.5.4 to 7.5.5 in /webapp [`#1075`](https://github.com/Human-Connection/Human-Connection/pull/1075) +- Bump graphql-shield from 6.0.3 to 6.0.4 in /backend [`#1061`](https://github.com/Human-Connection/Human-Connection/pull/1061) +- Bump apollo-server from 2.6.9 to 2.7.0 in /backend [`#1058`](https://github.com/Human-Connection/Human-Connection/pull/1058) +- Bump @babel/node from 7.5.0 to 7.5.5 in /backend [`#1074`](https://github.com/Human-Connection/Human-Connection/pull/1074) +- Created imprint page, added English translations [`#1080`](https://github.com/Human-Connection/Human-Connection/pull/1080) +- Bump helmet from 3.18.0 to 3.19.0 in /backend [`#1072`](https://github.com/Human-Connection/Human-Connection/pull/1072) +- Bump @babel/register from 7.4.4 to 7.5.5 in /backend [`#1070`](https://github.com/Human-Connection/Human-Connection/pull/1070) +- Bump @babel/core from 7.5.4 to 7.5.5 in /webapp [`#1076`](https://github.com/Human-Connection/Human-Connection/pull/1076) +- Bump eslint-plugin-jest from 22.9.0 to 22.11.1 in /backend [`#1078`](https://github.com/Human-Connection/Human-Connection/pull/1078) +- Bump eslint-plugin-jest from 22.9.0 to 22.11.1 in /webapp [`#1079`](https://github.com/Human-Connection/Human-Connection/pull/1079) +- Bump eslint-plugin-jest from 22.8.0 to 22.9.0 in /backend [`#1060`](https://github.com/Human-Connection/Human-Connection/pull/1060) +- Bump eslint-plugin-jest from 22.8.0 to 22.9.0 in /webapp [`#1057`](https://github.com/Human-Connection/Human-Connection/pull/1057) +- Bump apollo-server-testing from 2.6.9 to 2.7.0 in /backend [`#1056`](https://github.com/Human-Connection/Human-Connection/pull/1056) +- Bump cypress-file-upload from 3.3.1 to 3.3.2 [`#1055`](https://github.com/Human-Connection/Human-Connection/pull/1055) +- Update comment [`#788`](https://github.com/Human-Connection/Human-Connection/pull/788) +- Refactoring: Split User and Email [`#1044`](https://github.com/Human-Connection/Human-Connection/pull/1044) +- Move secrets into secrets kubernetes template [`#1049`](https://github.com/Human-Connection/Human-Connection/pull/1049) +- Bump eslint-plugin-jest from 22.7.2 to 22.8.0 in /webapp [`#1050`](https://github.com/Human-Connection/Human-Connection/pull/1050) +- Bump eslint-plugin-jest from 22.7.2 to 22.8.0 in /backend [`#1052`](https://github.com/Human-Connection/Human-Connection/pull/1052) +- Remove graphql-yoga [`#1040`](https://github.com/Human-Connection/Human-Connection/pull/1040) +- Implement paginating user view for admins [`#1019`](https://github.com/Human-Connection/Human-Connection/pull/1019) +- Filter posts by category [`#969`](https://github.com/Human-Connection/Human-Connection/pull/969) +- Fix #822 by updating the counts of the user [`#1041`](https://github.com/Human-Connection/Human-Connection/pull/1041) +- Fix regression of data import [`#1042`](https://github.com/Human-Connection/Human-Connection/pull/1042) +- Silence codecov [`#1043`](https://github.com/Human-Connection/Human-Connection/pull/1043) +- Bump cypress-file-upload from 3.2.1 to 3.3.1 [`#1046`](https://github.com/Human-Connection/Human-Connection/pull/1046) +- Bump linkify-it from 2.1.0 to 2.2.0 in /webapp [`#1047`](https://github.com/Human-Connection/Human-Connection/pull/1047) +- Bump nodemailer from 6.2.1 to 6.3.0 in /backend [`#1045`](https://github.com/Human-Connection/Human-Connection/pull/1045) +- Sledgehammer the file permission problem on docker [`#1048`](https://github.com/Human-Connection/Human-Connection/pull/1048) +- Bugfix: Signup email was missing email query param [`#1039`](https://github.com/Human-Connection/Human-Connection/pull/1039) +- polish language on current status [`#968`](https://github.com/Human-Connection/Human-Connection/pull/968) +- Refactor CreateComment resolver [`#1038`](https://github.com/Human-Connection/Human-Connection/pull/1038) +- Refactor reward/unreward Badges in backend [`#1016`](https://github.com/Human-Connection/Human-Connection/pull/1016) +- Bump apollo-server from 2.6.8 to 2.6.9 in /backend [`#1034`](https://github.com/Human-Connection/Human-Connection/pull/1034) +- Bump apollo-server-testing from 2.6.8 to 2.6.9 in /backend [`#1029`](https://github.com/Human-Connection/Human-Connection/pull/1029) +- Bump cypress-file-upload from 3.2.0 to 3.2.1 [`#1031`](https://github.com/Human-Connection/Human-Connection/pull/1031) +- Bump wait-on from 3.2.0 to 3.3.0 in /backend [`#1028`](https://github.com/Human-Connection/Human-Connection/pull/1028) +- [Security] Bump lodash from 4.17.11 to 4.17.14 in /webapp [`#1023`](https://github.com/Human-Connection/Human-Connection/pull/1023) +- Bump lodash from 4.17.13 to 4.17.14 in /backend [`#1027`](https://github.com/Human-Connection/Human-Connection/pull/1027) +- 🍰 2019/kw27/data_import_emotions [`#944`](https://github.com/Human-Connection/Human-Connection/pull/944) +- [Security] Bump lodash.template from 4.4.0 to 4.5.0 in /webapp [`#1022`](https://github.com/Human-Connection/Human-Connection/pull/1022) +- [Security] Bump lodash.mergewith from 4.6.1 to 4.6.2 in /backend [`#1021`](https://github.com/Human-Connection/Human-Connection/pull/1021) +- Signup new users as admin [`#994`](https://github.com/Human-Connection/Human-Connection/pull/994) +- Bump @babel/preset-env from 7.5.2 to 7.5.4 in /backend [`#1009`](https://github.com/Human-Connection/Human-Connection/pull/1009) +- Bump @babel/preset-env from 7.4.5 to 7.5.4 in /webapp [`#1013`](https://github.com/Human-Connection/Human-Connection/pull/1013) +- Follow up of #958 [`#1007`](https://github.com/Human-Connection/Human-Connection/pull/1007) +- Bump apollo-server from 2.6.6 to 2.6.8 in /backend [`#1014`](https://github.com/Human-Connection/Human-Connection/pull/1014) +- Bump cypress from 3.3.2 to 3.4.0 [`#1008`](https://github.com/Human-Connection/Human-Connection/pull/1008) +- Bump @babel/core from 7.5.0 to 7.5.4 in /backend [`#1012`](https://github.com/Human-Connection/Human-Connection/pull/1012) +- Bump @babel/core from 7.5.0 to 7.5.4 in /webapp [`#1010`](https://github.com/Human-Connection/Human-Connection/pull/1010) +- Bump apollo-server-testing from 2.6.7 to 2.6.8 in /backend [`#1011`](https://github.com/Human-Connection/Human-Connection/pull/1011) +- Bump lodash from 4.17.11 to 4.17.13 in /backend [`#1015`](https://github.com/Human-Connection/Human-Connection/pull/1015) +- footer created imprint and all language ok [`#999`](https://github.com/Human-Connection/Human-Connection/pull/999) +- 🐛 [Bug] full ads width use with small devices [`#958`](https://github.com/Human-Connection/Human-Connection/pull/958) +- large images are set to maximum height [`#976`](https://github.com/Human-Connection/Human-Connection/pull/976) +- User role not nullable [`#993`](https://github.com/Human-Connection/Human-Connection/pull/993) +- Tags in the Text of a Contribution like Mentions [`#848`](https://github.com/Human-Connection/Human-Connection/pull/848) +- Neo4j uses a different name for its env vars [`#989`](https://github.com/Human-Connection/Human-Connection/pull/989) +- Update docs [`#995`](https://github.com/Human-Connection/Human-Connection/pull/995) +- Bump graphql-shield from 6.0.2 to 6.0.3 in /backend [`#998`](https://github.com/Human-Connection/Human-Connection/pull/998) +- Bump @babel/core from 7.4.5 to 7.5.0 in /backend [`#988`](https://github.com/Human-Connection/Human-Connection/pull/988) +- Bump @babel/preset-env from 7.4.5 to 7.5.2 in /backend [`#996`](https://github.com/Human-Connection/Human-Connection/pull/996) +- Bump node from 12.5-alpine to 12.6-alpine in /backend [`#987`](https://github.com/Human-Connection/Human-Connection/pull/987) +- Bump node from 12.5-alpine to 12.6-alpine in /webapp [`#985`](https://github.com/Human-Connection/Human-Connection/pull/985) +- Bump @babel/preset-env from 7.4.5 to 7.5.0 in /backend [`#984`](https://github.com/Human-Connection/Human-Connection/pull/984) +- Bump eslint-plugin-jest from 22.7.1 to 22.7.2 in /webapp [`#983`](https://github.com/Human-Connection/Human-Connection/pull/983) +- Bump @babel/node from 7.4.5 to 7.5.0 in /backend [`#982`](https://github.com/Human-Connection/Human-Connection/pull/982) +- Bump eslint-plugin-jest from 22.7.1 to 22.7.2 in /backend [`#981`](https://github.com/Human-Connection/Human-Connection/pull/981) +- Bump @babel/core from 7.4.5 to 7.5.0 in /webapp [`#980`](https://github.com/Human-Connection/Human-Connection/pull/980) +- Bump @babel/cli from 7.4.4 to 7.5.0 in /backend [`#979`](https://github.com/Human-Connection/Human-Connection/pull/979) +- Bump eslint-loader from 2.2.0 to 2.2.1 in /webapp [`#978`](https://github.com/Human-Connection/Human-Connection/pull/978) +- Remove request for image in UpdatePost mutation from UI [`#966`](https://github.com/Human-Connection/Human-Connection/pull/966) +- Bump graphql from 14.4.0 to 14.4.2 in /backend [`#973`](https://github.com/Human-Connection/Human-Connection/pull/973) +- Bump graphql from 14.4.0 to 14.4.2 in /webapp [`#972`](https://github.com/Human-Connection/Human-Connection/pull/972) +- Bump @vue/cli-shared-utils from 3.8.0 to 3.9.0 in /webapp [`#971`](https://github.com/Human-Connection/Human-Connection/pull/971) +- Bump eslint-loader from 2.1.2 to 2.2.0 in /webapp [`#970`](https://github.com/Human-Connection/Human-Connection/pull/970) +- 943 bring user mutation under control [`#945`](https://github.com/Human-Connection/Human-Connection/pull/945) +- long links and content is wrapped [`#963`](https://github.com/Human-Connection/Human-Connection/pull/963) +- Remove unused query as cleanup [`#951`](https://github.com/Human-Connection/Human-Connection/pull/951) +- 🐛 [Bug] : long comments can be folded up and down [`#864`](https://github.com/Human-Connection/Human-Connection/pull/864) +- the one-time reporting of a user, post or comment [`#908`](https://github.com/Human-Connection/Human-Connection/pull/908) +- Add basic styling for comment list [`#955`](https://github.com/Human-Connection/Human-Connection/pull/955) +- Refactor code to extract query to it's own file [`#954`](https://github.com/Human-Connection/Human-Connection/pull/954) +- Bump metascraper-soundcloud from 4.8.5 to 5.5.3 [`#76`](https://github.com/Human-Connection/Human-Connection/pull/76) +- Bump apollo-server from 2.3.1 to 2.6.7 [`#75`](https://github.com/Human-Connection/Human-Connection/pull/75) +- Bump metascraper-url from 4.8.5 to 5.5.0 [`#73`](https://github.com/Human-Connection/Human-Connection/pull/73) +- Bump metascraper-video from 4.8.5 to 5.5.0 [`#72`](https://github.com/Human-Connection/Human-Connection/pull/72) +- Bump metascraper-audio from 4.8.5 to 5.5.0 [`#71`](https://github.com/Human-Connection/Human-Connection/pull/71) +- Bump metascraper-logo from 4.8.5 to 5.5.0 [`#69`](https://github.com/Human-Connection/Human-Connection/pull/69) +- Bump metascraper-clearbit-logo from 4.8.5 to 5.3.0 [`#25`](https://github.com/Human-Connection/Human-Connection/pull/25) +- Bump metascraper-description from 4.8.5 to 5.5.0 [`#67`](https://github.com/Human-Connection/Human-Connection/pull/67) +- Bump metascraper from 4.8.5 to 4.10.3 [`#17`](https://github.com/Human-Connection/Human-Connection/pull/17) +- Bump got from 9.5.0 to 9.6.0 [`#7`](https://github.com/Human-Connection/Human-Connection/pull/7) +- Bump graphql-shield from 5.6.2 to 6.0.2 in /backend [`#948`](https://github.com/Human-Connection/Human-Connection/pull/948) +- Post with categories [`#919`](https://github.com/Human-Connection/Human-Connection/pull/919) +- 907 mobile responsive menu navbar [`#933`](https://github.com/Human-Connection/Human-Connection/pull/933) +- region and place is no longer created twice. [`#905`](https://github.com/Human-Connection/Human-Connection/pull/905) +- Bump node from 12.4-alpine to 12.5-alpine in /backend [`#941`](https://github.com/Human-Connection/Human-Connection/pull/941) +- Bump node from 12.4-alpine to 12.5-alpine in /webapp [`#942`](https://github.com/Human-Connection/Human-Connection/pull/942) +- Bump cypress-file-upload from 3.1.4 to 3.2.0 [`#937`](https://github.com/Human-Connection/Human-Connection/pull/937) +- Patch cypher injection vulnerability [`#935`](https://github.com/Human-Connection/Human-Connection/pull/935) +- Add teaser image to contribution form [`#787`](https://github.com/Human-Connection/Human-Connection/pull/787) +- Bump date-fns from 2.0.0-beta.1 to 2.0.0-beta.2 in /backend [`#930`](https://github.com/Human-Connection/Human-Connection/pull/930) +- Bump date-fns from 2.0.0-alpha.37 to 2.0.0-beta.2 in /webapp [`#928`](https://github.com/Human-Connection/Human-Connection/pull/928) +- Bump apollo-server from 2.6.6 to 2.6.7 in /backend [`#924`](https://github.com/Human-Connection/Human-Connection/pull/924) +- Bump cypress from 3.3.1 to 3.3.2 [`#929`](https://github.com/Human-Connection/Human-Connection/pull/929) +- Bump date-fns from 2.0.0-alpha.37 to 2.0.0-beta.1 in /webapp [`#925`](https://github.com/Human-Connection/Human-Connection/pull/925) +- Bump graphql from 14.3.1 to 14.4.0 in /webapp [`#923`](https://github.com/Human-Connection/Human-Connection/pull/923) +- Bump eslint-config-prettier from 5.0.0 to 6.0.0 in /backend [`#909`](https://github.com/Human-Connection/Human-Connection/pull/909) +- Bump date-fns from 2.0.0-alpha.37 to 2.0.0-beta.1 in /backend [`#922`](https://github.com/Human-Connection/Human-Connection/pull/922) +- Bump graphql from 14.3.1 to 14.4.0 in /backend [`#921`](https://github.com/Human-Connection/Human-Connection/pull/921) +- Bump apollo-server-testing from 2.6.6 to 2.6.7 in /backend [`#920`](https://github.com/Human-Connection/Human-Connection/pull/920) +- Bump apollo-server from 2.6.4 to 2.6.6 in /backend [`#910`](https://github.com/Human-Connection/Human-Connection/pull/910) +- Bump eslint-config-prettier from 5.0.0 to 6.0.0 in /webapp [`#911`](https://github.com/Human-Connection/Human-Connection/pull/911) +- Bump apollo-server-testing from 2.6.4 to 2.6.6 in /backend [`#912`](https://github.com/Human-Connection/Human-Connection/pull/912) +- Bump eslint-plugin-promise from 4.1.1 to 4.2.1 in /webapp [`#913`](https://github.com/Human-Connection/Human-Connection/pull/913) +- Bump eslint-plugin-promise from 4.1.1 to 4.2.1 in /backend [`#914`](https://github.com/Human-Connection/Human-Connection/pull/914) +- Bump date-fns from 2.0.0-alpha.36 to 2.0.0-alpha.37 in /backend [`#915`](https://github.com/Human-Connection/Human-Connection/pull/915) +- Bump date-fns from 2.0.0-alpha.36 to 2.0.0-alpha.37 in /webapp [`#916`](https://github.com/Human-Connection/Human-Connection/pull/916) +- Bump eslint from 5.16.0 to 6.0.1 in /backend [`#917`](https://github.com/Human-Connection/Human-Connection/pull/917) +- Page size of 12 entries on / page [`#881`](https://github.com/Human-Connection/Human-Connection/pull/881) +- Refactor CreateComments functionality [`#901`](https://github.com/Human-Connection/Human-Connection/pull/901) +- Use upstream neo4j-graphql-js [`#882`](https://github.com/Human-Connection/Human-Connection/pull/882) +- Write tests for UpdatePost, refactor tests [`#898`](https://github.com/Human-Connection/Human-Connection/pull/898) +- Bump eslint-plugin-import from 2.17.3 to 2.18.0 in /backend [`#902`](https://github.com/Human-Connection/Human-Connection/pull/902) +- Bump eslint-plugin-vue from 5.2.2 to 5.2.3 in /webapp [`#903`](https://github.com/Human-Connection/Human-Connection/pull/903) +- Bump eslint-plugin-import from 2.17.3 to 2.18.0 in /webapp [`#904`](https://github.com/Human-Connection/Human-Connection/pull/904) +- Bump date-fns from 2.0.0-alpha.34 to 2.0.0-alpha.36 in /backend [`#877`](https://github.com/Human-Connection/Human-Connection/pull/877) +- Bump date-fns from 2.0.0-alpha.35 to 2.0.0-alpha.36 in /webapp [`#888`](https://github.com/Human-Connection/Human-Connection/pull/888) +- Bump eslint-plugin-jest from 22.7.0 to 22.7.1 in /backend [`#890`](https://github.com/Human-Connection/Human-Connection/pull/890) +- Bump eslint-plugin-jest from 22.7.0 to 22.7.1 in /webapp [`#889`](https://github.com/Human-Connection/Human-Connection/pull/889) +- Deploy development mailserver [`#893`](https://github.com/Human-Connection/Human-Connection/pull/893) +- Fix failing build due to lorempixel.com being down [`#885`](https://github.com/Human-Connection/Human-Connection/pull/885) +- 🍰 2019/kw25/improve_import_accuracy [`#863`](https://github.com/Human-Connection/Human-Connection/pull/863) +- Add enableDeletionValue to unlock deletion [`#868`](https://github.com/Human-Connection/Human-Connection/pull/868) +- 🍰 2019/kw24/fix_imported_img_urls [`#829`](https://github.com/Human-Connection/Human-Connection/pull/829) +- 801 reset password [`#834`](https://github.com/Human-Connection/Human-Connection/pull/834) +- Bump eslint-plugin-jest from 22.6.4 to 22.7.0 in /webapp [`#871`](https://github.com/Human-Connection/Human-Connection/pull/871) +- Bump apollo-server from 2.6.3 to 2.6.4 in /backend [`#873`](https://github.com/Human-Connection/Human-Connection/pull/873) +- Bump graphql-shield from 5.6.1 to 5.7.1 in /backend [`#876`](https://github.com/Human-Connection/Human-Connection/pull/876) +- Bump date-fns from 2.0.0-alpha.34 to 2.0.0-alpha.35 in /backend [`#870`](https://github.com/Human-Connection/Human-Connection/pull/870) +- Bump date-fns from 2.0.0-alpha.34 to 2.0.0-alpha.35 in /webapp [`#872`](https://github.com/Human-Connection/Human-Connection/pull/872) +- Bump apollo-server-testing from 2.6.3 to 2.6.4 in /backend [`#874`](https://github.com/Human-Connection/Human-Connection/pull/874) +- Bump eslint-plugin-jest from 22.6.4 to 22.7.0 in /backend [`#875`](https://github.com/Human-Connection/Human-Connection/pull/875) +- Bump graphql-shield from 5.3.8 to 5.6.1 in /backend [`#866`](https://github.com/Human-Connection/Human-Connection/pull/866) +- Bump graphql-yoga from 1.17.4 to 1.18.0 in /backend [`#865`](https://github.com/Human-Connection/Human-Connection/pull/865) +- Set up editor placeholder to use Vuex [`#836`](https://github.com/Human-Connection/Human-Connection/pull/836) +- Bump babel-eslint from 10.0.1 to 10.0.2 in /backend [`#851`](https://github.com/Human-Connection/Human-Connection/pull/851) +- Bump babel-eslint from 10.0.1 to 10.0.2 in /webapp [`#854`](https://github.com/Human-Connection/Human-Connection/pull/854) +- Bump apollo-client from 2.6.2 to 2.6.3 in /webapp [`#861`](https://github.com/Human-Connection/Human-Connection/pull/861) +- Bump apollo-client from 2.6.2 to 2.6.3 in /backend [`#862`](https://github.com/Human-Connection/Human-Connection/pull/862) +- When you call up a post you can scroll to the comments [`#855`](https://github.com/Human-Connection/Human-Connection/pull/855) +- Fix a text description for the filter bubble [`#856`](https://github.com/Human-Connection/Human-Connection/pull/856) +- [Bug] Untranslated Texts in Post Page #764 [`#786`](https://github.com/Human-Connection/Human-Connection/pull/786) +- Bump apollo-link-http from 1.5.14 to 1.5.15 in /backend [`#843`](https://github.com/Human-Connection/Human-Connection/pull/843) +- Bump eslint-config-prettier from 4.3.0 to 5.0.0 in /backend [`#842`](https://github.com/Human-Connection/Human-Connection/pull/842) +- Bump apollo-link-context from 1.0.17 to 1.0.18 in /backend [`#841`](https://github.com/Human-Connection/Human-Connection/pull/841) +- Bump eslint-config-prettier from 4.3.0 to 5.0.0 in /webapp [`#838`](https://github.com/Human-Connection/Human-Connection/pull/838) +- Bump date-fns from 2.0.0-alpha.33 to 2.0.0-alpha.34 in /backend [`#840`](https://github.com/Human-Connection/Human-Connection/pull/840) +- Bump date-fns from 2.0.0-alpha.33 to 2.0.0-alpha.34 in /webapp [`#837`](https://github.com/Human-Connection/Human-Connection/pull/837) +- Add placeholder for new contributions [`#833`](https://github.com/Human-Connection/Human-Connection/pull/833) +- Turn off $root listeners in beforeDestroy [`#831`](https://github.com/Human-Connection/Human-Connection/pull/831) +- Bump tiptap from 1.20.1 to 1.21.0 in /webapp [`#720`](https://github.com/Human-Connection/Human-Connection/pull/720) +- 2019/kw24/import_images_improvements [`#830`](https://github.com/Human-Connection/Human-Connection/pull/830) +- Authorisation problem when disabling a post [`#763`](https://github.com/Human-Connection/Human-Connection/pull/763) +- Create Posts with a specified language, or fallback [`#795`](https://github.com/Human-Connection/Human-Connection/pull/795) +- Delete my User Account functionality [`#740`](https://github.com/Human-Connection/Human-Connection/pull/740) +- Whitelist queries/mutations, fix tests [`#823`](https://github.com/Human-Connection/Human-Connection/pull/823) +- Bump date-fns from 2.0.0-alpha.31 to 2.0.0-alpha.33 in /backend [`#825`](https://github.com/Human-Connection/Human-Connection/pull/825) +- Follow up on #820 [`#828`](https://github.com/Human-Connection/Human-Connection/pull/828) +- Bump cypress-file-upload from 3.1.3 to 3.1.4 [`#826`](https://github.com/Human-Connection/Human-Connection/pull/826) +- Bump date-fns from 2.0.0-alpha.32 to 2.0.0-alpha.33 in /webapp [`#827`](https://github.com/Human-Connection/Human-Connection/pull/827) +- Bump cypress-cucumber-preprocessor from 1.11.2 to 1.12.0 [`#813`](https://github.com/Human-Connection/Human-Connection/pull/813) +- Bump apollo-server-testing from 2.6.2 to 2.6.3 in /backend [`#819`](https://github.com/Human-Connection/Human-Connection/pull/819) +- Bump apollo-server from 2.6.2 to 2.6.3 in /backend [`#818`](https://github.com/Human-Connection/Human-Connection/pull/818) +- Bump date-fns from 2.0.0-alpha.31 to 2.0.0-alpha.32 in /backend [`#817`](https://github.com/Human-Connection/Human-Connection/pull/817) +- Bump date-fns from 2.0.0-alpha.31 to 2.0.0-alpha.32 in /webapp [`#815`](https://github.com/Human-Connection/Human-Connection/pull/815) +- Bump cypress-file-upload from 3.1.2 to 3.1.3 [`#812`](https://github.com/Human-Connection/Human-Connection/pull/812) +- Implement prefix of image urls with a filter [`#821`](https://github.com/Human-Connection/Human-Connection/pull/821) +- Revert changes to cypress record [`#824`](https://github.com/Human-Connection/Human-Connection/pull/824) +- Fix #800 [`#809`](https://github.com/Human-Connection/Human-Connection/pull/809) +- Fix #799 [`#810`](https://github.com/Human-Connection/Human-Connection/pull/810) +- Fix #470 [`#807`](https://github.com/Human-Connection/Human-Connection/pull/807) +- Bump tiptap-extensions from 1.22.1 to 1.22.2 in /webapp [`#798`](https://github.com/Human-Connection/Human-Connection/pull/798) +- Bump graphql-shield from 5.3.7 to 5.3.8 in /backend [`#797`](https://github.com/Human-Connection/Human-Connection/pull/797) +- 402 userprofil tab click function [`#754`](https://github.com/Human-Connection/Human-Connection/pull/754) +- Bump prettier from 1.17.1 to 1.18.2 in /backend [`#789`](https://github.com/Human-Connection/Human-Connection/pull/789) +- Bump graphql-shield from 5.3.6 to 5.3.7 in /backend [`#796`](https://github.com/Human-Connection/Human-Connection/pull/796) +- Bump prettier from 1.18.0 to 1.18.2 in /webapp [`#794`](https://github.com/Human-Connection/Human-Connection/pull/794) +- Bump tiptap-extensions from 1.21.0 to 1.22.1 in /webapp [`#793`](https://github.com/Human-Connection/Human-Connection/pull/793) +- Bump cookie-universal-nuxt from 2.0.14 to 2.0.16 in /webapp [`#792`](https://github.com/Human-Connection/Human-Connection/pull/792) +- Bump tippy.js from 4.3.3 to 4.3.4 in /webapp [`#790`](https://github.com/Human-Connection/Human-Connection/pull/790) +- Bump apollo-cache-inmemory from 1.6.1 to 1.6.2 in /backend [`#771`](https://github.com/Human-Connection/Human-Connection/pull/771) +- Translate network tab on the left profile page [`#780`](https://github.com/Human-Connection/Human-Connection/pull/780) +- Replace dependency `ms` completely [`#783`](https://github.com/Human-Connection/Human-Connection/pull/783) +- Bump apollo-client from 2.6.1 to 2.6.2 in /webapp [`#772`](https://github.com/Human-Connection/Human-Connection/pull/772) +- Bump apollo-client from 2.6.1 to 2.6.2 in /backend [`#768`](https://github.com/Human-Connection/Human-Connection/pull/768) +- Bump apollo-server-testing from 2.6.1 to 2.6.2 in /backend [`#773`](https://github.com/Human-Connection/Human-Connection/pull/773) +- Bump prettier from 1.17.1 to 1.18.0 in /webapp [`#766`](https://github.com/Human-Connection/Human-Connection/pull/766) +- Bump date-fns from 2.0.0-alpha.29 to 2.0.0-alpha.31 in /webapp [`#770`](https://github.com/Human-Connection/Human-Connection/pull/770) +- Bump apollo-server from 2.6.1 to 2.6.2 in /backend [`#776`](https://github.com/Human-Connection/Human-Connection/pull/776) +- Bump date-fns from 2.0.0-alpha.29 to 2.0.0-alpha.31 in /backend [`#775`](https://github.com/Human-Connection/Human-Connection/pull/775) +- Refactor filter bubble [`#762`](https://github.com/Human-Connection/Human-Connection/pull/762) +- Disable the location.reloads [`#761`](https://github.com/Human-Connection/Human-Connection/pull/761) +- Hover effect user image upload [`#732`](https://github.com/Human-Connection/Human-Connection/pull/732) +- Bump tippy.js from 4.3.2 to 4.3.3 in /webapp [`#742`](https://github.com/Human-Connection/Human-Connection/pull/742) +- Bump @nuxtjs/axios from 5.5.3 to 5.5.4 in /webapp [`#756`](https://github.com/Human-Connection/Human-Connection/pull/756) +- Bump date-fns from 2.0.0-alpha.27 to 2.0.0-alpha.29 in /webapp [`#757`](https://github.com/Human-Connection/Human-Connection/pull/757) +- Bump date-fns from 2.0.0-alpha.27 to 2.0.0-alpha.29 in /backend [`#758`](https://github.com/Human-Connection/Human-Connection/pull/758) +- 269 filter by followed users [`#699`](https://github.com/Human-Connection/Human-Connection/pull/699) +- Bump tiptap-extensions from 1.20.2 to 1.21.0 in /webapp [`#746`](https://github.com/Human-Connection/Human-Connection/pull/746) +- Bump apollo-cache-inmemory from 1.6.0 to 1.6.1 in /backend [`#741`](https://github.com/Human-Connection/Human-Connection/pull/741) +- 269 filter by followed users backend part [`#730`](https://github.com/Human-Connection/Human-Connection/pull/730) +- 206 setup neo4j db constraints on startup [`#739`](https://github.com/Human-Connection/Human-Connection/pull/739) +- Fix deployment - wrong docker image name [`#751`](https://github.com/Human-Connection/Human-Connection/pull/751) +- 402 userprofile shouted tab 2 [`#752`](https://github.com/Human-Connection/Human-Connection/pull/752) +- Bump graphql-shield from 5.3.5 to 5.3.6 in /backend [`#743`](https://github.com/Human-Connection/Human-Connection/pull/743) +- Bump apollo-client from 2.6.0 to 2.6.1 in /webapp [`#744`](https://github.com/Human-Connection/Human-Connection/pull/744) +- Bump apollo-client from 2.5.1 to 2.6.1 in /backend [`#745`](https://github.com/Human-Connection/Human-Connection/pull/745) +- Bump nuxt from 2.8.0 to 2.8.1 in /webapp [`#748`](https://github.com/Human-Connection/Human-Connection/pull/748) +- Bump node from 12.3.1-alpine to 12.4-alpine in /webapp [`#749`](https://github.com/Human-Connection/Human-Connection/pull/749) +- Bump node from 12.3.1-alpine to 12.4-alpine in /backend [`#750`](https://github.com/Human-Connection/Human-Connection/pull/750) +- Add date relative date on comments [`#731`](https://github.com/Human-Connection/Human-Connection/pull/731) +- DISABLED_MIDDLEWARES only set outside production [`#735`](https://github.com/Human-Connection/Human-Connection/pull/735) +- 🍰 release contribution again [`#736`](https://github.com/Human-Connection/Human-Connection/pull/736) +- Bump tippy.js from 4.3.1 to 4.3.2 in /webapp [`#733`](https://github.com/Human-Connection/Human-Connection/pull/733) +- 🍰 2019/kw22/refactor_backend_structure [`#724`](https://github.com/Human-Connection/Human-Connection/pull/724) +- Bump tiptap-extensions from 1.20.1 to 1.20.2 in /webapp [`#721`](https://github.com/Human-Connection/Human-Connection/pull/721) +- User can not anymore delete Comments of others [`#729`](https://github.com/Human-Connection/Human-Connection/pull/729) +- Don't remove middlewares in production [`#725`](https://github.com/Human-Connection/Human-Connection/pull/725) +- Delete Comment [`#609`](https://github.com/Human-Connection/Human-Connection/pull/609) +- Bump apollo-server from 2.5.1 to 2.6.1 in /backend [`#718`](https://github.com/Human-Connection/Human-Connection/pull/718) +- Deploy maintenance worker to dockerhub automatically with smaller split size [`#714`](https://github.com/Human-Connection/Human-Connection/pull/714) +- Bump apollo-server-testing from 2.5.1 to 2.6.1 in /backend [`#719`](https://github.com/Human-Connection/Human-Connection/pull/719) +- Fix upload large file sizes bug [`#698`](https://github.com/Human-Connection/Human-Connection/pull/698) +- Bump node from 10-alpine to 12.3.1-alpine in /webapp [`#712`](https://github.com/Human-Connection/Human-Connection/pull/712) +- Bump prettier from 1.14.3 to 1.17.1 in /webapp [`#663`](https://github.com/Human-Connection/Human-Connection/pull/663) +- 🍰 2019/kw22/alpha_data_import_status_schema_split [`#703`](https://github.com/Human-Connection/Human-Connection/pull/703) +- Bump nuxt from 2.7.1 to 2.8.0 in /webapp [`#711`](https://github.com/Human-Connection/Human-Connection/pull/711) +- Upgrade @nuxt/axios [`#707`](https://github.com/Human-Connection/Human-Connection/pull/707) +- Bump apollo-server-testing from 2.5.0 to 2.5.1 in /backend [`#708`](https://github.com/Human-Connection/Human-Connection/pull/708) +- 🍰 Fix update incompatibility [`#701`](https://github.com/Human-Connection/Human-Connection/pull/701) +- Bump fuse.js from 3.4.4 to 3.4.5 in /webapp [`#704`](https://github.com/Human-Connection/Human-Connection/pull/704) +- 🍰 Avoid deletion of entire namespace by accident [`#702`](https://github.com/Human-Connection/Human-Connection/pull/702) +- Add logo as default avatar background image [`#706`](https://github.com/Human-Connection/Human-Connection/pull/706) +- Bump @nuxtjs/axios from 5.5.2 to 5.5.3 in /webapp [`#710`](https://github.com/Human-Connection/Human-Connection/pull/710) +- Bump apollo-server from 2.5.0 to 2.5.1 in /backend [`#709`](https://github.com/Human-Connection/Human-Connection/pull/709) +- Bump node from 10-alpine to 12.3.1-alpine in /backend [`#713`](https://github.com/Human-Connection/Human-Connection/pull/713) +- Bump prettier from 1.14.3 to 1.17.1 in /backend [`#664`](https://github.com/Human-Connection/Human-Connection/pull/664) +- Fix user avatar jumping on upload [`#700`](https://github.com/Human-Connection/Human-Connection/pull/700) +- Fix placeholder text not translating bug [`#695`](https://github.com/Human-Connection/Human-Connection/pull/695) +- 🍰 2019/kw22/alpha_data_import [`#697`](https://github.com/Human-Connection/Human-Connection/pull/697) +- Fix duplicate tags by using the name as the id [`#662`](https://github.com/Human-Connection/Human-Connection/pull/662) +- Run coverage only if requested [`#696`](https://github.com/Human-Connection/Human-Connection/pull/696) +- 🍰 2019/kw22/alpha_data_export [`#694`](https://github.com/Human-Connection/Human-Connection/pull/694) +- 689 fix broken image urls after upload [`#693`](https://github.com/Human-Connection/Human-Connection/pull/693) +- Fix edit field render bug(CommentForm) [`#690`](https://github.com/Human-Connection/Human-Connection/pull/690) +- Bump eslint-plugin-import from 2.17.2 to 2.17.3 in /backend [`#676`](https://github.com/Human-Connection/Human-Connection/pull/676) +- Bump neo4j-driver from 1.7.4 to 1.7.5 [`#675`](https://github.com/Human-Connection/Human-Connection/pull/675) +- Bump @vue/cli-shared-utils from 3.7.0 to 3.8.0 in /webapp [`#679`](https://github.com/Human-Connection/Human-Connection/pull/679) +- Use our own fork to workaround #600 for now [`#684`](https://github.com/Human-Connection/Human-Connection/pull/684) +- Bump express from 4.17.0 to 4.17.1 in /backend [`#674`](https://github.com/Human-Connection/Human-Connection/pull/674) +- More descriptive minikube setup instructions [`#585`](https://github.com/Human-Connection/Human-Connection/pull/585) +- User profile image uploads [`#605`](https://github.com/Human-Connection/Human-Connection/pull/605) +- Image upload backend implementation [`#636`](https://github.com/Human-Connection/Human-Connection/pull/636) +- Bump nodemon from 1.19.0 to 1.19.1 in /backend [`#678`](https://github.com/Human-Connection/Human-Connection/pull/678) +- Bump nodemon from 1.19.0 to 1.19.1 in /webapp [`#680`](https://github.com/Human-Connection/Human-Connection/pull/680) +- Bump @nuxtjs/axios from 5.5.1 to 5.5.2 in /webapp [`#681`](https://github.com/Human-Connection/Human-Connection/pull/681) +- Bump express from 4.17.0 to 4.17.1 in /webapp [`#682`](https://github.com/Human-Connection/Human-Connection/pull/682) +- Bump eslint-plugin-import from 2.17.2 to 2.17.3 in /webapp [`#683`](https://github.com/Human-Connection/Human-Connection/pull/683) +- Bump graphql from 14.3.0 to 14.3.1 in /webapp [`#670`](https://github.com/Human-Connection/Human-Connection/pull/670) +- Bump eslint-plugin-jest from 22.5.1 to 22.6.4 in /webapp [`#671`](https://github.com/Human-Connection/Human-Connection/pull/671) +- [Security] Bump tar from 2.2.1 to 2.2.2 in /webapp [`#673`](https://github.com/Human-Connection/Human-Connection/pull/673) +- Bump eslint-plugin-node from 9.0.1 to 9.1.0 in /webapp [`#669`](https://github.com/Human-Connection/Human-Connection/pull/669) +- Bump @nuxtjs/axios from 5.5.0 to 5.5.1 in /webapp [`#668`](https://github.com/Human-Connection/Human-Connection/pull/668) +- Bump cypress from 3.3.0 to 3.3.1 [`#667`](https://github.com/Human-Connection/Human-Connection/pull/667) +- Bump graphql from 14.3.0 to 14.3.1 in /backend [`#666`](https://github.com/Human-Connection/Human-Connection/pull/666) +- Bump eslint-plugin-node from 9.0.1 to 9.1.0 in /backend [`#665`](https://github.com/Human-Connection/Human-Connection/pull/665) +- Bump tiptap-extensions from 1.19.10 to 1.20.1 in /webapp [`#661`](https://github.com/Human-Connection/Human-Connection/pull/661) +- Bump @babel/preset-env from 7.4.4 to 7.4.5 in /webapp [`#655`](https://github.com/Human-Connection/Human-Connection/pull/655) +- Bump @babel/core from 7.4.4 to 7.4.5 in /webapp [`#653`](https://github.com/Human-Connection/Human-Connection/pull/653) +- Bump apollo-client from 2.5.1 to 2.6.0 in /webapp [`#654`](https://github.com/Human-Connection/Human-Connection/pull/654) +- Bump tiptap from 1.19.8 to 1.20.1 in /webapp [`#660`](https://github.com/Human-Connection/Human-Connection/pull/660) +- Bump @nuxtjs/axios from 5.4.1 to 5.5.0 in /webapp [`#659`](https://github.com/Human-Connection/Human-Connection/pull/659) +- Bump cypress-plugin-retries from 1.2.1 to 1.2.2 [`#658`](https://github.com/Human-Connection/Human-Connection/pull/658) +- Bump @babel/preset-env from 7.4.4 to 7.4.5 in /backend [`#649`](https://github.com/Human-Connection/Human-Connection/pull/649) +- Bump @babel/node from 7.2.2 to 7.4.5 in /backend [`#648`](https://github.com/Human-Connection/Human-Connection/pull/648) +- Bump @babel/core from 7.4.4 to 7.4.5 in /backend [`#646`](https://github.com/Human-Connection/Human-Connection/pull/646) +- Bump apollo-cache-inmemory from 1.5.1 to 1.6.0 in /backend [`#645`](https://github.com/Human-Connection/Human-Connection/pull/645) +- Bump eslint-plugin-jest from 22.5.1 to 22.6.4 in /backend [`#657`](https://github.com/Human-Connection/Human-Connection/pull/657) +- 🍰 2019/kw20/fix_lint_rules_and_vscode [`#625`](https://github.com/Human-Connection/Human-Connection/pull/625) +- Bump tiptap-extensions from 1.19.8 to 1.19.10 in /webapp [`#640`](https://github.com/Human-Connection/Human-Connection/pull/640) +- Bump neo4j-graphql-js from 2.6.0 to 2.6.1 in /backend [`#639`](https://github.com/Human-Connection/Human-Connection/pull/639) +- Change ds-avatar size to x-large [`#644`](https://github.com/Human-Connection/Human-Connection/pull/644) +- 🍰 2019/kw20/fix_post_card_design [`#622`](https://github.com/Human-Connection/Human-Connection/pull/622) +- 🍰 2019/kw21/github_templates_update [`#633`](https://github.com/Human-Connection/Human-Connection/pull/633) +- Bump tiptap from 1.19.3 to 1.19.8 in /webapp [`#637`](https://github.com/Human-Connection/Human-Connection/pull/637) +- Bump tiptap-extensions from 1.19.4 to 1.19.8 in /webapp [`#630`](https://github.com/Human-Connection/Human-Connection/pull/630) +- Bump cypress from 3.2.0 to 3.3.0 [`#629`](https://github.com/Human-Connection/Human-Connection/pull/629) +- Bump cypress-cucumber-preprocessor from 1.11.0 to 1.11.2 [`#628`](https://github.com/Human-Connection/Human-Connection/pull/628) +- Fix wrong screenshot in kubernetes docs [`#616`](https://github.com/Human-Connection/Human-Connection/pull/616) +- Refactor to use <img> instead of <hc-image> component for images [`#626`](https://github.com/Human-Connection/Human-Connection/pull/626) +- Bump node-fetch from 2.5.0 to 2.6.0 in /backend [`#617`](https://github.com/Human-Connection/Human-Connection/pull/617) +- Bump express from 4.16.4 to 4.17.0 in /backend [`#618`](https://github.com/Human-Connection/Human-Connection/pull/618) +- Bump express from 4.16.4 to 4.17.0 in /webapp [`#619`](https://github.com/Human-Connection/Human-Connection/pull/619) +- Bump eslint-config-prettier from 4.2.0 to 4.3.0 in /webapp [`#620`](https://github.com/Human-Connection/Human-Connection/pull/620) +- Bump @nuxtjs/apollo from 4.0.0-rc4.1 to 4.0.0-rc4.2 in /webapp [`#621`](https://github.com/Human-Connection/Human-Connection/pull/621) +- Bump tiptap-extensions from 1.19.2 to 1.19.4 in /webapp [`#610`](https://github.com/Human-Connection/Human-Connection/pull/610) +- Bump tiptap from 1.19.2 to 1.19.3 in /webapp [`#611`](https://github.com/Human-Connection/Human-Connection/pull/611) +- Bump neo4j-graphql-js from 2.4.2 to 2.6.0 in /backend [`#524`](https://github.com/Human-Connection/Human-Connection/pull/524) +- Update CONTRIBUTING.md with codereview notes [`#612`](https://github.com/Human-Connection/Human-Connection/pull/612) +- Bump nuxt from 2.6.3 to 2.7.1 in /webapp [`#607`](https://github.com/Human-Connection/Human-Connection/pull/607) +- Bump codecov from 3.4.0 to 3.5.0 [`#606`](https://github.com/Human-Connection/Human-Connection/pull/606) +- Docs 354 kubernetes cron job for backups [`#573`](https://github.com/Human-Connection/Human-Connection/pull/573) +- Docs update https configuration [`#608`](https://github.com/Human-Connection/Human-Connection/pull/608) +- Bump tiptap from 1.19.0 to 1.19.2 in /webapp [`#602`](https://github.com/Human-Connection/Human-Connection/pull/602) +- Bump cypress-plugin-retries from 1.2.0 to 1.2.1 [`#603`](https://github.com/Human-Connection/Human-Connection/pull/603) +- Bump tiptap-extensions from 1.19.1 to 1.19.2 in /webapp [`#601`](https://github.com/Human-Connection/Human-Connection/pull/601) +- Bump @nuxtjs/apollo from 4.0.0-rc4 to 4.0.0-rc4.1 in /webapp [`#604`](https://github.com/Human-Connection/Human-Connection/pull/604) +- Bump eslint-plugin-prettier from 3.0.1 to 3.1.0 in /webapp [`#598`](https://github.com/Human-Connection/Human-Connection/pull/598) +- Refactoring validation middleware [`#596`](https://github.com/Human-Connection/Human-Connection/pull/596) +- Revert logout.vue [`#594`](https://github.com/Human-Connection/Human-Connection/pull/594) +- Bump tiptap-extensions from 1.17.0 to 1.19.1 in /webapp [`#597`](https://github.com/Human-Connection/Human-Connection/pull/597) +- Bump tiptap from 1.17.0 to 1.19.0 in /webapp [`#592`](https://github.com/Human-Connection/Human-Connection/pull/592) +- Bump apollo-server-testing from 2.4.8 to 2.5.0 in /backend [`#567`](https://github.com/Human-Connection/Human-Connection/pull/567) +- Bump graphql from 14.2.1 to 14.3.0 in /backend [`#566`](https://github.com/Human-Connection/Human-Connection/pull/566) +- Delete posts [`#595`](https://github.com/Human-Connection/Human-Connection/pull/595) +- Write component tests for CommentForm.vue [`#556`](https://github.com/Human-Connection/Human-Connection/pull/556) +- Update ds-avatar size to use small [`#587`](https://github.com/Human-Connection/Human-Connection/pull/587) +- 2019/kw15/change password strength [`#551`](https://github.com/Human-Connection/Human-Connection/pull/551) +- 296 image component [`#499`](https://github.com/Human-Connection/Human-Connection/pull/499) +- Bump @human-connection/styleguide from 0.5.15 to 0.5.17 in /webapp [`#564`](https://github.com/Human-Connection/Human-Connection/pull/564) +- Bump codecov from 3.3.0 to 3.4.0 [`#580`](https://github.com/Human-Connection/Human-Connection/pull/580) +- Bump tippy.js from 4.3.0 to 4.3.1 in /webapp [`#579`](https://github.com/Human-Connection/Human-Connection/pull/579) +- Revert tiptap upgrade [`#575`](https://github.com/Human-Connection/Human-Connection/pull/575) +- 2019/kw15/user can change its username to emptystring [`#478`](https://github.com/Human-Connection/Human-Connection/pull/478) +- Delete SocialMedia [`#535`](https://github.com/Human-Connection/Human-Connection/pull/535) +- 2019/kw19/codecov [`#574`](https://github.com/Human-Connection/Human-Connection/pull/574) +- 2019/kw19/codecov [`#569`](https://github.com/Human-Connection/Human-Connection/pull/569) +- Bump graphql from 14.2.1 to 14.3.0 in /webapp [`#565`](https://github.com/Human-Connection/Human-Connection/pull/565) +- Bump apollo-server from 2.4.8 to 2.5.0 in /backend [`#568`](https://github.com/Human-Connection/Human-Connection/pull/568) +- Prevent `argument list too long` error [`#562`](https://github.com/Human-Connection/Human-Connection/pull/562) +- Performant import of production data [`#529`](https://github.com/Human-Connection/Human-Connection/pull/529) +- Bump jest from 24.7.1 to 24.8.0 in /backend [`#544`](https://github.com/Human-Connection/Human-Connection/pull/544) +- Bump eslint-plugin-node from 8.0.1 to 9.0.1 in /backend [`#543`](https://github.com/Human-Connection/Human-Connection/pull/543) +- Bump babel-jest from 24.7.1 to 24.8.0 in /backend [`#547`](https://github.com/Human-Connection/Human-Connection/pull/547) +- Bump jest from 24.7.1 to 24.8.0 in /webapp [`#550`](https://github.com/Human-Connection/Human-Connection/pull/550) +- Bump dotenv from 7.0.0 to 8.0.0 in /backend [`#540`](https://github.com/Human-Connection/Human-Connection/pull/540) +- Bump tiptap-extensions from 1.17.0 to 1.18.1 in /webapp [`#559`](https://github.com/Human-Connection/Human-Connection/pull/559) +- [WIP] Rerun moderation/report tests on failure [`#512`](https://github.com/Human-Connection/Human-Connection/pull/512) +- Add authorship to comments at creation [`#536`](https://github.com/Human-Connection/Human-Connection/pull/536) +- Bump node-fetch from 2.4.1 to 2.5.0 in /backend [`#530`](https://github.com/Human-Connection/Human-Connection/pull/530) +- Bump dotenv from 7.0.0 to 8.0.0 [`#538`](https://github.com/Human-Connection/Human-Connection/pull/538) +- Bump tiptap from 1.17.0 to 1.18.0 in /webapp [`#558`](https://github.com/Human-Connection/Human-Connection/pull/558) +- Bump node-sass from 4.11.0 to 4.12.0 in /webapp [`#515`](https://github.com/Human-Connection/Human-Connection/pull/515) +- Bump helmet from 3.16.0 to 3.18.0 in /backend [`#546`](https://github.com/Human-Connection/Human-Connection/pull/546) +- Bump neo4j-driver from 1.7.3 to 1.7.4 [`#548`](https://github.com/Human-Connection/Human-Connection/pull/548) +- Add test coverage [`#504`](https://github.com/Human-Connection/Human-Connection/pull/504) +- [WIP] 451 lokalise unlocalised texts [`#452`](https://github.com/Human-Connection/Human-Connection/pull/452) +- Bump neo4j from 3.5.4 to 3.5.5 in /neo4j [`#523`](https://github.com/Human-Connection/Human-Connection/pull/523) +- Bump nodemon from 1.18.11 to 1.19.0 in /webapp [`#531`](https://github.com/Human-Connection/Human-Connection/pull/531) +- Bump nodemon from 1.18.11 to 1.19.0 in /backend [`#532`](https://github.com/Human-Connection/Human-Connection/pull/532) +- Bump graphql-shield from 5.3.4 to 5.3.5 in /backend [`#539`](https://github.com/Human-Connection/Human-Connection/pull/539) +- Bump neo4j-driver from 1.7.3 to 1.7.4 in /backend [`#545`](https://github.com/Human-Connection/Human-Connection/pull/545) +- Bump babel-jest from 24.7.1 to 24.8.0 in /webapp [`#549`](https://github.com/Human-Connection/Human-Connection/pull/549) +- Unexpose irrelevant port 8080 [`#526`](https://github.com/Human-Connection/Human-Connection/pull/526) +- Bump sanitize-html from 1.20.0 to 1.20.1 in /backend [`#507`](https://github.com/Human-Connection/Human-Connection/pull/507) +- Bump tiptap-extensions from 1.16.2 to 1.17.0 in /webapp [`#527`](https://github.com/Human-Connection/Human-Connection/pull/527) +- Bump @babel/preset-env from 7.4.3 to 7.4.4 in /webapp [`#513`](https://github.com/Human-Connection/Human-Connection/pull/513) +- Bump tiptap from 1.16.2 to 1.17.0 in /webapp [`#528`](https://github.com/Human-Connection/Human-Connection/pull/528) +- Bump nuxt from 2.6.2 to 2.6.3 in /webapp [`#505`](https://github.com/Human-Connection/Human-Connection/pull/505) +- Bump @vue/cli-shared-utils from 3.6.0 to 3.7.0 in /webapp [`#514`](https://github.com/Human-Connection/Human-Connection/pull/514) +- Bump node-fetch from 2.3.0 to 2.4.1 in /backend [`#521`](https://github.com/Human-Connection/Human-Connection/pull/521) +- Bump @babel/cli from 7.4.3 to 7.4.4 in /backend [`#518`](https://github.com/Human-Connection/Human-Connection/pull/518) +- Bump eslint-config-prettier from 4.1.0 to 4.2.0 in /webapp [`#506`](https://github.com/Human-Connection/Human-Connection/pull/506) +- Bump @babel/core from 7.4.3 to 7.4.4 in /backend [`#516`](https://github.com/Human-Connection/Human-Connection/pull/516) +- Bump @babel/register from 7.4.0 to 7.4.4 in /backend [`#522`](https://github.com/Human-Connection/Human-Connection/pull/522) +- Bump eslint-plugin-jest from 22.5.0 to 22.5.1 in /backend [`#508`](https://github.com/Human-Connection/Human-Connection/pull/508) +- Add Comment Form [`#475`](https://github.com/Human-Connection/Human-Connection/pull/475) +- Fix? flaky cypress by waiting for <no-ssr> content [`#484`](https://github.com/Human-Connection/Human-Connection/pull/484) +- Bump @babel/core from 7.4.3 to 7.4.4 in /webapp [`#517`](https://github.com/Human-Connection/Human-Connection/pull/517) +- Bump v-tooltip from 2.0.1 to 2.0.2 in /webapp [`#519`](https://github.com/Human-Connection/Human-Connection/pull/519) +- Bump @babel/preset-env from 7.4.3 to 7.4.4 in /backend [`#520`](https://github.com/Human-Connection/Human-Connection/pull/520) +- Refactor maintenance-worker docker image [`#485`](https://github.com/Human-Connection/Human-Connection/pull/485) +- Bump eslint-plugin-jest from 22.4.1 to 22.5.0 in /backend [`#500`](https://github.com/Human-Connection/Human-Connection/pull/500) +- Bump tippy.js from 4.2.1 to 4.3.0 in /webapp [`#501`](https://github.com/Human-Connection/Human-Connection/pull/501) +- Bump tiptap-extensions from 1.15.0 to 1.16.2 in /webapp [`#496`](https://github.com/Human-Connection/Human-Connection/pull/496) +- [Security] Bump jquery from 3.3.1 to 3.4.0 in /backend [`#498`](https://github.com/Human-Connection/Human-Connection/pull/498) +- Add documentation for neo4j backups in kubernetes [`#492`](https://github.com/Human-Connection/Human-Connection/pull/492) +- Bump tiptap from 1.15.0 to 1.16.2 in /webapp [`#497`](https://github.com/Human-Connection/Human-Connection/pull/497) +- Bump graphql-shield from 5.3.3 to 5.3.4 in /backend [`#486`](https://github.com/Human-Connection/Human-Connection/pull/486) +- [WIP] Frontend implementation for notifications [`#439`](https://github.com/Human-Connection/Human-Connection/pull/439) +- [WIP] Generate a link for @-Mentionings [`#462`](https://github.com/Human-Connection/Human-Connection/pull/462) +- Bump eslint-plugin-import from 2.17.1 to 2.17.2 in /backend [`#476`](https://github.com/Human-Connection/Human-Connection/pull/476) +- Docs improve installation instructions [`#464`](https://github.com/Human-Connection/Human-Connection/pull/464) +- Try to fix flaky "ReportContent.feature" [`#477`](https://github.com/Human-Connection/Human-Connection/pull/477) +- refactor follow, unfollow, shout, unshout to custom mutations, but don't add date-time to relation yet [`#449`](https://github.com/Human-Connection/Human-Connection/pull/449) +- Bump nuxt from 2.6.1 to 2.6.2 in /webapp [`#472`](https://github.com/Human-Connection/Human-Connection/pull/472) +- Bump tiptap-extensions from 1.14.0 to 1.15.0 in /webapp [`#473`](https://github.com/Human-Connection/Human-Connection/pull/473) +- Disable cypress test recordings [`#471`](https://github.com/Human-Connection/Human-Connection/pull/471) +- Bump v-tooltip from 2.0.0 to 2.0.1 in /webapp [`#467`](https://github.com/Human-Connection/Human-Connection/pull/467) +- Bump @vue/cli-shared-utils from 3.5.1 to 3.6.0 in /webapp [`#468`](https://github.com/Human-Connection/Human-Connection/pull/468) +- Bump graphql-shield from 5.3.2 to 5.3.3 in /backend [`#465`](https://github.com/Human-Connection/Human-Connection/pull/465) +- Bump eslint-plugin-import from 2.16.0 to 2.17.1 in /backend [`#466`](https://github.com/Human-Connection/Human-Connection/pull/466) +- List socialMedia links [`#237`](https://github.com/Human-Connection/Human-Connection/pull/237) +- Add instruction to configure local cypress config [`#460`](https://github.com/Human-Connection/Human-Connection/pull/460) +- Don't expose private RSA key [`#406`](https://github.com/Human-Connection/Human-Connection/pull/406) +- Fix emojis, remove duplicate link [`#459`](https://github.com/Human-Connection/Human-Connection/pull/459) +- Fix links to documentation [`#455`](https://github.com/Human-Connection/Human-Connection/pull/455) +- 342 merge documentation [`#387`](https://github.com/Human-Connection/Human-Connection/pull/387) +- 2019/kw15/design_differences_post [`#440`](https://github.com/Human-Connection/Human-Connection/pull/440) +- Bump v-tooltip from 2.0.0-rc.33 to 2.0.0 in /webapp [`#450`](https://github.com/Human-Connection/Human-Connection/pull/450) +- Bump neo4j from 3.5.3 to 3.5.4 in /backend/neo4j [`#448`](https://github.com/Human-Connection/Human-Connection/pull/448) +- Bump nuxt from 2.4.5 to 2.6.1 in /webapp [`#416`](https://github.com/Human-Connection/Human-Connection/pull/416) +- Bump @babel/cli from 7.2.3 to 7.4.3 in /backend [`#366`](https://github.com/Human-Connection/Human-Connection/pull/366) +- Bump neo4j from 3.5.0 to 3.5.3 in /backend/neo4j [`#196`](https://github.com/Human-Connection/Human-Connection/pull/196) +- 2019/kw15/hide_all_unfuctional_content [`#434`](https://github.com/Human-Connection/Human-Connection/pull/434) +- Mark a notification in the backend as read [`#445`](https://github.com/Human-Connection/Human-Connection/pull/445) +- Bump nodemon from 1.18.10 to 1.18.11 in /webapp [`#442`](https://github.com/Human-Connection/Human-Connection/pull/442) +- Disabling activityPub middleware again [`#444`](https://github.com/Human-Connection/Human-Connection/pull/444) +- Bump graphql-shield from 5.3.1 to 5.3.2 in /backend [`#446`](https://github.com/Human-Connection/Human-Connection/pull/446) +- 2019/kw15/new_post_button_position [`#438`](https://github.com/Human-Connection/Human-Connection/pull/438) +- 2019/kw15/Access_Settings_from_Profile_as_Owner [`#443`](https://github.com/Human-Connection/Human-Connection/pull/443) +- 2019/kw15/disabled_post_fix_disabled_border_z-index [`#437`](https://github.com/Human-Connection/Human-Connection/pull/437) +- Bump nodemon from 1.18.10 to 1.18.11 in /backend [`#441`](https://github.com/Human-Connection/Human-Connection/pull/441) +- 2019/kw15/post_make_menu_sticky [`#435`](https://github.com/Human-Connection/Human-Connection/pull/435) +- Bump cheerio from 1.0.0-rc.2 to 1.0.0-rc.3 in /backend [`#431`](https://github.com/Human-Connection/Human-Connection/pull/431) +- Fixes #361 [`#362`](https://github.com/Human-Connection/Human-Connection/pull/362) +- Use github emojis if possible [`#396`](https://github.com/Human-Connection/Human-Connection/pull/396) +- Bump @babel/core from 7.4.0 to 7.4.3 in /webapp [`#368`](https://github.com/Human-Connection/Human-Connection/pull/368) +- Bump jest from 24.7.0 to 24.7.1 in /webapp [`#390`](https://github.com/Human-Connection/Human-Connection/pull/390) +- 350 query current users notifications [`#415`](https://github.com/Human-Connection/Human-Connection/pull/415) +- Bump babel-jest from 24.5.0 to 24.7.1 in /backend [`#388`](https://github.com/Human-Connection/Human-Connection/pull/388) +- Bump @babel/core from 7.4.0 to 7.4.3 in /backend [`#363`](https://github.com/Human-Connection/Human-Connection/pull/363) +- Bump babel-jest from 24.6.0 to 24.7.1 in /webapp [`#391`](https://github.com/Human-Connection/Human-Connection/pull/391) +- Fix flaky cucumbers [`#383`](https://github.com/Human-Connection/Human-Connection/pull/383) +- Bump jest from 24.7.0 to 24.7.1 in /backend [`#389`](https://github.com/Human-Connection/Human-Connection/pull/389) +- Setup kubectl so it never expires [`#380`](https://github.com/Human-Connection/Human-Connection/pull/380) +- Bump @babel/preset-env from 7.4.2 to 7.4.3 in /backend [`#367`](https://github.com/Human-Connection/Human-Connection/pull/367) +- Bump jest from 24.6.0 to 24.7.0 in /backend [`#364`](https://github.com/Human-Connection/Human-Connection/pull/364) +- Bump @babel/preset-env from 7.4.2 to 7.4.3 in /webapp [`#369`](https://github.com/Human-Connection/Human-Connection/pull/369) +- Bump jest from 24.6.0 to 24.7.0 in /webapp [`#371`](https://github.com/Human-Connection/Human-Connection/pull/371) +- 2019/kw14/GitHub issue pullrequest templates [`#360`](https://github.com/Human-Connection/Human-Connection/pull/360) +- Bump jest from 24.5.0 to 24.6.0 in /webapp [`#355`](https://github.com/Human-Connection/Human-Connection/pull/355) +- Bump babel-jest from 24.5.0 to 24.6.0 in /webapp [`#356`](https://github.com/Human-Connection/Human-Connection/pull/356) +- Bump jest from 24.5.0 to 24.6.0 in /backend [`#358`](https://github.com/Human-Connection/Human-Connection/pull/358) +- Bump eslint-plugin-promise from 4.0.1 to 4.1.1 in /backend [`#359`](https://github.com/Human-Connection/Human-Connection/pull/359) +- 2019/kw14/fixed_windows_build_docu_fixed_server_start_text [`#339`](https://github.com/Human-Connection/Human-Connection/pull/339) +- Bump slug from 1.0.0 to 1.1.0 in /backend [`#336`](https://github.com/Human-Connection/Human-Connection/pull/336) +- Bump graphql from 14.2.0 to 14.2.1 in /webapp [`#333`](https://github.com/Human-Connection/Human-Connection/pull/333) +- Bump graphql from 14.2.0 to 14.2.1 in /backend [`#334`](https://github.com/Human-Connection/Human-Connection/pull/334) +- Bump eslint from 5.15.3 to 5.16.0 in /webapp [`#332`](https://github.com/Human-Connection/Human-Connection/pull/332) +- Bump eslint from 5.15.3 to 5.16.0 in /backend [`#335`](https://github.com/Human-Connection/Human-Connection/pull/335) +- Added PRIVATE_KEY_PASSPHRASE to needed env vars [`#244`](https://github.com/Human-Connection/Human-Connection/pull/244) +- Bump graphql from 14.1.1 to 14.2.0 in /webapp [`#301`](https://github.com/Human-Connection/Human-Connection/pull/301) +- Bump graphql from 14.1.1 to 14.2.0 in /backend [`#300`](https://github.com/Human-Connection/Human-Connection/pull/300) +- Reduce Travis CI build log [`#320`](https://github.com/Human-Connection/Human-Connection/pull/320) +- 240 persistent links [`#248`](https://github.com/Human-Connection/Human-Connection/pull/248) +- Another fix of activity pub implementation [`#249`](https://github.com/Human-Connection/Human-Connection/pull/249) +- Network Concept added [`#30`](https://github.com/Human-Connection/Human-Connection/pull/30) +- Order all resources by createdAt descending (as default) [`#239`](https://github.com/Human-Connection/Human-Connection/pull/239) +- Apparently this change got overlooked [`#246`](https://github.com/Human-Connection/Human-Connection/pull/246) +- Trigger rollout with a change to spec.template [`#245`](https://github.com/Human-Connection/Human-Connection/pull/245) +- Should fix #234 + refactor domain to hostname [`#243`](https://github.com/Human-Connection/Human-Connection/pull/243) +- [User profile] Change password [`#233`](https://github.com/Human-Connection/Human-Connection/pull/233) +- Bump @babel/core from 7.3.4 to 7.4.0 in /backend [`#194`](https://github.com/Human-Connection/Human-Connection/pull/194) +- Bump helmet from 3.15.1 to 3.16.0 in /backend [`#198`](https://github.com/Human-Connection/Human-Connection/pull/198) +- Bump @vue/cli-shared-utils from 3.4.1 to 3.5.1 in /webapp [`#190`](https://github.com/Human-Connection/Human-Connection/pull/190) +- Bump @babel/core from 7.3.4 to 7.4.0 in /webapp [`#195`](https://github.com/Human-Connection/Human-Connection/pull/195) +- Bump vue-svg-loader from 0.11.0 to 0.12.0 in /webapp [`#200`](https://github.com/Human-Connection/Human-Connection/pull/200) +- Bump supertest from 4.0.0 to 4.0.2 in /backend [`#192`](https://github.com/Human-Connection/Human-Connection/pull/192) +- Bump graphql-shield from 5.3.0 to 5.3.1 in /backend [`#197`](https://github.com/Human-Connection/Human-Connection/pull/197) +- Bump @babel/preset-env from 7.3.4 to 7.4.2 in /webapp [`#228`](https://github.com/Human-Connection/Human-Connection/pull/228) +- 204 tests for rewarding badges [`#223`](https://github.com/Human-Connection/Human-Connection/pull/223) +- Bump @babel/preset-env from 7.3.4 to 7.4.2 in /backend [`#227`](https://github.com/Human-Connection/Human-Connection/pull/227) +- Bump eslint from 5.15.1 to 5.15.3 in /backend [`#191`](https://github.com/Human-Connection/Human-Connection/pull/191) +- Bump eslint-config-prettier from 3.6.0 to 4.1.0 in /webapp [`#229`](https://github.com/Human-Connection/Human-Connection/pull/229) +- Bump apollo-link-http from 1.5.13 to 1.5.14 in /backend [`#224`](https://github.com/Human-Connection/Human-Connection/pull/224) +- Bump eslint-plugin-jest from 22.3.2 to 22.4.1 in /backend [`#225`](https://github.com/Human-Connection/Human-Connection/pull/225) +- Bump @babel/register from 7.0.0 to 7.4.0 in /backend [`#226`](https://github.com/Human-Connection/Human-Connection/pull/226) +- Bump eslint from 5.15.1 to 5.15.3 in /webapp [`#231`](https://github.com/Human-Connection/Human-Connection/pull/231) +- Install kubectl and deploy automatically [`#202`](https://github.com/Human-Connection/Human-Connection/pull/202) +- [WIP] Rollout deployment [`#186`](https://github.com/Human-Connection/Human-Connection/pull/186) +- Monorepo [`#184`](https://github.com/Human-Connection/Human-Connection/pull/184) +- Bundle all activityPub in middleware [`#254`](https://github.com/Human-Connection/Human-Connection/pull/254) +- [WIP] Activitypub Service integrated into Backend [`#212`](https://github.com/Human-Connection/Human-Connection/pull/212) +- Bump jsonwebtoken from 8.5.0 to 8.5.1 [`#253`](https://github.com/Human-Connection/Human-Connection/pull/253) +- Bump jsonwebtoken from 8.5.0 to 8.5.1 [`#266`](https://github.com/Human-Connection/Human-Connection/pull/266) +- Bump Nitro-Backend from `158ac36` to `fd8cc3f` [`#177`](https://github.com/Human-Connection/Human-Connection/pull/177) +- Bump Nitro-Styleguide from `baf3134` to `3f7a00b` [`#178`](https://github.com/Human-Connection/Human-Connection/pull/178) +- Bump Nitro-Web from `0fae7a2` to `0c2a228` [`#180`](https://github.com/Human-Connection/Human-Connection/pull/180) +- 37 full text search top bar [`#264`](https://github.com/Human-Connection/Human-Connection/pull/264) +- Deploy all the things [`#12`](https://github.com/Human-Connection/Human-Connection/pull/12) +- Disable posts [`#208`](https://github.com/Human-Connection/Human-Connection/pull/208) +- Obfuscate user name and use a neutral replacement [`#246`](https://github.com/Human-Connection/Human-Connection/pull/246) +- [WIP] Disabled users are unauthenticated [`#242`](https://github.com/Human-Connection/Human-Connection/pull/242) +- Bump Nitro-Backend from `fd02679` to `158ac36` [`#176`](https://github.com/Human-Connection/Human-Connection/pull/176) +- Bump dotenv from 6.2.0 to 7.0.0 [`#241`](https://github.com/Human-Connection/Human-Connection/pull/241) +- Bump Nitro-Backend from `0dce83b` to `fd02679` [`#175`](https://github.com/Human-Connection/Human-Connection/pull/175) +- Bump Nitro-Web from `a05b438` to `0fae7a2` [`#174`](https://github.com/Human-Connection/Human-Connection/pull/174) +- Bump jest from 24.1.0 to 24.5.0 [`#254`](https://github.com/Human-Connection/Human-Connection/pull/254) +- Bump babel-jest from 24.3.1 to 24.5.0 [`#255`](https://github.com/Human-Connection/Human-Connection/pull/255) +- Obfuscate disabled comments and posts by default [`#230`](https://github.com/Human-Connection/Human-Connection/pull/230) +- Bump jest from 24.3.1 to 24.4.0 [`#227`](https://github.com/Human-Connection/Human-Connection/pull/227) +- Bump supertest from 3.4.2 to 4.0.0 [`#225`](https://github.com/Human-Connection/Human-Connection/pull/225) +- Remove cypress from package.json [`#258`](https://github.com/Human-Connection/Human-Connection/pull/258) +- Bump eslint-plugin-jest from 22.3.0 to 22.3.2 [`#232`](https://github.com/Human-Connection/Human-Connection/pull/232) +- Bump babel-jest from 24.3.1 to 24.5.0 [`#231`](https://github.com/Human-Connection/Human-Connection/pull/231) +- Change password in the backend [`#223`](https://github.com/Human-Connection/Human-Connection/pull/223) +- Add unauthenticated test to follow and shout [`#235`](https://github.com/Human-Connection/Human-Connection/pull/235) +- Bump Nitro-Styleguide from `e77754c` to `baf3134` [`#173`](https://github.com/Human-Connection/Human-Connection/pull/173) +- Bump Nitro-Web from `4aa640a` to `a05b438` [`#172`](https://github.com/Human-Connection/Human-Connection/pull/172) +- Bump Nitro-Backend from `28a37c2` to `0dce83b` [`#171`](https://github.com/Human-Connection/Human-Connection/pull/171) +- Update local-backend.md [`#27`](https://github.com/Human-Connection/Human-Connection/pull/27) +- Fix styleguide issues [`#253`](https://github.com/Human-Connection/Human-Connection/pull/253) +- [WIP] 37 full text search top bar [`#152`](https://github.com/Human-Connection/Human-Connection/pull/152) +- Update ReportModal to work with back end [`#233`](https://github.com/Human-Connection/Human-Connection/pull/233) +- Bump date-fns from 2.0.0-alpha.26 to 2.0.0-alpha.27 [`#112`](https://github.com/Human-Connection/Human-Connection/pull/112) +- Bump Nitro-Styleguide from `562fddb` to `e77754c` [`#170`](https://github.com/Human-Connection/Human-Connection/pull/170) +- Lokalise: Translations update [`#250`](https://github.com/Human-Connection/Human-Connection/pull/250) +- Update translations [`#240`](https://github.com/Human-Connection/Human-Connection/pull/240) +- Bump Nitro-Backend from `2d24fc9` to `28a37c2` [`#168`](https://github.com/Human-Connection/Human-Connection/pull/168) +- Bump Nitro-Styleguide from `ce69fe5` to `ce20d5b` [`#166`](https://github.com/Human-Connection/Human-Connection/pull/166) +- [WIP] Killing the past [`#164`](https://github.com/Human-Connection/Human-Connection/pull/164) +- Get rid of resource type [`#213`](https://github.com/Human-Connection/Human-Connection/pull/213) +- x [`#165`](https://github.com/Human-Connection/Human-Connection/pull/165) +- [WIP] Remove portal [`#237`](https://github.com/Human-Connection/Human-Connection/pull/237) +- Bump vue-jest from 3.0.3 to 3.0.4 [`#220`](https://github.com/Human-Connection/Human-Connection/pull/220) +- Fix follow and shout status [`#201`](https://github.com/Human-Connection/Human-Connection/pull/201) +- Fix follow and shout status [`#213`](https://github.com/Human-Connection/Human-Connection/pull/213) +- Bump jest from 24.1.0 to 24.3.1 [`#215`](https://github.com/Human-Connection/Human-Connection/pull/215) +- Bump babel-jest from 24.1.0 to 24.3.1 [`#216`](https://github.com/Human-Connection/Human-Connection/pull/216) +- Update .gitmodules [`#163`](https://github.com/Human-Connection/Human-Connection/pull/163) +- Show all repos build stats [`#147`](https://github.com/Human-Connection/Human-Connection/pull/147) +- Bump Nitro-Backend from `2813de4` to `2d24fc9` [`#162`](https://github.com/Human-Connection/Human-Connection/pull/162) +- Disable posts [`#207`](https://github.com/Human-Connection/Human-Connection/pull/207) +- Bump apollo-link-http from 1.5.11 to 1.5.12 [`#208`](https://github.com/Human-Connection/Human-Connection/pull/208) +- Bump Nitro-Web from `d857252` to `d848929` [`#159`](https://github.com/Human-Connection/Human-Connection/pull/159) +- Bump Nitro-Backend from `c5c62d2` to `2813de4` [`#160`](https://github.com/Human-Connection/Human-Connection/pull/160) +- Bump eslint from 5.15.0 to 5.15.1 [`#206`](https://github.com/Human-Connection/Human-Connection/pull/206) +- Fix current build [`#203`](https://github.com/Human-Connection/Human-Connection/pull/203) +- Show disabled or deleted posts for moderators [`#192`](https://github.com/Human-Connection/Human-Connection/pull/192) +- Bump eslint from 5.14.1 to 5.15.0 [`#202`](https://github.com/Human-Connection/Human-Connection/pull/202) +- [WIP] Write API test badge creation [`#195`](https://github.com/Human-Connection/Human-Connection/pull/195) +- Add to .gitignore `.DS_Store` [`#196`](https://github.com/Human-Connection/Human-Connection/pull/196) +- Add styleguide dev mode [`#197`](https://github.com/Human-Connection/Human-Connection/pull/197) +- 194 stop decoding jwt on frontend [`#199`](https://github.com/Human-Connection/Human-Connection/pull/199) +- Implement currentUser query [`#187`](https://github.com/Human-Connection/Human-Connection/pull/187) +- Extract Styleguide [`#182`](https://github.com/Human-Connection/Human-Connection/pull/182) +- Bump eslint from 5.13.0 to 5.14.1 [`#165`](https://github.com/Human-Connection/Human-Connection/pull/165) +- Editor [`#153`](https://github.com/Human-Connection/Human-Connection/pull/153) +- [WIP] Editor [`#136`](https://github.com/Human-Connection/Human-Connection/pull/136) +- Improved documentation for remote deployment [`#11`](https://github.com/Human-Connection/Human-Connection/pull/11) +- Fix wrong secret references [`#9`](https://github.com/Human-Connection/Human-Connection/pull/9) +- Fix staging urls [`#10`](https://github.com/Human-Connection/Human-Connection/pull/10) +- Fix Digital Ocean Documentation [`#8`](https://github.com/Human-Connection/Human-Connection/pull/8) +- [WIP] Use factories in cucumber features [`#190`](https://github.com/Human-Connection/Human-Connection/pull/190) +- Added IntersectionObserver polyfill and fixed linting on login [`#192`](https://github.com/Human-Connection/Human-Connection/pull/192) +- [WIP] Start unit testing report feature [`#171`](https://github.com/Human-Connection/Human-Connection/pull/171) +- Refactor seeds and tests [`#164`](https://github.com/Human-Connection/Human-Connection/pull/164) +- Added instruction for Docker install for Backend and little corrections [`#25`](https://github.com/Human-Connection/Human-Connection/pull/25) +- Bump eslint from 5.14.0 to 5.14.1 [`#187`](https://github.com/Human-Connection/Human-Connection/pull/187) +- Bump Nitro-Backend from `13c6743` to `c5c62d2` [`#138`](https://github.com/Human-Connection/Human-Connection/pull/138) +- Bump Nitro-Web from `9ba2200` to `d857252` [`#140`](https://github.com/Human-Connection/Human-Connection/pull/140) +- Bump API from `02dcc22` to `8f0edaf` [`#137`](https://github.com/Human-Connection/Human-Connection/pull/137) +- Bump WebApp from `e153eb6` to `d4917da` [`#134`](https://github.com/Human-Connection/Human-Connection/pull/134) +- Bump @babel/core from 7.2.2 to 7.3.3 [`#184`](https://github.com/Human-Connection/Human-Connection/pull/184) +- Bump date-fns from 2.0.0-alpha.26 to 2.0.0-alpha.27 [`#132`](https://github.com/Human-Connection/Human-Connection/pull/132) +- Bump eslint-plugin-vue from 5.1.0 to 5.2.1 [`#180`](https://github.com/Human-Connection/Human-Connection/pull/180) +- Bump eslint from 5.13.0 to 5.14.0 [`#185`](https://github.com/Human-Connection/Human-Connection/pull/185) +- Update .travis.yml [`#183`](https://github.com/Human-Connection/Human-Connection/pull/183) +- Upgrade to Nuxt 2.4 [`#175`](https://github.com/Human-Connection/Human-Connection/pull/175) +- Upgrade dependencies [`#173`](https://github.com/Human-Connection/Human-Connection/pull/173) +- Certmanager [`#7`](https://github.com/Human-Connection/Human-Connection/pull/7) +- Upgrade dependencies [`#149`](https://github.com/Human-Connection/Human-Connection/pull/149) +- Bump Nitro-Backend from `8b8caae` to `13c6743` [`#131`](https://github.com/Human-Connection/Human-Connection/pull/131) +- Bump Nitro-Web from `70aca85` to `9ba2200` [`#130`](https://github.com/Human-Connection/Human-Connection/pull/130) +- Bump WebApp from `fa26d7c` to `e153eb6` [`#118`](https://github.com/Human-Connection/Human-Connection/pull/118) +- Add db migration worker deployment [`#4`](https://github.com/Human-Connection/Human-Connection/pull/4) +- Set titles for some tabs correctly and fixed wrong quotation marks (macOS encod… [`#23`](https://github.com/Human-Connection/Human-Connection/pull/23) +- Refactoring db migration worker [`#145`](https://github.com/Human-Connection/Human-Connection/pull/145) +- Create CODE_OF_CONDUCT.md [`#139`](https://github.com/Human-Connection/Human-Connection/pull/139) +- Add instructions to get Neo4j Desktop to work [`#17`](https://github.com/Human-Connection/Human-Connection/pull/17) +- Fix certain configuration for Digital Ocean [`#5`](https://github.com/Human-Connection/Human-Connection/pull/5) +- fix mobile friendliness for post [`#145`](https://github.com/Human-Connection/Human-Connection/pull/145) +- Don't copy private SSH key into docker image [`#138`](https://github.com/Human-Connection/Human-Connection/pull/138) +- Follow up on data import [`#127`](https://github.com/Human-Connection/Human-Connection/pull/127) +- Create CODE_OF_CONDUCT.md [`#149`](https://github.com/Human-Connection/Human-Connection/pull/149) +- Update APOC plugin version [`#21`](https://github.com/Human-Connection/Human-Connection/pull/21) +- Move troubleshoots to end; add install links for docker and minikube [`#131`](https://github.com/Human-Connection/Human-Connection/pull/131) +- Add instructions to expose/access the services [`#3`](https://github.com/Human-Connection/Human-Connection/pull/3) +- Fix jwt quotes [`#150`](https://github.com/Human-Connection/Human-Connection/pull/150) +- Add instructions to docs, fix kubectl commands [`#2`](https://github.com/Human-Connection/Human-Connection/pull/2) +- Update to the latest changes on the backend [`#18`](https://github.com/Human-Connection/Human-Connection/pull/18) +- Report Content [`#107`](https://github.com/Human-Connection/Human-Connection/pull/107) +- Report content [`#99`](https://github.com/Human-Connection/Human-Connection/pull/99) +- Provide documentation for `db-migration-worker` [`#16`](https://github.com/Human-Connection/Human-Connection/pull/16) +- Import remote mongodb in neo4j [`#115`](https://github.com/Human-Connection/Human-Connection/pull/115) +- Add screenshot file for forking nitro-backend [`#6`](https://github.com/Human-Connection/Human-Connection/pull/6) +- 106 authorization [`#109`](https://github.com/Human-Connection/Human-Connection/pull/109) +- Comming Soon and Empty States [`#141`](https://github.com/Human-Connection/Human-Connection/pull/141) +- Change path of the file "edit-this-documentation.md" [`#14`](https://github.com/Human-Connection/Human-Connection/pull/14) +- Upgrade styleguide [`#134`](https://github.com/Human-Connection/Human-Connection/pull/134) +- New file describes how to edit this documentation [`#3`](https://github.com/Human-Connection/Human-Connection/pull/3) +- Change Summary [`#2`](https://github.com/Human-Connection/Human-Connection/pull/2) +- Add instructions for forking (or cloning) repo [`#1`](https://github.com/Human-Connection/Human-Connection/pull/1) +- Add a new section for how to change the documentation [`#10`](https://github.com/Human-Connection/Human-Connection/pull/10) +- Add a new section for how to change the documentation [`#9`](https://github.com/Human-Connection/Human-Connection/pull/9) +- Locations & About Me [`#16`](https://github.com/Human-Connection/Human-Connection/pull/16) +- Locations & About Me [`#9`](https://github.com/Human-Connection/Human-Connection/pull/9) +- Language menu [`#130`](https://github.com/Human-Connection/Human-Connection/pull/130) +- fix #41 jwt strategy [`#104`](https://github.com/Human-Connection/Human-Connection/pull/104) +- Multistage build docker [`#115`](https://github.com/Human-Connection/Human-Connection/pull/115) +- Improve dropdown [`#129`](https://github.com/Human-Connection/Human-Connection/pull/129) +- Don't run tests in /dist folder [`#108`](https://github.com/Human-Connection/Human-Connection/pull/108) +- i18n pluralization [`#111`](https://github.com/Human-Connection/Human-Connection/pull/111) +- Add instructions for forking (or cloning) repo [`#7`](https://github.com/Human-Connection/Human-Connection/pull/7) +- Lokalise: Translations update [`#123`](https://github.com/Human-Connection/Human-Connection/pull/123) +- Update README.md [`#100`](https://github.com/Human-Connection/Human-Connection/pull/100) +- FIX #49 capture error in readable format with try and catch [`#93`](https://github.com/Human-Connection/Human-Connection/pull/93) +- Login: Friendly error message if email is incorrect [`#88`](https://github.com/Human-Connection/Human-Connection/pull/88) +- Bump @babel/node from 7.2.0 to 7.2.2 [`#79`](https://github.com/Human-Connection/Human-Connection/pull/79) +- Bump @vue/test-utils from 1.0.0-beta.27 to 1.0.0-beta.28 [`#109`](https://github.com/Human-Connection/Human-Connection/pull/109) +- Bump eslint-plugin-prettier from 3.0.0 to 3.0.1 [`#110`](https://github.com/Human-Connection/Human-Connection/pull/110) +- added login credentials [`#2`](https://github.com/Human-Connection/Human-Connection/pull/2) +- Change db scripts to run nodemon instead of node [`#92`](https://github.com/Human-Connection/Human-Connection/pull/92) +- Bump eslint from 5.10.0 to 5.11.0 [`#103`](https://github.com/Human-Connection/Human-Connection/pull/103) +- Refactor login method and setup unit tests [`#100`](https://github.com/Human-Connection/Human-Connection/pull/100) +- [ImgBot] Optimize images [`#104`](https://github.com/Human-Connection/Human-Connection/pull/104) +- Integrate i18n [`#99`](https://github.com/Human-Connection/Human-Connection/pull/99) +- Update README.md [`#89`](https://github.com/Human-Connection/Human-Connection/pull/89) +- Fix 'audience must be a string or array' [`#84`](https://github.com/Human-Connection/Human-Connection/pull/84) +- Bump sanitize-html from 1.19.3 to 1.20.0 [`#82`](https://github.com/Human-Connection/Human-Connection/pull/82) +- added icons to usermenu [`#96`](https://github.com/Human-Connection/Human-Connection/pull/96) +- Bump apollo-link-http from 1.5.8 to 1.5.9 [`#81`](https://github.com/Human-Connection/Human-Connection/pull/81) +- Bump nodemon from 1.18.8 to 1.18.9 [`#78`](https://github.com/Human-Connection/Human-Connection/pull/78) +- Bump nodemon from 1.18.8 to 1.18.9 [`#94`](https://github.com/Human-Connection/Human-Connection/pull/94) +- Typo fixes [`#1`](https://github.com/Human-Connection/Human-Connection/pull/1) +- Bump apollo-server from 2.2.6 to 2.3.1 [`#75`](https://github.com/Human-Connection/Human-Connection/pull/75) +- Bump apollo-link-http from 1.5.7 to 1.5.8 [`#74`](https://github.com/Human-Connection/Human-Connection/pull/74) +- Add travis build status to README [`#91`](https://github.com/Human-Connection/Human-Connection/pull/91) +- Use build stage `builder` for backend [`#92`](https://github.com/Human-Connection/Human-Connection/pull/92) +- Add docker push script deployment [`#90`](https://github.com/Human-Connection/Human-Connection/pull/90) +- Reduce image size with multistage builds [`#72`](https://github.com/Human-Connection/Human-Connection/pull/72) +- Build application for production without nodemon [`#70`](https://github.com/Human-Connection/Human-Connection/pull/70) +- Use deployment script for docker images [`#77`](https://github.com/Human-Connection/Human-Connection/pull/77) +- CI Improvements [`#59`](https://github.com/Human-Connection/Human-Connection/pull/59) +- Admin tags and categories [`#79`](https://github.com/Human-Connection/Human-Connection/pull/79) +- Admin tags and categories [`#62`](https://github.com/Human-Connection/Human-Connection/pull/62) +- Bump vue-jest from 3.0.1 to 3.0.2 [`#89`](https://github.com/Human-Connection/Human-Connection/pull/89) +- Docker ignore scripts folder, bash compatibility [`#66`](https://github.com/Human-Connection/Human-Connection/pull/66) +- Bump eslint from 5.9.0 to 5.10.0 [`#63`](https://github.com/Human-Connection/Human-Connection/pull/63) +- Bump date-fns from 2.0.0-alpha.25 to 2.0.0-alpha.26 [`#71`](https://github.com/Human-Connection/Human-Connection/pull/71) +- Bump slug from 0.9.2 to 0.9.3 [`#67`](https://github.com/Human-Connection/Human-Connection/pull/67) +- Bump eslint from 5.9.0 to 5.10.0 [`#82`](https://github.com/Human-Connection/Human-Connection/pull/82) +- Bump date-fns from 2.0.0-alpha.25 to 2.0.0-alpha.26 [`#88`](https://github.com/Human-Connection/Human-Connection/pull/88) +- Remove end-to-end tests [`#79`](https://github.com/Human-Connection/Human-Connection/pull/79) +- Bump @vue/test-utils from 1.0.0-beta.26 to 1.0.0-beta.27 [`#81`](https://github.com/Human-Connection/Human-Connection/pull/81) +- Bump nodemon from 1.18.7 to 1.18.8 [`#85`](https://github.com/Human-Connection/Human-Connection/pull/85) +- Bump nodemon from 1.18.7 to 1.18.8 [`#69`](https://github.com/Human-Connection/Human-Connection/pull/69) +- Bump graphql-shield from 4.1.1 to 4.1.2 [`#68`](https://github.com/Human-Connection/Human-Connection/pull/68) +- Run full stack tests in the repo [`#86`](https://github.com/Human-Connection/Human-Connection/pull/86) +- Add build status to README.md [`#76`](https://github.com/Human-Connection/Human-Connection/pull/76) +- Several updates on travis yml [`#84`](https://github.com/Human-Connection/Human-Connection/pull/84) +- Full stack tests with submodules and cypress [`#73`](https://github.com/Human-Connection/Human-Connection/pull/73) +- Setup integration testing with MochaJS [`#21`](https://github.com/Human-Connection/Human-Connection/pull/21) +- Bump node-sass from 4.10.0 to 4.11.0 [`#83`](https://github.com/Human-Connection/Human-Connection/pull/83) +- Split development config from production config [`#76`](https://github.com/Human-Connection/Human-Connection/pull/76) +- Fix refresh bug [`#80`](https://github.com/Human-Connection/Human-Connection/pull/80) +- No need to call xip.io anymore [`#78`](https://github.com/Human-Connection/Human-Connection/pull/78) +- Remove xip.io hack [`#61`](https://github.com/Human-Connection/Human-Connection/pull/61) +- Bind name to name instead of model [`#77`](https://github.com/Human-Connection/Human-Connection/pull/77) +- Replace concurrently with npm-run-all [`#60`](https://github.com/Human-Connection/Human-Connection/pull/60) +- [WIP] Pin dependencies [`#58`](https://github.com/Human-Connection/Human-Connection/pull/58) +- Upgraded Styleguide [`#75`](https://github.com/Human-Connection/Human-Connection/pull/75) +- Bump neo4j-graphql-js from 2.0.0 to 2.0.1 [`#57`](https://github.com/Human-Connection/Human-Connection/pull/57) +- Bump neo4j from 3.4 to 3.5.0 [`#26`](https://github.com/Human-Connection/Human-Connection/pull/26) +- Tagged deployments [`#42`](https://github.com/Human-Connection/Human-Connection/pull/42) +- Bump neo4j-graphql-js from 1.0.5 to 2.0.0 [`#51`](https://github.com/Human-Connection/Human-Connection/pull/51) +- Add a missing discord link [`#56`](https://github.com/Human-Connection/Human-Connection/pull/56) +- Use the right db seed name [`#54`](https://github.com/Human-Connection/Human-Connection/pull/54) +- Bump sanitize-html from 1.19.2 to 1.19.3 [`#53`](https://github.com/Human-Connection/Human-Connection/pull/53) +- Copy+paste deployment configuration from backend [`#68`](https://github.com/Human-Connection/Human-Connection/pull/68) +- Preparation for integration testing [`#13`](https://github.com/Human-Connection/Human-Connection/pull/13) +- Bump dotenv from 6.1.0 to 6.2.0 [`#52`](https://github.com/Human-Connection/Human-Connection/pull/52) +- Bump sanitize-html from 1.19.1 to 1.19.2 [`#47`](https://github.com/Human-Connection/Human-Connection/pull/47) +- Run seeds and eslint on Travis CI [`#40`](https://github.com/Human-Connection/Human-Connection/pull/40) +- Bump eslint-plugin-vue from 4.7.1 to 5.0.0 [`#69`](https://github.com/Human-Connection/Human-Connection/pull/69) +- Add troubleshoot to kubernetes README [`#43`](https://github.com/Human-Connection/Human-Connection/pull/43) +- Add unseeder [`#15`](https://github.com/Human-Connection/Human-Connection/pull/15) +- adapt docs to styleguide integration [`#64`](https://github.com/Human-Connection/Human-Connection/pull/64) +- Integrate styleguide with hot reload [`#55`](https://github.com/Human-Connection/Human-Connection/pull/55) +- uppgraded packages [`#38`](https://github.com/Human-Connection/Human-Connection/pull/38) +- [ImgBot] Optimize images [`#62`](https://github.com/Human-Connection/Human-Connection/pull/62) +- Travis ci build [`#22`](https://github.com/Human-Connection/Human-Connection/pull/22) +- updated packages for security and higher digits :) [`#61`](https://github.com/Human-Connection/Human-Connection/pull/61) +- Fix proxy reload issue [`#22`](https://github.com/Human-Connection/Human-Connection/pull/22) +- updated kubernetes documentation - use humanconnection/nitro-backend [`#23`](https://github.com/Human-Connection/Human-Connection/pull/23) +- Fixed Badges Seeder and URL Fix Middleware [`#24`](https://github.com/Human-Connection/Human-Connection/pull/24) +- Update Seed Command in README.md [`#25`](https://github.com/Human-Connection/Human-Connection/pull/25) +- Proxy API Requests [`#21`](https://github.com/Human-Connection/Human-Connection/pull/21) +- Add travis configuration [`#20`](https://github.com/Human-Connection/Human-Connection/pull/20) +- adjust user relation after changing the author relation in the backend [`#19`](https://github.com/Human-Connection/Human-Connection/pull/19) +- Seed the database with authentication [`#18`](https://github.com/Human-Connection/Human-Connection/pull/18) +- Fix container build with styleguide [`#18`](https://github.com/Human-Connection/Human-Connection/pull/18) +- Fix vuep package source [`#17`](https://github.com/Human-Connection/Human-Connection/pull/17) +- Update Babel & babel-node [`#20`](https://github.com/Human-Connection/Human-Connection/pull/20) +- Add Issue template and modify README [`#14`](https://github.com/Human-Connection/Human-Connection/pull/14) +- Test and build server setup [`#6`](https://github.com/Human-Connection/Human-Connection/pull/6) +- Kompose [`#12`](https://github.com/Human-Connection/Human-Connection/pull/12) +- Delete .gitignored file .env [`#13`](https://github.com/Human-Connection/Human-Connection/pull/13) +- Add volumes to use docker for development [`#11`](https://github.com/Human-Connection/Human-Connection/pull/11) +- Expose neo4j-browser ports [`#10`](https://github.com/Human-Connection/Human-Connection/pull/10) +- Use xip.io trick for server/client side rendering [`#9`](https://github.com/Human-Connection/Human-Connection/pull/9) +- Docker [`#3`](https://github.com/Human-Connection/Human-Connection/pull/3) +- Update schema.graphql [`#8`](https://github.com/Human-Connection/Human-Connection/pull/8) +- Bump API from `da96078` to `4437261` [`#53`](https://github.com/Human-Connection/Human-Connection/pull/53) +- Custom Dockerfile to install apoc plugin in neo4j [`#6`](https://github.com/Human-Connection/Human-Connection/pull/6) +- Add Installation with Docker [`#5`](https://github.com/Human-Connection/Human-Connection/pull/5) +- Add README instructions to configure .env [`#1`](https://github.com/Human-Connection/Human-Connection/pull/1) +- Replace fake email domain with example.org [`#3`](https://github.com/Human-Connection/Human-Connection/pull/3) +- Add submodules to auto-update those by dependabot [`#24`](https://github.com/Human-Connection/Human-Connection/pull/24) +- Delete docker-compose.yml [`#23`](https://github.com/Human-Connection/Human-Connection/pull/23) +- Create .gitignore [`#22`](https://github.com/Human-Connection/Human-Connection/pull/22) +- Update README.md [`#13`](https://github.com/Human-Connection/Human-Connection/pull/13) +- Fix #1616 [`#1616`](https://github.com/Human-Connection/Human-Connection/issues/1616) +- Fix #1555 [`#1555`](https://github.com/Human-Connection/Human-Connection/issues/1555) +- Merge pull request #1508 from Human-Connection/1505_remove_html_in_moderation_view [`#1505`](https://github.com/Human-Connection/Human-Connection/issues/1505) +- Fix #1506 [`#1506`](https://github.com/Human-Connection/Human-Connection/issues/1506) +- Fix #1505 remove html [`#1505`](https://github.com/Human-Connection/Human-Connection/issues/1505) +- Merge pull request #1479 from Human-Connection/1394-proper_pagination_implementation [`#1394`](https://github.com/Human-Connection/Human-Connection/issues/1394) +- Fix #1394 [`#1394`](https://github.com/Human-Connection/Human-Connection/issues/1394) +- Refactor seeds - no need for a factories server! [`#1427`](https://github.com/Human-Connection/Human-Connection/issues/1427) +- Notifications query filters for deleted resources [`#1414`](https://github.com/Human-Connection/Human-Connection/issues/1414) +- Fix #1333 [`#1333`](https://github.com/Human-Connection/Human-Connection/issues/1333) +- Merge pull request #1314 from Human-Connection/1308-fix_broken_graphql_query [`#1308`](https://github.com/Human-Connection/Human-Connection/issues/1308) +- Merge pull request #1316 from Human-Connection/1315-fix_unique_constraint_bug_when_merging_a_new_hashtag [`#1315`](https://github.com/Human-Connection/Human-Connection/issues/1315) +- Fix #1315 [`#1315`](https://github.com/Human-Connection/Human-Connection/issues/1315) +- Fix #1308 [`#1308`](https://github.com/Human-Connection/Human-Connection/issues/1308) +- Massive refactoring [`#1231`](https://github.com/Human-Connection/Human-Connection/issues/1231) +- Merge pull request #1041 from Human-Connection/822_fix_load_more_button [`#822`](https://github.com/Human-Connection/Human-Connection/issues/822) +- Fix #822 by updating the counts of the user [`#822`](https://github.com/Human-Connection/Human-Connection/issues/822) +- Implement prefix of image urls with a filter [`#820`](https://github.com/Human-Connection/Human-Connection/issues/820) +- Merge pull request #809 from Human-Connection/800_fix_broken_image_urls [`#800`](https://github.com/Human-Connection/Human-Connection/issues/800) +- Merge pull request #810 from Human-Connection/799_fix_badge_urls [`#799`](https://github.com/Human-Connection/Human-Connection/issues/799) +- Fix #799 [`#799`](https://github.com/Human-Connection/Human-Connection/issues/799) +- Merge pull request #807 from Human-Connection/470_re_enable_cypress_and_fix_builds_on_forks [`#470`](https://github.com/Human-Connection/Human-Connection/issues/470) +- Fix #800 [`#800`](https://github.com/Human-Connection/Human-Connection/issues/800) +- Fix #470 [`#470`](https://github.com/Human-Connection/Human-Connection/issues/470) +- make post sticky - fixes https://github.com/Human-Connection/Human-Connection/issues/385 [`#385`](https://github.com/Human-Connection/Human-Connection/issues/385) +- hide all unfunctional content - fixes https://github.com/Human-Connection/Human-Connection/issues/429 [`#429`](https://github.com/Human-Connection/Human-Connection/issues/429) +- Merge pull request #362 from Human-Connection/quick-fix [`#361`](https://github.com/Human-Connection/Human-Connection/issues/361) +- Fixes #361 [`#361`](https://github.com/Human-Connection/Human-Connection/issues/361) +- Merge pull request #243 from Human-Connection/234-fix [`#234`](https://github.com/Human-Connection/Human-Connection/issues/234) +- Should fix #234 + refactor domain to hostname [`#234`](https://github.com/Human-Connection/Human-Connection/issues/234) +- Add unauthenticated test to follow and shout [`#234`](https://github.com/Human-Connection/Human-Connection/issues/234) +- Fix #106 [`#106`](https://github.com/Human-Connection/Human-Connection/issues/106) +- Merge pull request #104 from DakshMiglani/master [`#41`](https://github.com/Human-Connection/Human-Connection/issues/41) +- Merge pull request #93 from Gerald1614/500_error_on_login [`#49`](https://github.com/Human-Connection/Human-Connection/issues/49) +- Update schema.graphql [`#7`](https://github.com/Human-Connection/Human-Connection/issues/7) +- Refactore the import and hashtags to all unicode characters [`0bc4c55`](https://github.com/Human-Connection/Human-Connection/commit/0bc4c558ae8f01d6d975b8ee1ea7f0f42b056d91) +- Remove package-lock.json [`25bd96e`](https://github.com/Human-Connection/Human-Connection/commit/25bd96eedf6be5b7ea6e94c8433d044e13d62e70) +- Remove Styleguide [`53ea934`](https://github.com/Human-Connection/Human-Connection/commit/53ea93492dcc7f861743cd50a4ddf7728c9d659b) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0eb90a824..e0926678b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,79 +1,101 @@ # CONTRIBUTING -Thanks so much for thinking of contributing to the Human Connection project, we really appreciate it! :-\) +Thank you so much for thinking of contributing to the Human Connection project! It's awesome you're here, we really appreciate it. :-\) ## 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 and some code guidelines 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](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): +To get you started we recommend that you join forces with a regular contributor. Please join [our discord instance](https://human-connection.org/discord) to chat with developers or just get in touch directly on an issue on either [Github](https://github.com/Human-Connection/Human-Connection/issues) or [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) -Here are some general notes on our development flow: +We also have regular pair programming sessions that you are very welcome to join! We feel this is often the best way to get to know both the project and the team. Most developers are also available for spontaneous sessions if the times listed below don't work for you – just ping us on discord. -## Development +## 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/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 - * all the people who are hired by HC non-profit corporation - * you can Meet-the-team [every two weeks in German](https://human-connection.org/veranstaltungen/) and [every month in English](https://human-connection.org/en/events/). - * 9 people - * 2 core developers \(Robert [@roschaefer](https://github.com/roschaefer) and Greg [@appinteractive](https://github.com/appinteractive)\) - * 3 marketeers Jasi, Dennis and Sensi - * Hardy doing business development - * Martin head of IT and previously data protection officer - * Victor doing accounting and controlling - * Nicolas is the community manager \(reviews content in the network\) reflects community opinion back to the core team -* when can folks pair with Robert - * 10am UTC until 5pm UTC every working day +We operate in two week sprints that are planned, estimated and prioritised on [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f). All issues are also linked to and synced with [Github](https://github.com/Human-Connection/Human-Connection/issues). Look for the `good first issue` label if you're not sure where to start! + +We try to discuss all questions directly related to a feature or bug in the respective issue, in order to preserve it for the future and for other developers. We use discord for real-time communication. + +This is how we solve bugs and implement features, step by step: +1. We find an issue we want to work on, usually during the sprint planning but as an open source contributor this can happen at any time. +2. We communicate with the team to see if the issue is still available. (When you comment on an issue but don't get an answer there within 1-2 days try to mention @Human-Connection/hc-dev-team to make sure we check in.) +3. We make sure we understand the issue in detail – what problem is it solving and how should it be implemented? +4. We assign ourselves to the issue and move it to `In Progress` on [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f). +5. We start working on it in a `new branch` and open a `pull request` prefixed with `[WIP]` (work in progress) to which we regularly push our changes. +6. When questions come up we clarify them with the team (directly in the issue on Github). +7. When we are happy with our work and our PR is passing all tests we remove the `[WIP]` from the PR description and ask for reviews (if you're not sure who to ask there is @Human-Connection/hc-dev-team which pings all core developers). +8. We then incorporate the suggestions from the reviews into our work and once it has been approved it can be merged into master! + +Every pull request needs to: +* fix an issue (if there is something you want to work on but there is no issue for it, create one first and discuss it with the team) +* include tests for the code that is added or changed +* pass all tests (linter, backend, frontend, end-to-end) +* be approved by at least 1 developer who is not the owner of the PR (when more than 10 files were changed it needs 2 approvals) + +## The Team + +There are many volunteers all around the world helping us build this network and without their contributions we wouldn't be where we are today. Big thank you to all of you! + +You can see the core team behind Human Connection [on our website](https://human-connection.org/en/the-team/). On Github you will mostly run into our developers: +* Robert (@roschaefer) +* Matt (@mattwr18) +* Wolle (@Tirokk) +* Alex (@ogerly) +* Alina (@alina-beck) +* Martin (@datenbrei), our head of IT +* and sometimes Dennis (@DennisHack), the founder of Human Connection + +## Meetings and Pair Programming Sessions + +Times below refer to **German Time** – that's CET (GMT+1) in winter and CEST (GMT+2) in summer – because most Human Connection core team members are living in Germany. + +Daily standup +* every Monday–Friday 11:30 +* in the discord `Conference Room` +* all contributors welcome! +* everybody shares what they are working on and asks for help if they are blocked + +Regular pair programming sessions +* every Monday, Wednesday and Thursday 15:00 +* the link will be posted in the [discord chat](https://discord.gg/6ub73U3) and on the [Agile Ventures website](https://www.agileventures.org/events?utf8=%E2%9C%93&project_id=220&commit=Filter+by+Project) +* all contributors welcome! +* we team up and work on an issue together (often using Visual Studio live sharing sessions) + +Open-Source Community Meeting +* every Thursday 13:00 +* the link will be posted in the [discord chat](https://discord.gg/6ub73U3) and on the [Agile Ventures website](https://www.agileventures.org/events?utf8=%E2%9C%93&project_id=220&commit=Filter+by+Project) +* all contributors welcome! + +Meet the team +* every Monday 21:00 (at the moment only in German) +* details here https://human-connection.org/veranstaltungen/ +* via this [zoom link](https://zoom.us/j/936943532) +* all contributors and users of the network welcome! +* users of the network chat with the Human Connection team and discuss current questions and issues + +Sprint planning +* bi-weekly on Tuesday 13:00 +* via this [zoom link](https://zoom.us/j/7743582385) +* all contributors welcome (recommended for those who want to work on an issue in this sprint) +* we select and prioritise the issues we will work on in the following two weeks + +Sprint retrospective +* bi-weekly on Monday 13:00 +* via this [zoom link](https://zoom.us/j/7743582385) +* all contributors welcome (most interesting for those who participated in the sprint) +* we review the past sprint and talk about what went well and what we could improve ## Philosophy We practise [collective code ownership](http://www.extremeprogramming.org/rules/collective.html) rather than strong code ownership, which means that: - -* anyone can start working on anyone elses code -* we avoid blocking because someone else isn't working on something -* however it's sometimes good to leave something in order to create successful education experience +* developers can make contributions to other people's PRs (after checking in with them) +* we avoid blocking because someone else isn't working, so we sometimes take over PRs from other developers * everyone should always push their code to branches so others can see it -Everyone feel free to request merges or answers to issues from the project managers +We believe in open source contributions as a learning experience – everyone is welcome to join our team of volunteers and to contribute to the project, no matter their background or level of experience. -But what do we do when waiting for merge into master \(wanting to keep PRs small\) --> Robert recommends creating a pull request for each step +We use pair programming sessions as a tool for knowledge sharing. We can learn a lot from each other and only by sharing what we know and overcoming challenges together can we grow as a team and truly own this project collectively. -* programming is also about thinking about other people - empathy for your co-workers - * but what about when you are waiting for merge? - * 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 - - 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 - - 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 - -Robert shares: [Zenhub board](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/boards?repos=112590397,152252353,152252578,157710732,163305928) Robert says the order of tickets are preserved in ZenHub and reflect their priority \(most important at the top\) and so check out the current milestones - -Matt - question about who can work on [ticket 100](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/issues/human-connection/human-connection/100) --> Robert - in rare occasions it might be exclusive to someone with admin permissions Robert: notes greg just pushed this today: [https://github.com/Human-Connection/Nitro-Deployment](https://github.com/Human-Connection/Nitro-Deployment) - -Matt makes point that new stories will have to be taken off the "New Issues" and Robert says that's fine, if you don't like the first one, then you can take the next one. Volunteeers have no commitment except their own self development and their awesomeness by contributing to free and open-source software projects. - -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\) +As a volunteeer you have no commitment except your own self development and your awesomeness by contributing to this free and open-source software project. Cheers to you! diff --git a/README.md b/README.md index 1e305b201..eaf71acb8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![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://discordapp.com/invite/DFSjPaX) +[![Open Source Helpers](https://www.codetriage.com/human-connection/human-connection/badges/users.svg)](https://www.codetriage.com/human-connection/human-connection) Human Connection is a nonprofit social, action and knowledge network that connects information to action and promotes positive local and global change in all areas of life. @@ -24,7 +25,7 @@ Human Connection is a nonprofit social, action and knowledge network that connec ## Live demo -Try out our deployed [staging environment](https://nitro-staging.human-connection.org/). +Try out our deployed [development environment](https://develop.human-connection.org/). Logins: @@ -49,10 +50,16 @@ Join our friendly open-source community on [Discord](https://discordapp.com/invi Just introduce yourself at `#introduce-yourself` and mention `@@Mentor` to get you onboard :neckbeard: Check out the [contribution guideline](./CONTRIBUTING.md), too! +[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/0)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/0)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/1)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/1)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/2)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/2)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/3)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/3)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/4)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/4)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/5)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/5)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/6)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/6)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/7)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/7) + ## Attributions -Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/) +Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/). + +Browser compatibility testing with [BrowserStack](https://www.browserstack.com/). + +BrowserStack Logo ## License See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT). diff --git a/SUMMARY.md b/SUMMARY.md index 2c73ba5d5..53beae7ad 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -6,16 +6,12 @@ * [Neo4J](neo4j/README.md) * [Backend](backend/README.md) * [GraphQL](backend/graphql.md) + * [neo4j-graphql-js](backend/neo4j-graphql-js.md) * [Webapp](webapp/README.md) - * [COMPONENTS](webapp/components.md) - * [PLUGINS](webapp/plugins.md) - * [STORE](webapp/store.md) - * [PAGES](webapp/pages.md) - * [ASSETS](webapp/assets.md) - * [LAYOUTS](webapp/layouts.md) - * [Styleguide](webapp/styleguide.md) - * [STATIC](webapp/static.md) - * [MIDDLEWARE](webapp/middleware.md) + * [Components](webapp/components.md) + * [HTML](webapp/html.md) + * [SCSS](webapp/scss.md) + * [Vue](webapp/vue.md) * [Testing Guide](testing.md) * [End-to-end tests](cypress/README.md) * [Frontend tests](webapp/testing.md) @@ -32,9 +28,11 @@ * [Maintenance](deployment/human-connection/maintenance/README.md) * [Volumes](deployment/volumes/README.md) * [Neo4J Offline-Backups](deployment/volumes/neo4j-offline-backup/README.md) + * [Neo4J Online-Backups](deployment/volumes/neo4j-online-backup/README.md) * [Volume Snapshots](deployment/volumes/volume-snapshots/README.md) * [Reclaim Policy](deployment/volumes/reclaim-policy/README.md) * [Velero](deployment/volumes/velero/README.md) + * [Metrics](deployment/monitoring/README.md) * [Legacy Migration](deployment/legacy-migration/README.md) * [Feature Specification](cypress/features.md) * [Code of conduct](CODE_OF_CONDUCT.md) diff --git a/VERSION b/VERSION deleted file mode 100644 index 6e8bf73aa..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 000000000..2d91b3635 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "10" + } + } + ] + ] +} diff --git a/backend/.env.template b/backend/.env.template index 6697f09c4..b4c91da9a 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -1,7 +1,6 @@ NEO4J_URI=bolt://localhost:7687 NEO4J_USERNAME=neo4j NEO4J_PASSWORD=letmein -GRAPHQL_PORT=4000 GRAPHQL_URI=http://localhost:4000 CLIENT_URI=http://localhost:3000 SMTP_HOST= @@ -17,3 +16,4 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78" SENTRY_DSN_BACKEND= COMMIT= +PUBLIC_REGISTRATION=false diff --git a/backend/Dockerfile b/backend/Dockerfile index dc2e6da87..957bc6ab5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.10.0-alpine as base +FROM node:lts-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 @@ -24,4 +24,5 @@ FROM base as production ENV NODE_ENV=production COPY --from=build-and-test /nitro-backend/dist ./dist COPY ./public/img/ ./public/img/ +COPY ./public/providers.json ./public/providers.json RUN yarn install --production=true --frozen-lockfile --non-interactive --no-cache diff --git a/backend/README.md b/backend/README.md index cd56e231f..7fd49faf8 100644 --- a/backend/README.md +++ b/backend/README.md @@ -53,6 +53,27 @@ can issue GraphQL requests or access GraphQL Playground in the browser. ![GraphQL Playground](../.gitbook/assets/graphql-playground.png) +### Database Indices and Constraints + +Database indices and constraints need to be created when the database and the +backend is running: + +{% tabs %} +{% tab title="Docker" %} +```bash +docker-compose exec backend yarn run db:migrate init +``` +{% endtab %} + +{% tab title="Without Docker" %} +```bash +# in folder backend/ +# make sure your database is running on http://localhost:7474/browser/ +yarn run db:migrate init +``` +{% endtab %} +{% endtabs %} + #### Seed Database @@ -72,6 +93,8 @@ To reset the database run: $ docker-compose exec backend yarn run db:reset # you could also wipe out your neo4j database and delete all volumes with: $ docker-compose down -v +# if container is not running, run this command to set up your database indeces and contstraints +$ docker-compose run backend yarn run db:migrate init ``` {% endtab %} @@ -88,6 +111,37 @@ $ yarn run db:reset {% endtab %} {% endtabs %} +### Data migrations + +Although Neo4J is schema-less,you might find yourself in a situation in which +you have to migrate your data e.g. because your data modeling has changed. + +{% tabs %} +{% tab title="Docker" %} +Generate a data migration file: +```bash +$ docker-compose exec backend yarn run db:migrate:create your_data_migration +# Edit the file in ./src/db/migrations/ +``` + +To run the migration: +```bash +$ docker-compose exec backend yarn run db:migrate up +``` +{% endtab %} +{% tab title="Without Docker" %} +Generate a data migration file: +```bash +$ yarn run db:migrate:create your_data_migration +# Edit the file in ./src/db/migrations/ +``` + +To run the migration: +```bash +$ yarn run db:migrate up +``` +{% endtab %} +{% endtabs %} # Testing diff --git a/backend/.babelrc b/backend/babel.config.json similarity index 100% rename from backend/.babelrc rename to backend/babel.config.json diff --git a/backend/neo4j-graphql-js.md b/backend/neo4j-graphql-js.md new file mode 100644 index 000000000..280942acd --- /dev/null +++ b/backend/neo4j-graphql-js.md @@ -0,0 +1,16 @@ +# neo4j-graphql.js + +We use an npm package called `neo4j-graphql-js` as a cypher query builder. This +library also generates resolvers for graphql queries, unless we implement them +ourselves. + + +## Debugging + +As you can see in their [documentation](https://github.com/neo4j-graphql/neo4j-graphql-js) +it is possible to log out the generated cypher statements. To do so, run the +backend like this: + +```sh +DEBUG=neo4j-graphql-js yarn run dev +``` diff --git a/backend/package.json b/backend/package.json index aa1fe0deb..e9e0f44a3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,26 +1,22 @@ { "name": "human-connection-backend", - "version": "0.0.1", + "version": "0.2.2", "description": "GraphQL Backend for Human Connection", "main": "src/index.js", "scripts": { - "build": "babel src/ -d dist/ --copy-files", + "__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations", + "prod:migrate": "migrate --migrations-dir ./dist/db/migrations --store ./dist/db/migrate/store.js", "start": "node dist/", + "build": "babel src/ -d dist/ --copy-files", "dev": "nodemon --exec babel-node src/ -e js,gql", - "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,gql", + "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql", "lint": "eslint src --config .eslintrc.js", - "jest": "jest --forceExit --detectOpenHandles --runInBand", - "test": "run-s test:jest test:cucumber", - "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev 2> /dev/null", - "test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev 2> /dev/null", - "test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand", - "test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/", - "test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand", - "test:jest": "run-p --race test:before:* \"test:jest:cmd {@}\" --", - "test:cucumber": " cross-env CLIENT_URI=http://localhost:4123 run-p --race test:before:* 'test:cucumber:cmd {@}' --", - "test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --", - "db:reset": "babel-node src/seed/reset-db.js", - "db:seed": "babel-node src/seed/seed-db.js" + "test": "jest --forceExit --detectOpenHandles --runInBand", + "db:clean": "babel-node src/db/clean.js", + "db:reset": "yarn run db:clean", + "db:seed": "babel-node src/db/seed.js", + "db:migrate": "yarn run __migrate --store ./src/db/migrate/store.js", + "db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.js --date-format 'yyyymmddHHmmss' create" }, "author": "Human Connection gGmbH", "license": "MIT", @@ -41,94 +37,96 @@ ] }, "dependencies": { - "@hapi/joi": "^16.0.1", - "@sentry/node": "^5.6.2", - "activitystrea.ms": "~2.1.3", - "apollo-cache-inmemory": "~1.6.3", - "apollo-client": "~2.6.4", + "@hapi/joi": "^17.1.0", + "@sentry/node": "^5.11.1", + "apollo-cache-inmemory": "~1.6.5", + "apollo-client": "~2.6.8", "apollo-link-context": "~1.0.19", "apollo-link-http": "~1.5.16", - "apollo-server": "~2.9.3", - "apollo-server-express": "^2.9.0", + "apollo-server": "~2.9.16", + "apollo-server-express": "^2.9.16", "babel-plugin-transform-runtime": "^6.23.0", "bcryptjs": "~2.4.3", "cheerio": "~1.0.0-rc.3", "cors": "~2.8.5", - "cross-env": "~5.2.1", - "date-fns": "2.1.0", + "cross-env": "~7.0.0", + "date-fns": "2.9.0", "debug": "~4.1.1", - "dotenv": "~8.1.0", + "dotenv": "~8.2.0", "express": "^4.17.1", "faker": "Marak/faker.js#master", - "graphql": "^14.5.4", + "graphql": "^14.6.0", "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", - "graphql-middleware": "~3.0.5", - "graphql-middleware-sentry": "^3.2.0", - "graphql-shield": "~6.1.0", + "graphql-middleware": "~4.0.2", + "graphql-middleware-sentry": "^3.2.1", + "graphql-shield": "~7.0.8", "graphql-tag": "~2.10.1", - "helmet": "~3.21.0", + "helmet": "~3.21.2", "jsonwebtoken": "~8.5.1", "linkifyjs": "~2.1.8", "lodash": "~4.17.14", - "merge-graphql-schemas": "^1.7.0", - "metascraper": "^4.10.3", - "metascraper-audio": "^5.6.5", - "metascraper-author": "^5.6.5", + "merge-graphql-schemas": "^1.7.6", + "metascraper": "^5.10.6", + "metascraper-audio": "^5.10.6", + "metascraper-author": "^5.10.6", "metascraper-clearbit-logo": "^5.3.0", - "metascraper-date": "^5.7.0", - "metascraper-description": "^5.7.4", - "metascraper-image": "^5.6.5", - "metascraper-lang": "^5.6.5", - "metascraper-lang-detector": "^4.8.5", - "metascraper-logo": "^5.7.4", - "metascraper-publisher": "^5.6.5", - "metascraper-soundcloud": "^5.6.7", - "metascraper-title": "^5.7.0", - "metascraper-url": "^5.7.4", - "metascraper-video": "^5.6.5", - "metascraper-youtube": "^5.7.4", + "metascraper-date": "^5.10.6", + "metascraper-description": "^5.10.6", + "metascraper-image": "^5.10.6", + "metascraper-lang": "^5.10.6", + "metascraper-lang-detector": "^4.10.2", + "metascraper-logo": "^5.10.6", + "metascraper-publisher": "^5.10.6", + "metascraper-soundcloud": "^5.10.6", + "metascraper-title": "^5.10.6", + "metascraper-url": "^5.10.6", + "metascraper-video": "^5.10.6", + "metascraper-youtube": "^5.10.6", + "migrate": "^1.6.2", "minimatch": "^3.0.4", - "neo4j-driver": "~1.7.6", - "neo4j-graphql-js": "^2.7.2", - "neode": "^0.3.3", + "mustache": "^4.0.0", + "neo4j-driver": "^4.0.1", + "neo4j-graphql-js": "^2.11.5", + "neode": "^0.3.7", "node-fetch": "~2.6.0", - "nodemailer": "^6.3.0", + "nodemailer": "^6.4.2", + "nodemailer-html-to-text": "^3.1.0", "npm-run-all": "~4.1.5", "request": "~2.88.0", - "sanitize-html": "~1.20.1", - "slug": "~1.1.0", + "sanitize-html": "~1.21.1", + "slug": "~2.1.1", "trunc-html": "~1.1.2", - "uuid": "~3.3.3", - "xregexp": "^4.2.4", - "wait-on": "~3.3.0" + "uuid": "~3.4.0", + "validator": "^12.2.0", + "wait-on": "~4.0.0", + "xregexp": "^4.2.4" }, "devDependencies": { - "@babel/cli": "~7.6.0", - "@babel/core": "~7.6.0", - "@babel/node": "~7.6.1", - "@babel/plugin-proposal-throw-expressions": "^7.2.0", - "@babel/preset-env": "~7.6.0", - "@babel/register": "~7.6.0", - "apollo-server-testing": "~2.9.3", + "@babel/cli": "~7.8.3", + "@babel/core": "~7.8.3", + "@babel/node": "~7.8.3", + "@babel/plugin-proposal-throw-expressions": "^7.8.3", + "@babel/preset-env": "~7.8.3", + "@babel/register": "^7.8.3", + "apollo-server-testing": "~2.9.16", "babel-core": "~7.0.0-0", "babel-eslint": "~10.0.3", - "babel-jest": "~24.9.0", + "babel-jest": "~25.1.0", "chai": "~4.2.0", - "cucumber": "~5.1.0", - "eslint": "~6.3.0", - "eslint-config-prettier": "~6.3.0", + "cucumber": "~6.0.5", + "eslint": "~6.8.0", + "eslint-config-prettier": "~6.9.0", "eslint-config-standard": "~14.1.0", - "eslint-plugin-import": "~2.18.2", - "eslint-plugin-jest": "~22.17.0", - "eslint-plugin-node": "~10.0.0", - "eslint-plugin-prettier": "~3.1.0", + "eslint-plugin-import": "~2.20.0", + "eslint-plugin-jest": "~23.6.0", + "eslint-plugin-node": "~11.0.0", + "eslint-plugin-prettier": "~3.1.2", "eslint-plugin-promise": "~4.2.1", "eslint-plugin-standard": "~4.0.1", - "graphql-request": "~1.8.2", - "jest": "~24.9.0", - "nodemon": "~1.19.2", - "prettier": "~1.18.2", + "jest": "~25.1.0", + "nodemon": "~2.0.2", + "prettier": "~1.19.1", "supertest": "~4.0.2" } -} \ No newline at end of file +} diff --git a/backend/public/providers.json b/backend/public/providers.json new file mode 100644 index 000000000..ef9f04bff --- /dev/null +++ b/backend/public/providers.json @@ -0,0 +1,257 @@ +[ + { + "provider_name": "Codepen", + "provider_url": "https:\/\/codepen.io", + "endpoints": [ + { + "schemes": [ + "http:\/\/codepen.io\/*", + "https:\/\/codepen.io\/*" + ], + "url": "http:\/\/codepen.io\/api\/oembed" + } + ] + }, + { + "provider_name": "DTube", + "provider_url": "https:\/\/d.tube\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/d.tube\/v\/*" + ], + "url": "https:\/\/api.d.tube\/oembed", + "discovery": true + } + ] + }, + { + "provider_name": "Facebook (Post)", + "provider_url": "https:\/\/www.facebook.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/www.facebook.com\/*\/posts\/*", + "https:\/\/www.facebook.com\/photos\/*", + "https:\/\/www.facebook.com\/*\/photos\/*", + "https:\/\/www.facebook.com\/photo.php*", + "https:\/\/www.facebook.com\/photo.php", + "https:\/\/www.facebook.com\/*\/activity\/*", + "https:\/\/www.facebook.com\/permalink.php", + "https:\/\/www.facebook.com\/media\/set?set=*", + "https:\/\/www.facebook.com\/questions\/*", + "https:\/\/www.facebook.com\/notes\/*\/*\/*" + ], + "url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json", + "discovery": true + } + ] + }, + { + "provider_name": "Facebook (Video)", + "provider_url": "https:\/\/www.facebook.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/www.facebook.com\/*\/videos\/*", + "https:\/\/www.facebook.com\/video.php" + ], + "url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json", + "discovery": true + } + ] + }, + { + "provider_name": "Flickr", + "provider_url": "https:\/\/www.flickr.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/*.flickr.com\/photos\/*", + "http:\/\/flic.kr\/p\/*", + "https:\/\/*.flickr.com\/photos\/*", + "https:\/\/flic.kr\/p\/*" + ], + "url": "https:\/\/www.flickr.com\/services\/oembed\/", + "discovery": true + } + ] + }, + { + "provider_name": "GIPHY", + "provider_url": "https:\/\/giphy.com", + "endpoints": [ + { + "schemes": [ + "https:\/\/giphy.com\/gifs\/*", + "http:\/\/gph.is\/*", + "https:\/\/media.giphy.com\/media\/*\/giphy.gif" + ], + "url": "https:\/\/giphy.com\/services\/oembed", + "discovery": true + } + ] + }, + { + "provider_name": "Instagram", + "provider_url": "https:\/\/instagram.com", + "endpoints": [ + { + "schemes": [ + "http:\/\/instagram.com\/p\/*", + "http:\/\/instagr.am\/p\/*", + "http:\/\/www.instagram.com\/p\/*", + "http:\/\/www.instagr.am\/p\/*", + "https:\/\/instagram.com\/p\/*", + "https:\/\/instagr.am\/p\/*", + "https:\/\/www.instagram.com\/p\/*", + "https:\/\/www.instagr.am\/p\/*" + ], + "url": "https:\/\/api.instagram.com\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "Meetup", + "provider_url": "http:\/\/www.meetup.com", + "endpoints": [ + { + "schemes": [ + "http:\/\/meetup.com\/*", + "https:\/\/www.meetup.com\/*", + "https:\/\/meetup.com\/*", + "http:\/\/meetu.ps\/*" + ], + "url": "https:\/\/api.meetup.com\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "MixCloud", + "provider_url": "https:\/\/mixcloud.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/www.mixcloud.com\/*\/*\/", + "https:\/\/www.mixcloud.com\/*\/*\/" + ], + "url": "https:\/\/www.mixcloud.com\/oembed\/" + } + ] + }, + { + "provider_name": "Reddit", + "provider_url": "https:\/\/reddit.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/reddit.com\/r\/*\/comments\/*\/*", + "https:\/\/www.reddit.com\/r\/*\/comments\/*\/*" + ], + "url": "https:\/\/www.reddit.com\/oembed" + } + ] + }, + { + "provider_name": "SlideShare", + "provider_url": "http:\/\/www.slideshare.net\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/www.slideshare.net\/*\/*", + "http:\/\/fr.slideshare.net\/*\/*", + "http:\/\/de.slideshare.net\/*\/*", + "http:\/\/es.slideshare.net\/*\/*", + "http:\/\/pt.slideshare.net\/*\/*" + ], + "url": "http:\/\/www.slideshare.net\/api\/oembed\/2", + "discovery": true + } + ] + }, + { + "provider_name": "SoundCloud", + "provider_url": "http:\/\/soundcloud.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/soundcloud.com\/*", + "https:\/\/soundcloud.com\/*" + ], + "url": "https:\/\/soundcloud.com\/oembed" + } + ] + }, + { + "provider_name": "Twitch", + "provider_url": "https:\/\/www.twitch.tv", + "endpoints": [ + { + "schemes": [ + "http:\/\/clips.twitch.tv\/*", + "https:\/\/clips.twitch.tv\/*", + "http:\/\/www.twitch.tv\/*", + "https:\/\/www.twitch.tv\/*", + "http:\/\/twitch.tv\/*", + "https:\/\/twitch.tv\/*" + ], + "url": "https:\/\/api.twitch.tv\/v4\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "Twitter", + "provider_url": "http:\/\/www.twitter.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/twitter.com\/*\/status\/*", + "https:\/\/*.twitter.com\/*\/status\/*" + ], + "url": "https:\/\/publish.twitter.com\/oembed" + } + ] + }, + { + "provider_name": "Vimeo", + "provider_url": "https:\/\/vimeo.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/vimeo.com\/*", + "https:\/\/vimeo.com\/album\/*\/video\/*", + "https:\/\/vimeo.com\/channels\/*\/*", + "https:\/\/vimeo.com\/groups\/*\/videos\/*", + "https:\/\/vimeo.com\/ondemand\/*\/*", + "https:\/\/player.vimeo.com\/video\/*" + ], + "url": "https:\/\/vimeo.com\/api\/oembed.{format}", + "discovery": true + } + ] + }, + { + "provider_name": "YouTube", + "provider_url": "https:\/\/www.youtube.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/*.youtube.com\/watch*", + "https:\/\/*.youtube.com\/v\/*", + "https:\/\/youtu.be\/*" + ], + "url": "https:\/\/www.youtube.com\/oembed", + "discovery": true + } + ] + } +] \ No newline at end of file diff --git a/backend/snapshots/embeds/HumanConnectionOrg.html b/backend/snapshots/embeds/HumanConnectionOrg.html new file mode 100644 index 000000000..67a7844d2 --- /dev/null +++ b/backend/snapshots/embeds/HumanConnectionOrg.html @@ -0,0 +1,2667 @@ + + + + + + + +Human Connection - Startseite | +Facebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
Gehe +zu:
+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Mehr von Human Connection auf Facebook +anzeigen
+ +
+
+
+
+
+
Mehr von Human Connection auf Facebook +anzeigen
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
oder
+
+Neues Konto erstellen + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Willkommen bei Human Connection
+
+
Human Connection +ist ein gemeinnütziges soziales Wissens- und Aktionsnetzwerk mit +Sitz in Weilheim-T...
+
+Mehr anzeigen
+
+
+
CommunityAlle +ansehen +
+
+
+
+
+"Highlights
+
+
24.407 Personen gefällt das
+
+
+
+
+
+
+"Highlights
+
+
25.652 Personen haben das abonniert
+
+
+
+
+
+
+"Highlights
+
+
72 Besuche
+
+
+
+
+
+
InfoAlle ansehen +
+
+
+ +
+
+
+
+"Highlights
+
+
Bahnhofstraße 11 (512,71 km)
+73235 Weilheim an der Teck
+ +
+
+
+
+
+
+"Highlights
+
+
01514 3804222
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+"Highlights
+
+
Preisklasse €
+
+
+
+
+ +
+
+
+
SeitentransparenzMehr +anzeigen +
+
+
Facebook liefert +Informationen, mit denen du die Intention von Seiten besser +verstehst. Hier erfährst du mehr zu den Personen, die die Seiten +verwalten und Beiträge darin posten.
+
+
+Seite erstellt – 21. Oktober 2015
+
+
+
+
+
+
+
+
+
+
+
+
+
Personen
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ähnliche +Seiten
+
+ +
+
+
+
+
+
+
+
+ +
Seiten, die +dieser Seite gefallen
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
Orte +Weilheim an der Teck +Human Connection
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
Beiträge +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+

Human Connection soll mehr als nur ein einfacher Facebookersatz +werden – nämlich vor allem lösungsorientiert und zukunftsfähig. Wir +möchten mit unserem Netzwerk dazu beitragen, dass mehr Menschen +aktiv werden und selbst etwas verändern. Vor allem möchten wir, +dass sie nicht jede negative Neuigkeit in einem sozialen Netzwerk +“schlucken” müssen. Der Gedanke man könne doch eh nichts ändern, +stimmt nur dann, wenn andere Leser genauso denken. Gemeinsam ist so +viel mehr möglich und... +dafür braucht es zunächst erst +einmal jeden Einzelnen.

+
+

Wie kann Human Connection dabei helfen?

+

Das erfahrt ihr im ganzen Beitrag:
+https://human-connection.org/human-connection-loesungsorie…/

+
+Mehr anzeigen
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+

Human Connection ist unser Herzensprojekt, welches allerdings +uns und auch euch sehr viel Geduld abfordert. Warum dauert das +eigentlich so lange?

+

Hier erfahrt ihr die Antwort:

+
+
+
+
+
+
+
+ +
+
+
+
+
human-connection.org
+
+
+
+
+ +
Human Connection ist +unser Herzensprojekt, welches allerdings uns und auch euch sehr +viel Geduld abfordert. Warum dauert das eigentlich so +lange?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
Videos +
+
+
+
+
+
Schau es an und Du +weißt, was zu tun ist
+ +
+
+
+
+
+
+
+
+
+
+
+
10
+
+
+
+
2
+
+
+
+
+
+
+
+
+Messe Fair Handeln: Human Connection beim Hackathon (Stuttgart +26.-28.04.2019)
+ +
+
+
+
+
+
+
+
+
+
+
9
+
+
+
+
1
+
+
+
+
+
+
+
+
In +Nerds We Trust - Open Source - Wir lieben freie +Software!
+ +
+
+
+
+
+
+
+
+
+
+
4
+
+
+
+
1
+
+
+
+
+
+
+
+ +
+
+
+
Fotos +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/backend/snapshots/embeds/babyLovesCat.html b/backend/snapshots/embeds/babyLovesCat.html new file mode 100644 index 000000000..61db8cf72 --- /dev/null +++ b/backend/snapshots/embeds/babyLovesCat.html @@ -0,0 +1,1783 @@ + + + + + + + + + + + + + Baby Loves Cat - YouTube + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ + +
+
+
+
+
+ DE +
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+

+ + + +Wird geladen... + +

+ +
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+ + Du siehst YouTube auf Deutsch. Du kannst diese Einstellung unten ändern. +
+
+ + You're viewing YouTube in German. You can change this preference below. +
+
+
+
+ +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

+ + + + + Baby Loves Cat + + +

+
+
+ + +
+
+ + + + + +
+ +
+
+
+
+
30.160.931 Aufrufe
+
+
+
+
+
+ + + + + +
+
+
+ + + +
+
+
+
+

+ + + +Wird geladen... + +

+ +
+
+
+ +
+ +
+
+
+

+ + + +Wird geladen... + +

+ +
+
+
+

+ Transkript +

+
+ +
+ + + +
+
+ Das interaktive Transkript konnte nicht geladen werden. +
+ + +
+
+ +
+ +
+
+

+ + + +Wird geladen... + +

+ +
+
+ + +
+
+ Die Bewertungsfunktion ist nach Ausleihen des Videos verfügbar. +
+ +
+ +
+
+ Diese Funktion ist gerade nicht verfügbar. Bitte versuche es später noch einmal. +
+
+ + +
+ + +
+ + +
+
+
+
+
Am 16.08.2015 veröffentlicht
+
+

She's incapable of controlling her limbs when her kitty is around. The obsession grows every day.

Ps. That's a sleep sack she's in. Not a starfish outfit. Although I wish I were cool enough to dress my daughter in a starfish outfit.

Jukin Media Verified (Original)
*For licensing / permission to use please contact licensing(at)jukinmediadotcom

+
+
+
    +
  • +

    + Kategorie +

    + +
  • + +
+
+
+
+
+ +
+ + +
+
+
+ + +
+ Kommentare sind für dieses Video deaktiviert. +
+ +
+ + + + +
+
+
+
+ +
+ + +
+
+
+ + + +
+
+ +
+ +
+
+
+ Anzeige +
+
+
+
+ + +
+
+
+
+
+ + + + Wenn Autoplay aktiviert ist, wird die Wiedergabe automatisch mit einem der aktuellen Videovorschläge fortgesetzt. + + + + +
+

+ Nächstes Video +

+ + +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ , um dieses Video zur Playlist "Später ansehen" hinzuzufügen. + +
+
+
+

+ Hinzufügen +

+
+
+

+ + + + Playlists werden geladen... + +

+ +
+
+
+ + + + + + + diff --git a/backend/src/jest/snapshots/embeds/pr960.html b/backend/snapshots/embeds/pr960.html similarity index 100% rename from backend/src/jest/snapshots/embeds/pr960.html rename to backend/snapshots/embeds/pr960.html diff --git a/backend/src/activitypub/ActivityPub.js b/backend/src/activitypub/ActivityPub.js index 01e95d4f4..1794c2d3b 100644 --- a/backend/src/activitypub/ActivityPub.js +++ b/backend/src/activitypub/ActivityPub.js @@ -1,7 +1,9 @@ -import { extractNameFromId, extractDomainFromUrl, signAndSend } from './utils' -import { isPublicAddressed, sendAcceptActivity, sendRejectActivity } from './utils/activity' +// import { extractDomainFromUrl, signAndSend } from './utils' +import { extractNameFromId, signAndSend } from './utils' +import { isPublicAddressed } from './utils/activity' +// import { isPublicAddressed, sendAcceptActivity, sendRejectActivity } from './utils/activity' import request from 'request' -import as from 'activitystrea.ms' +// import as from 'activitystrea.ms' import NitroDataSource from './NitroDataSource' import router from './routes' import Collections from './Collections' @@ -33,71 +35,71 @@ export default class ActivityPub { } } - handleFollowActivity(activity) { - debug(`inside FOLLOW ${activity.actor}`) - const toActorName = extractNameFromId(activity.object) - const fromDomain = extractDomainFromUrl(activity.actor) - const dataSource = this.dataSource + // handleFollowActivity(activity) { + // debug(`inside FOLLOW ${activity.actor}`) + // const toActorName = extractNameFromId(activity.object) + // const fromDomain = extractDomainFromUrl(activity.actor) + // const dataSource = this.dataSource - return new Promise((resolve, reject) => { - request( - { - url: activity.actor, - headers: { - Accept: 'application/activity+json', - }, - }, - async (err, response, toActorObject) => { - if (err) return reject(err) - // save shared inbox - toActorObject = JSON.parse(toActorObject) - await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox) + // return new Promise((resolve, reject) => { + // request( + // { + // url: activity.actor, + // headers: { + // Accept: 'application/activity+json', + // }, + // }, + // async (err, response, toActorObject) => { + // if (err) return reject(err) + // // save shared inbox + // toActorObject = JSON.parse(toActorObject) + // await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox) - const followersCollectionPage = await this.dataSource.getFollowersCollectionPage( - activity.object, - ) + // const followersCollectionPage = await this.dataSource.getFollowersCollectionPage( + // activity.object, + // ) - const followActivity = as - .follow() - .id(activity.id) - .actor(activity.actor) - .object(activity.object) + // const followActivity = as + // .follow() + // .id(activity.id) + // .actor(activity.actor) + // .object(activity.object) - // add follower if not already in collection - if (followersCollectionPage.orderedItems.includes(activity.actor)) { - debug('follower already in collection!') - debug(`inbox = ${toActorObject.inbox}`) - resolve( - sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), - ) - } else { - followersCollectionPage.orderedItems.push(activity.actor) - } - debug(`toActorObject = ${toActorObject}`) - toActorObject = - typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject - debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`) - debug(`inbox = ${toActorObject.inbox}`) - debug(`outbox = ${toActorObject.outbox}`) - debug(`followers = ${toActorObject.followers}`) - debug(`following = ${toActorObject.following}`) + // // add follower if not already in collection + // if (followersCollectionPage.orderedItems.includes(activity.actor)) { + // debug('follower already in collection!') + // debug(`inbox = ${toActorObject.inbox}`) + // resolve( + // sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + // ) + // } else { + // followersCollectionPage.orderedItems.push(activity.actor) + // } + // debug(`toActorObject = ${toActorObject}`) + // toActorObject = + // typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject + // debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`) + // debug(`inbox = ${toActorObject.inbox}`) + // debug(`outbox = ${toActorObject.outbox}`) + // debug(`followers = ${toActorObject.followers}`) + // debug(`following = ${toActorObject.following}`) - try { - await dataSource.saveFollowersCollectionPage(followersCollectionPage) - debug('follow activity saved') - resolve( - sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), - ) - } catch (e) { - debug('followers update error!', e) - resolve( - sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), - ) - } - }, - ) - }) - } + // try { + // await dataSource.saveFollowersCollectionPage(followersCollectionPage) + // debug('follow activity saved') + // resolve( + // sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + // ) + // } catch (e) { + // debug('followers update error!', e) + // resolve( + // sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox), + // ) + // } + // }, + // ) + // }) + // } handleUndoActivity(activity) { debug('inside UNDO') diff --git a/backend/src/activitypub/routes/inbox.js b/backend/src/activitypub/routes/inbox.js index b31b89ed4..18f1890af 100644 --- a/backend/src/activitypub/routes/inbox.js +++ b/backend/src/activitypub/routes/inbox.js @@ -18,9 +18,9 @@ router.post('/', async function(req, res, next) { case 'Undo': await activityPub.handleUndoActivity(req.body).catch(next) break - case 'Follow': - await activityPub.handleFollowActivity(req.body).catch(next) - break + // case 'Follow': + // await activityPub.handleFollowActivity(req.body).catch(next) + // break case 'Delete': await activityPub.handleDeleteActivity(req.body).catch(next) break diff --git a/backend/src/activitypub/routes/index.js b/backend/src/activitypub/routes/index.js index c7d31f1c4..fb4037004 100644 --- a/backend/src/activitypub/routes/index.js +++ b/backend/src/activitypub/routes/index.js @@ -1,27 +1,29 @@ import user from './user' import inbox from './inbox' -import webFinger from './webFinger' import express from 'express' import cors from 'cors' import verify from './verify' -const router = express.Router() - -router.use('/.well-known/webFinger', cors(), express.urlencoded({ extended: true }), webFinger) -router.use( - '/activitypub/users', - cors(), - express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }), - express.urlencoded({ extended: true }), - user, -) -router.use( - '/activitypub/inbox', - cors(), - express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }), - express.urlencoded({ extended: true }), - verify, - inbox, -) - -export default router +export default function() { + const router = express.Router() + router.use( + '/activitypub/users', + cors(), + express.json({ + type: ['application/activity+json', 'application/ld+json', 'application/json'], + }), + express.urlencoded({ extended: true }), + user, + ) + router.use( + '/activitypub/inbox', + cors(), + express.json({ + type: ['application/activity+json', 'application/ld+json', 'application/json'], + }), + express.urlencoded({ extended: true }), + verify, + inbox, + ) + return router +} diff --git a/backend/src/activitypub/routes/user.js b/backend/src/activitypub/routes/user.js index 9dc9b5071..84486fd19 100644 --- a/backend/src/activitypub/routes/user.js +++ b/backend/src/activitypub/routes/user.js @@ -56,9 +56,9 @@ router.post('/:name/inbox', verify, async function(req, res, next) { case 'Undo': await activityPub.handleUndoActivity(req.body).catch(next) break - case 'Follow': - await activityPub.handleFollowActivity(req.body).catch(next) - break + // case 'Follow': + // await activityPub.handleFollowActivity(req.body).catch(next) + // break case 'Delete': await activityPub.handleDeleteActivity(req.body).catch(next) break diff --git a/backend/src/activitypub/routes/webFinger.js b/backend/src/activitypub/routes/webFinger.js deleted file mode 100644 index 7d52c69cd..000000000 --- a/backend/src/activitypub/routes/webFinger.js +++ /dev/null @@ -1,43 +0,0 @@ -import express from 'express' -import { createWebFinger } from '../utils/actor' -import gql from 'graphql-tag' - -const router = express.Router() - -router.get('/', async function(req, res) { - const resource = req.query.resource - if (!resource || !resource.includes('acct:')) { - return res - .status(400) - .send( - 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.', - ) - } else { - const nameAndDomain = resource.replace('acct:', '') - const name = nameAndDomain.split('@')[0] - - let result - try { - result = await req.app.get('ap').dataSource.client.query({ - query: gql` - query { - User(slug: "${name}") { - slug - } - } - `, - }) - } catch (error) { - return res.status(500).json({ error }) - } - - if (result.data && result.data.User.length > 0) { - const webFinger = createWebFinger(name) - return res.contentType('application/jrd+json').json(webFinger) - } else { - return res.status(404).json({ error: `No record found for ${nameAndDomain}.` }) - } - } -}) - -export default router diff --git a/backend/src/activitypub/routes/webfinger.js b/backend/src/activitypub/routes/webfinger.js new file mode 100644 index 000000000..c2cb96a6d --- /dev/null +++ b/backend/src/activitypub/routes/webfinger.js @@ -0,0 +1,59 @@ +import express from 'express' +import CONFIG from '../../config/' +import cors from 'cors' + +const debug = require('debug')('ea:webfinger') +const regex = /acct:([a-z0-9_-]*)@([a-z0-9_-]*)/ + +const createWebFinger = name => { + const { host } = new URL(CONFIG.CLIENT_URI) + return { + subject: `acct:${name}@${host}`, + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: `${CONFIG.CLIENT_URI}/activitypub/users/${name}`, + }, + ], + } +} + +export async function handler(req, res) { + const { resource = '' } = req.query + // eslint-disable-next-line no-unused-vars + const [_, name, domain] = resource.match(regex) || [] + if (!(name && domain)) + return res.status(400).json({ + error: 'Query parameter "?resource=acct:@" is missing.', + }) + + const session = req.app.get('driver').session() + try { + const [slug] = await session.readTransaction(async t => { + const result = await t.run('MATCH (u:User {slug: $slug}) RETURN u.slug AS slug', { + slug: name, + }) + return result.records.map(record => record.get('slug')) + }) + if (!slug) + return res.status(404).json({ + error: `No record found for "${name}@${domain}".`, + }) + const webFinger = createWebFinger(name) + return res.contentType('application/jrd+json').json(webFinger) + } catch (error) { + debug(error) + return res.status(500).json({ + error: 'Something went terribly wrong. Please contact support@human-connection.org', + }) + } finally { + session.close() + } +} + +export default function() { + const router = express.Router() + router.use('/webfinger', cors(), express.urlencoded({ extended: true }), handler) + return router +} diff --git a/backend/src/activitypub/routes/webfinger.spec.js b/backend/src/activitypub/routes/webfinger.spec.js new file mode 100644 index 000000000..06ca4577d --- /dev/null +++ b/backend/src/activitypub/routes/webfinger.spec.js @@ -0,0 +1,113 @@ +import { handler } from './webfinger' +import Factory from '../../factories' +import { getDriver } from '../../db/neo4j' + +let resource, res, json, status, contentType + +const factory = Factory() +const driver = getDriver() + +const request = () => { + json = jest.fn() + status = jest.fn(() => ({ json })) + contentType = jest.fn(() => ({ json })) + res = { status, contentType } + const req = { + app: { + get: key => { + return { + driver, + }[key] + }, + }, + query: { + resource, + }, + } + return handler(req, res) +} + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('webfinger', () => { + describe('no ressource', () => { + beforeEach(() => { + resource = undefined + }) + + it('sends HTTP 400', async () => { + await request() + expect(status).toHaveBeenCalledWith(400) + expect(json).toHaveBeenCalledWith({ + error: 'Query parameter "?resource=acct:@" is missing.', + }) + }) + }) + + describe('?resource query param', () => { + describe('is missing acct:', () => { + beforeEach(() => { + resource = 'some-user@domain' + }) + + it('sends HTTP 400', async () => { + await request() + expect(status).toHaveBeenCalledWith(400) + expect(json).toHaveBeenCalledWith({ + error: 'Query parameter "?resource=acct:@" is missing.', + }) + }) + }) + + describe('has no domain', () => { + beforeEach(() => { + resource = 'acct:some-user@' + }) + + it('sends HTTP 400', async () => { + await request() + expect(status).toHaveBeenCalledWith(400) + expect(json).toHaveBeenCalledWith({ + error: 'Query parameter "?resource=acct:@" is missing.', + }) + }) + }) + + describe('with acct:', () => { + beforeEach(() => { + resource = 'acct:some-user@domain' + }) + + it('returns error as json', async () => { + await request() + expect(status).toHaveBeenCalledWith(404) + expect(json).toHaveBeenCalledWith({ + error: 'No record found for "some-user@domain".', + }) + }) + + describe('given a user for acct', () => { + beforeEach(async () => { + await factory.create('User', { slug: 'some-user' }) + }) + + it('returns user object', async () => { + await request() + expect(contentType).toHaveBeenCalledWith('application/jrd+json') + expect(json).toHaveBeenCalledWith({ + links: [ + { + href: 'http://localhost:3000/activitypub/users/some-user', + rel: 'self', + type: 'application/activity+json', + }, + ], + subject: 'acct:some-user@localhost:3000', + }) + }) + }) + }) + }) +}) diff --git a/backend/src/activitypub/utils/activity.js b/backend/src/activitypub/utils/activity.js index baf13e1bf..2199361d8 100644 --- a/backend/src/activitypub/utils/activity.js +++ b/backend/src/activitypub/utils/activity.js @@ -1,10 +1,11 @@ import { activityPub } from '../ActivityPub' -import { signAndSend, throwErrorIfApolloErrorOccurred } from './index' +import { throwErrorIfApolloErrorOccurred } from './index' +// import { signAndSend, throwErrorIfApolloErrorOccurred } from './index' import crypto from 'crypto' -import as from 'activitystrea.ms' +// import as from 'activitystrea.ms' import gql from 'graphql-tag' -const debug = require('debug')('ea:utils:activity') +// const debug = require('debug')('ea:utils:activity') export function createNoteObject(text, name, id, published) { const createUuid = crypto.randomBytes(16).toString('hex') @@ -62,41 +63,41 @@ export async function getActorId(name) { } } -export function sendAcceptActivity(theBody, name, targetDomain, url) { - as.accept() - .id( - `${activityPub.endpoint}/activitypub/users/${name}/status/` + - crypto.randomBytes(16).toString('hex'), - ) - .actor(`${activityPub.endpoint}/activitypub/users/${name}`) - .object(theBody) - .prettyWrite((err, doc) => { - if (!err) { - return signAndSend(doc, name, targetDomain, url) - } else { - debug(`error serializing Accept object: ${err}`) - throw new Error('error serializing Accept object') - } - }) -} +// export function sendAcceptActivity(theBody, name, targetDomain, url) { +// as.accept() +// .id( +// `${activityPub.endpoint}/activitypub/users/${name}/status/` + +// crypto.randomBytes(16).toString('hex'), +// ) +// .actor(`${activityPub.endpoint}/activitypub/users/${name}`) +// .object(theBody) +// .prettyWrite((err, doc) => { +// if (!err) { +// return signAndSend(doc, name, targetDomain, url) +// } else { +// debug(`error serializing Accept object: ${err}`) +// throw new Error('error serializing Accept object') +// } +// }) +// } -export function sendRejectActivity(theBody, name, targetDomain, url) { - as.reject() - .id( - `${activityPub.endpoint}/activitypub/users/${name}/status/` + - crypto.randomBytes(16).toString('hex'), - ) - .actor(`${activityPub.endpoint}/activitypub/users/${name}`) - .object(theBody) - .prettyWrite((err, doc) => { - if (!err) { - return signAndSend(doc, name, targetDomain, url) - } else { - debug(`error serializing Accept object: ${err}`) - throw new Error('error serializing Accept object') - } - }) -} +// export function sendRejectActivity(theBody, name, targetDomain, url) { +// as.reject() +// .id( +// `${activityPub.endpoint}/activitypub/users/${name}/status/` + +// crypto.randomBytes(16).toString('hex'), +// ) +// .actor(`${activityPub.endpoint}/activitypub/users/${name}`) +// .object(theBody) +// .prettyWrite((err, doc) => { +// if (!err) { +// return signAndSend(doc, name, targetDomain, url) +// } else { +// debug(`error serializing Accept object: ${err}`) +// throw new Error('error serializing Accept object') +// } +// }) +// } export function isPublicAddressed(postObject) { if (typeof postObject.to === 'string') { diff --git a/backend/src/activitypub/utils/actor.js b/backend/src/activitypub/utils/actor.js index a08065778..e07397bdc 100644 --- a/backend/src/activitypub/utils/actor.js +++ b/backend/src/activitypub/utils/actor.js @@ -22,17 +22,3 @@ export function createActor(name, pubkey) { }, } } - -export function createWebFinger(name) { - const { host } = new URL(activityPub.endpoint) - return { - subject: `acct:${name}@${host}`, - links: [ - { - rel: 'self', - type: 'application/activity+json', - href: `${activityPub.endpoint}/activitypub/users/${name}`, - }, - ], - } -} diff --git a/backend/src/bootstrap/directives.js b/backend/src/bootstrap/directives.js deleted file mode 100644 index 93a7574fb..000000000 --- a/backend/src/bootstrap/directives.js +++ /dev/null @@ -1,12 +0,0 @@ -import { - GraphQLLowerCaseDirective, - GraphQLTrimDirective, - GraphQLDefaultToDirective, -} from 'graphql-custom-directives' - -export default function applyDirectives(augmentedSchema) { - const directives = [GraphQLLowerCaseDirective, GraphQLTrimDirective, GraphQLDefaultToDirective] - augmentedSchema._directives.push.apply(augmentedSchema._directives, directives) - - return augmentedSchema -} diff --git a/backend/src/bootstrap/neo4j.js b/backend/src/bootstrap/neo4j.js deleted file mode 100644 index f9e3a997d..000000000 --- a/backend/src/bootstrap/neo4j.js +++ /dev/null @@ -1,26 +0,0 @@ -import { v1 as neo4j } from 'neo4j-driver' -import CONFIG from './../config' -import setupNeode from './neode' - -let driver - -export function getDriver(options = {}) { - const { - uri = CONFIG.NEO4J_URI, - username = CONFIG.NEO4J_USERNAME, - password = CONFIG.NEO4J_PASSWORD, - } = options - if (!driver) { - driver = neo4j.driver(uri, neo4j.auth.basic(username, password)) - } - return driver -} - -let neodeInstance -export function neode() { - if (!neodeInstance) { - const { NEO4J_URI: uri, NEO4J_USERNAME: username, NEO4J_PASSWORD: password } = CONFIG - neodeInstance = setupNeode({ uri, username, password }) - } - return neodeInstance -} diff --git a/backend/src/bootstrap/neode.js b/backend/src/bootstrap/neode.js deleted file mode 100644 index 65a2074be..000000000 --- a/backend/src/bootstrap/neode.js +++ /dev/null @@ -1,9 +0,0 @@ -import Neode from 'neode' -import models from '../models' - -export default function setupNeode(options) { - const { uri, username, password } = options - const neodeInstance = new Neode(uri, username, password) - neodeInstance.with(models) - return neodeInstance -} diff --git a/backend/src/bootstrap/scalars.js b/backend/src/bootstrap/scalars.js deleted file mode 100644 index eb6d3739b..000000000 --- a/backend/src/bootstrap/scalars.js +++ /dev/null @@ -1,9 +0,0 @@ -import { GraphQLDate, GraphQLTime, GraphQLDateTime } from 'graphql-iso-date' - -export default function applyScalars(augmentedSchema) { - augmentedSchema._typeMap.Date = GraphQLDate - augmentedSchema._typeMap.Time = GraphQLTime - augmentedSchema._typeMap.DateTime = GraphQLDateTime - - return augmentedSchema -} diff --git a/backend/src/config/index.js b/backend/src/config/index.js index 3709b5cc3..2f8d0ed22 100644 --- a/backend/src/config/index.js +++ b/backend/src/config/index.js @@ -1,6 +1,8 @@ import dotenv from 'dotenv' - -dotenv.config() +if (require.resolve) { + // are we in a nodejs environment? + dotenv.config({ path: require.resolve('../../.env') }) +} const { MAPBOX_TOKEN, @@ -16,12 +18,25 @@ const { NEO4J_URI = 'bolt://localhost:7687', NEO4J_USERNAME = 'neo4j', NEO4J_PASSWORD = 'neo4j', - GRAPHQL_PORT = 4000, CLIENT_URI = 'http://localhost:3000', GRAPHQL_URI = 'http://localhost:4000', } = process.env -export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE } +export const requiredConfigs = { + MAPBOX_TOKEN, + JWT_SECRET, + PRIVATE_KEY_PASSPHRASE, +} + +if (require.resolve) { + // are we in a nodejs environment? + Object.entries(requiredConfigs).map(entry => { + if (!entry[1]) { + throw new Error(`ERROR: "${entry[0]}" env variable is missing.`) + } + }) +} + export const smtpConfigs = { SMTP_HOST, SMTP_PORT, @@ -30,7 +45,11 @@ export const smtpConfigs = { SMTP_PASSWORD, } export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD } -export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI } +export const serverConfigs = { + CLIENT_URI, + GRAPHQL_URI, + PUBLIC_REGISTRATION: process.env.PUBLIC_REGISTRATION === 'true', +} export const developmentConfigs = { DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG, diff --git a/backend/src/seed/reset-db.js b/backend/src/db/clean.js similarity index 91% rename from backend/src/seed/reset-db.js rename to backend/src/db/clean.js index 125d135d8..cbb1412e2 100644 --- a/backend/src/seed/reset-db.js +++ b/backend/src/db/clean.js @@ -1,4 +1,4 @@ -import { cleanDatabase } from './factories' +import { cleanDatabase } from '../factories' if (process.env.NODE_ENV === 'production') { throw new Error(`You cannot clean the database in production environment!`) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js new file mode 100644 index 000000000..8bc73b511 --- /dev/null +++ b/backend/src/db/migrate/store.js @@ -0,0 +1,97 @@ +import { getDriver, getNeode } from '../../db/neo4j' + +class Store { + async init(next) { + const neode = getNeode() + const { driver } = neode + const session = driver.session() + // eslint-disable-next-line no-console + const writeTxResultPromise = session.writeTransaction(async txc => { + await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices + return Promise.all( + [ + 'CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"])', + 'CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"])', + ].map(statement => txc.run(statement)), + ) + }) + try { + await writeTxResultPromise + await getNeode().schema.install() + // eslint-disable-next-line no-console + console.log('Successfully created database indices and constraints!') + next() + } catch (error) { + console.log(error) // eslint-disable-line no-console + next(error, null) + } finally { + session.close() + driver.close() + } + } + + async load(next) { + const driver = getDriver() + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const result = await txc.run( + 'MATCH (migration:Migration) RETURN migration {.*} ORDER BY migration.timestamp DESC', + ) + return result.records.map(r => r.get('migration')) + }) + try { + const migrations = await readTxResultPromise + if (migrations.length <= 0) { + // eslint-disable-next-line no-console + console.log( + "No migrations found in database. If it's the first time you run migrations, then this is normal.", + ) + return next(null, {}) + } + const [{ title: lastRun }] = migrations + next(null, { lastRun, migrations }) + } catch (error) { + console.log(error) // eslint-disable-line no-console + next(error) + } finally { + session.close() + } + } + + async save(set, next) { + const driver = getDriver() + const session = driver.session() + const { migrations } = set + const writeTxResultPromise = session.writeTransaction(txc => { + return Promise.all( + migrations.map(async migration => { + const { title, description, timestamp } = migration + const properties = { title, description, timestamp } + const migrationResult = await txc.run( + ` + MERGE (migration:Migration { title: $properties.title }) + ON MATCH SET + migration += $properties + ON CREATE SET + migration += $properties, + migration.migratedAt = toString(datetime()) + `, + { properties }, + ) + return migrationResult + }), + ) + }) + try { + await writeTxResultPromise + next() + } catch (error) { + console.log(error) // eslint-disable-line no-console + next(error) + } finally { + session.close() + } + } +} + +module.exports = Store diff --git a/backend/src/db/migrate/template.js b/backend/src/db/migrate/template.js new file mode 100644 index 000000000..b8511e9bb --- /dev/null +++ b/backend/src/db/migrate/template.js @@ -0,0 +1,31 @@ +import { getDriver } from '../../db/neo4j' + +export const description = '' + +export function up(next) { + const driver = getDriver() + const session = driver.session() + try { + // Implement your migration here. + next() + } catch (err) { + next(err) + } finally { + session.close() + driver.close() + } +} + +export function down(next) { + const driver = getDriver() + const session = driver.session() + try { + // Rollback your migration here. + next() + } catch (err) { + next(err) + } finally { + session.close() + driver.close() + } +} diff --git a/backend/src/db/migrations/20200123150105-merge_duplicate_user_accounts.js b/backend/src/db/migrations/20200123150105-merge_duplicate_user_accounts.js new file mode 100644 index 000000000..ec38befc5 --- /dev/null +++ b/backend/src/db/migrations/20200123150105-merge_duplicate_user_accounts.js @@ -0,0 +1,84 @@ +import { throwError, concat } from 'rxjs' +import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators' +import { getDriver } from '../neo4j' +import normalizeEmail from '../../schema/resolvers//helpers/normalizeEmail' + +export const description = ` + This migration merges duplicate :User and :EmailAddress nodes. It became + necessary after we implemented the email normalization but forgot to migrate + the existing data. Some (40) users decided to just register with a new account + but the same email address. On signup our backend would normalize the email, + which is good, but would also keep the existing unnormalized email address. + + This led to about 40 duplicate user and email address nodes in our database. +` +export function up(next) { + const driver = getDriver() + const rxSession = driver.rxSession() + rxSession + .beginTransaction() + .pipe( + flatMap(txc => + concat( + txc + .run('MATCH (email:EmailAddress) RETURN email {.email}') + .records() + .pipe( + map(record => { + const { email } = record.get('email') + const normalizedEmail = normalizeEmail(email) + return { email, normalizedEmail } + }), + filter(({ email, normalizedEmail }) => email !== normalizedEmail), + mergeMap(({ email, normalizedEmail }) => { + return txc + .run( + ` + MATCH (oldUser:User)-[:PRIMARY_EMAIL]->(oldEmail:EmailAddress {email: $email}), (oldUser)-[previousRelationship]-(oldEmail) + MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail}) + DELETE previousRelationship + WITH oldUser, oldEmail, user, email + CALL apoc.refactor.mergeNodes([user, oldUser], { properties: 'discard', mergeRels: true }) YIELD node as mergedUser + CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: 'discard', mergeRels: true }) YIELD node as mergedEmail + RETURN user {.*}, email {.*} + `, + { email, normalizedEmail }, + ) + .records() + .pipe( + map(r => ({ + oldEmail: email, + email: r.get('email'), + user: r.get('user'), + })), + ) + }), + ), + txc.commit(), + ).pipe(catchError(err => txc.rollback().pipe(throwError(err)))), + ), + ) + .subscribe({ + next: ({ user, email, oldUser, oldEmail }) => + // eslint-disable-next-line no-console + console.log(` + Merged: + ============================= + userId: ${user.id} + email: ${oldEmail} => ${email.email} + ============================= + `), + complete: () => { + // eslint-disable-next-line no-console + console.log('Merging of duplicate users completed') + next() + }, + error: error => { + next(new Error(error), null) + }, + }) +} + +export function down(next) { + next(new Error('Irreversible migration')) +} diff --git a/backend/src/db/migrations/20200123150110-merge_duplicate_location_nodes.js b/backend/src/db/migrations/20200123150110-merge_duplicate_location_nodes.js new file mode 100644 index 000000000..b2d6b260f --- /dev/null +++ b/backend/src/db/migrations/20200123150110-merge_duplicate_location_nodes.js @@ -0,0 +1,77 @@ +import { throwError, concat } from 'rxjs' +import { flatMap, mergeMap, map, catchError } from 'rxjs/operators' +import { getDriver } from '../neo4j' + +export const description = ` + This migration merges duplicate :Location nodes. It became + necessary after we realized that we had not set up constraints for Location.id in production. +` +export function up(next) { + const driver = getDriver() + const rxSession = driver.rxSession() + rxSession + .beginTransaction() + .pipe( + flatMap(transaction => + concat( + transaction + .run( + ` + MATCH (location:Location) + RETURN location {.id} + `, + ) + .records() + .pipe( + map(record => { + const { id: locationId } = record.get('location') + return { locationId } + }), + mergeMap(({ locationId }) => { + return transaction + .run( + ` + MATCH(location:Location {id: $locationId}), (location2:Location {id: $locationId}) + WHERE location.id = location2.id AND id(location) < id(location2) + CALL apoc.refactor.mergeNodes([location, location2], { properties: 'combine', mergeRels: true }) YIELD node as updatedLocation + RETURN location {.*},updatedLocation {.*} + `, + { locationId }, + ) + .records() + .pipe( + map(record => ({ + location: record.get('location'), + updatedLocation: record.get('updatedLocation'), + })), + ) + }), + ), + transaction.commit(), + ).pipe(catchError(error => transaction.rollback().pipe(throwError(error)))), + ), + ) + .subscribe({ + next: ({ updatedLocation, location }) => + // eslint-disable-next-line no-console + console.log(` + Merged: + ============================= + locationId: ${location.id} + updatedLocation: ${location.id} => ${updatedLocation.id} + ============================= + `), + complete: () => { + // eslint-disable-next-line no-console + console.log('Merging of duplicate locations completed') + next() + }, + error: error => { + next(new Error(error), null) + }, + }) +} + +export function down(next) { + next(new Error('Irreversible migration')) +} diff --git a/backend/src/db/neo4j.js b/backend/src/db/neo4j.js new file mode 100644 index 000000000..6d46a0279 --- /dev/null +++ b/backend/src/db/neo4j.js @@ -0,0 +1,29 @@ +import neo4j from 'neo4j-driver' +import CONFIG from './../config' +import Neode from 'neode' +import models from '../models' + +let driver +const defaultOptions = { + uri: CONFIG.NEO4J_URI, + username: CONFIG.NEO4J_USERNAME, + password: CONFIG.NEO4J_PASSWORD, +} + +export function getDriver(options = {}) { + const { uri, username, password } = { ...defaultOptions, ...options } + if (!driver) { + driver = neo4j.driver(uri, neo4j.auth.basic(username, password)) + } + return driver +} + +let neodeInstance +export function getNeode(options = {}) { + if (!neodeInstance) { + const { uri, username, password } = { ...defaultOptions, ...options } + neodeInstance = new Neode(uri, username, password).with(models) + return neodeInstance + } + return neodeInstance +} diff --git a/backend/src/seed/seed-db.js b/backend/src/db/seed.js similarity index 56% rename from backend/src/seed/seed-db.js rename to backend/src/db/seed.js index 48a739529..ba7ace90b 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/db/seed.js @@ -1,9 +1,12 @@ import faker from 'faker' +import sample from 'lodash/sample' import { createTestClient } from 'apollo-server-testing' import createServer from '../server' -import Factory from './factories' -import { neode as getNeode, getDriver } from '../bootstrap/neo4j' -import { gql } from '../jest/helpers' +import Factory from '../factories' +import { getNeode, getDriver } from '../db/neo4j' +import { gql } from '../helpers/jest' + +const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] /* eslint-disable no-multi-spaces */ ;(async function() { @@ -24,10 +27,8 @@ import { gql } from '../jest/helpers' }) const { mutate } = createTestClient(server) - const f = Factory() - const [Hamburg, Berlin, Germany, Paris, France] = await Promise.all([ - f.create('Location', { + factory.create('Location', { id: 'region.5127278006398860', name: 'Hamburg', type: 'region', @@ -41,8 +42,9 @@ import { gql } from '../jest/helpers' nameDE: 'Hamburg', nameNL: 'Hamburg', namePL: 'Hamburg', + nameRU: 'Гамбург', }), - f.create('Location', { + factory.create('Location', { id: 'region.14880313158564380', type: 'region', name: 'Berlin', @@ -56,8 +58,9 @@ import { gql } from '../jest/helpers' nameDE: 'Berlin', nameNL: 'Berlijn', namePL: 'Berlin', + nameRU: 'Берлин', }), - f.create('Location', { + factory.create('Location', { id: 'country.10743216036480410', name: 'Germany', type: 'country', @@ -69,8 +72,9 @@ import { gql } from '../jest/helpers' nameFR: 'Allemagne', nameIT: 'Germania', nameEN: 'Germany', + nameRU: 'Германия', }), - f.create('Location', { + factory.create('Location', { id: 'region.9397217726497330', name: 'Paris', type: 'region', @@ -84,8 +88,9 @@ import { gql } from '../jest/helpers' nameDE: 'Paris', nameNL: 'Parijs', namePL: 'Paryż', + nameRU: 'Париж', }), - f.create('Location', { + factory.create('Location', { id: 'country.9759535382641660', name: 'France', type: 'country', @@ -97,6 +102,7 @@ import { gql } from '../jest/helpers' nameFR: 'France', nameIT: 'Francia', nameEN: 'France', + nameRU: 'Франция', }), ]) await Promise.all([ @@ -106,27 +112,27 @@ import { gql } from '../jest/helpers' ]) const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([ - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_racoon', icon: '/img/badges/indiegogo_en_racoon.svg', }), - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_rabbit', icon: '/img/badges/indiegogo_en_rabbit.svg', }), - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_wolf', icon: '/img/badges/indiegogo_en_wolf.svg', }), - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_bear', icon: '/img/badges/indiegogo_en_bear.svg', }), - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_turtle', icon: '/img/badges/indiegogo_en_turtle.svg', }), - f.create('Badge', { + factory.create('Badge', { id: 'indiegogo_en_rhino', icon: '/img/badges/indiegogo_en_rhino.svg', }), @@ -141,49 +147,49 @@ import { gql } from '../jest/helpers' louie, dagobert, ] = await Promise.all([ - f.create('User', { + factory.create('User', { id: 'u1', name: 'Peter Lustig', slug: 'peter-lustig', role: 'admin', email: 'admin@example.org', }), - f.create('User', { + factory.create('User', { id: 'u2', name: 'Bob der Baumeister', slug: 'bob-der-baumeister', role: 'moderator', email: 'moderator@example.org', }), - f.create('User', { + factory.create('User', { id: 'u3', name: 'Jenny Rostock', slug: 'jenny-rostock', role: 'user', email: 'user@example.org', }), - f.create('User', { + factory.create('User', { id: 'u4', name: 'Huey', slug: 'huey', role: 'user', email: 'huey@example.org', }), - f.create('User', { + factory.create('User', { id: 'u5', name: 'Dewey', slug: 'dewey', role: 'user', email: 'dewey@example.org', }), - f.create('User', { + factory.create('User', { id: 'u6', name: 'Louie', slug: 'louie', role: 'user', email: 'louie@example.org', }), - f.create('User', { + factory.create('User', { id: 'u7', name: 'Dagobert', slug: 'dagobert', @@ -220,103 +226,107 @@ import { gql } from '../jest/helpers' dewey.relateTo(huey, 'following'), louie.relateTo(jennyRostock, 'following'), + huey.relateTo(dagobert, 'muted'), + dewey.relateTo(dagobert, 'muted'), + louie.relateTo(dagobert, 'muted'), + dagobert.relateTo(huey, 'blocked'), dagobert.relateTo(dewey, 'blocked'), dagobert.relateTo(louie, 'blocked'), ]) await Promise.all([ - f.create('Category', { + factory.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'just-for-fun', icon: 'smile', }), - f.create('Category', { + factory.create('Category', { id: 'cat2', name: 'Happiness & Values', slug: 'happiness-values', icon: 'heart-o', }), - f.create('Category', { + factory.create('Category', { id: 'cat3', name: 'Health & Wellbeing', slug: 'health-wellbeing', icon: 'medkit', }), - f.create('Category', { + factory.create('Category', { id: 'cat4', name: 'Environment & Nature', slug: 'environment-nature', icon: 'tree', }), - f.create('Category', { + factory.create('Category', { id: 'cat5', name: 'Animal Protection', slug: 'animal-protection', icon: 'paw', }), - f.create('Category', { + factory.create('Category', { id: 'cat6', name: 'Human Rights & Justice', slug: 'human-rights-justice', icon: 'balance-scale', }), - f.create('Category', { + factory.create('Category', { id: 'cat7', name: 'Education & Sciences', slug: 'education-sciences', icon: 'graduation-cap', }), - f.create('Category', { + factory.create('Category', { id: 'cat8', name: 'Cooperation & Development', slug: 'cooperation-development', icon: 'users', }), - f.create('Category', { + factory.create('Category', { id: 'cat9', name: 'Democracy & Politics', slug: 'democracy-politics', icon: 'university', }), - f.create('Category', { + factory.create('Category', { id: 'cat10', name: 'Economy & Finances', slug: 'economy-finances', icon: 'money', }), - f.create('Category', { + factory.create('Category', { id: 'cat11', name: 'Energy & Technology', slug: 'energy-technology', icon: 'flash', }), - f.create('Category', { + factory.create('Category', { id: 'cat12', name: 'IT, Internet & Data Privacy', slug: 'it-internet-data-privacy', icon: 'mouse-pointer', }), - f.create('Category', { + factory.create('Category', { id: 'cat13', name: 'Art, Culture & Sport', slug: 'art-culture-sport', icon: 'paint-brush', }), - f.create('Category', { + factory.create('Category', { id: 'cat14', name: 'Freedom of Speech', slug: 'freedom-of-speech', icon: 'bullhorn', }), - f.create('Category', { + factory.create('Category', { id: 'cat15', name: 'Consumption & Sustainability', slug: 'consumption-sustainability', icon: 'shopping-cart', }), - f.create('Category', { + factory.create('Category', { id: 'cat16', name: 'Global Peace & Nonviolence', slug: 'global-peace-nonviolence', @@ -325,16 +335,16 @@ import { gql } from '../jest/helpers' ]) const [environment, nature, democracy, freedom] = await Promise.all([ - f.create('Tag', { + factory.create('Tag', { id: 'Environment', }), - f.create('Tag', { + factory.create('Tag', { id: 'Nature', }), - f.create('Tag', { + factory.create('Tag', { id: 'Democracy', }), - f.create('Tag', { + factory.create('Tag', { id: 'Freedom', }), ]) @@ -343,66 +353,84 @@ import { gql } from '../jest/helpers' factory.create('Post', { author: peterLustig, id: 'p0', - image: faker.image.unsplash.food(), + language: sample(languages), + image: faker.image.unsplash.food(300, 169), categoryIds: ['cat16'], + imageBlurred: true, + imageAspectRatio: 300 / 169, }), factory.create('Post', { author: bobDerBaumeister, id: 'p1', - image: faker.image.unsplash.technology(), + language: sample(languages), + image: faker.image.unsplash.technology(300, 1500), categoryIds: ['cat1'], + imageAspectRatio: 300 / 1500, }), factory.create('Post', { author: huey, id: 'p3', + language: sample(languages), categoryIds: ['cat3'], }), factory.create('Post', { author: dewey, id: 'p4', + language: sample(languages), categoryIds: ['cat4'], }), factory.create('Post', { author: louie, id: 'p5', + language: sample(languages), categoryIds: ['cat5'], }), factory.create('Post', { authorId: 'u1', id: 'p6', - image: faker.image.unsplash.buildings(), + language: sample(languages), + image: faker.image.unsplash.buildings(300, 857), categoryIds: ['cat6'], + imageAspectRatio: 300 / 857, }), factory.create('Post', { author: huey, id: 'p9', + language: sample(languages), categoryIds: ['cat9'], }), factory.create('Post', { author: dewey, id: 'p10', categoryIds: ['cat10'], + imageBlurred: true, }), factory.create('Post', { author: louie, id: 'p11', - image: faker.image.unsplash.people(), + language: sample(languages), + image: faker.image.unsplash.people(300, 901), categoryIds: ['cat11'], + imageAspectRatio: 300 / 901, }), factory.create('Post', { author: bobDerBaumeister, id: 'p13', + language: sample(languages), categoryIds: ['cat13'], }), factory.create('Post', { author: jennyRostock, id: 'p14', - image: faker.image.unsplash.objects(), + language: sample(languages), + image: faker.image.unsplash.objects(300, 200), categoryIds: ['cat14'], + imageAspectRatio: 300 / 450, }), factory.create('Post', { author: huey, id: 'p15', + language: sample(languages), categoryIds: ['cat15'], }), ]) @@ -413,12 +441,26 @@ import { gql } from '../jest/helpers' const mention2 = 'Hey @jenny-rostock, here is another notification for you!' const hashtag1 = - 'See #NaturphilosophieYoga can really help you!' + 'See #NaturphilosophieYoga, it can really help you!' const hashtagAndMention1 = - 'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)' + 'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)' const createPostMutation = gql` - mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) { - CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) { + mutation( + $id: ID + $title: String! + $content: String! + $categoryIds: [ID] + $imageBlurred: Boolean + $imageAspectRatio: Float + ) { + CreatePost( + id: $id + title: $title + content: $content + categoryIds: $categoryIds + imageBlurred: $imageBlurred + imageAspectRatio: $imageAspectRatio + ) { id } } @@ -432,6 +474,7 @@ import { gql } from '../jest/helpers' title: `Nature Philosophy Yoga`, content: hashtag1, categoryIds: ['cat2'], + imageAspectRatio: 300 / 200, }, }), mutate({ @@ -441,6 +484,7 @@ import { gql } from '../jest/helpers' title: 'This is post #7', content: `${mention1} ${faker.lorem.paragraph()}`, categoryIds: ['cat7'], + imageAspectRatio: 300 / 180, }, }), mutate({ @@ -451,6 +495,7 @@ import { gql } from '../jest/helpers' title: `Quantum Flow Theory explains Quantum Gravity`, content: hashtagAndMention1, categoryIds: ['cat8'], + imageAspectRatio: 300 / 900, }, }), mutate({ @@ -460,6 +505,7 @@ import { gql } from '../jest/helpers' title: 'This is post #12', content: `${mention2} ${faker.lorem.paragraph()}`, categoryIds: ['cat12'], + imageAspectRatio: 300 / 200, }, }), ]) @@ -470,9 +516,9 @@ import { gql } from '../jest/helpers' authenticatedUser = await dewey.toJson() const mentionInComment1 = - 'I heard @jenny-rostock, practice it since 3 years now.' + 'I heard @jenny-rostock has practiced it for 3 years now.' const mentionInComment2 = - 'Did @peter-lustig told you?' + 'Did @peter-lustig tell you?' const createCommentMutation = gql` mutation($id: ID, $postId: ID!, $content: String!) { CreateComment(id: $id, postId: $postId, content: $content) { @@ -507,7 +553,7 @@ import { gql } from '../jest/helpers' ]) authenticatedUser = null - await Promise.all([ + const comments = await Promise.all([ factory.create('Comment', { author: jennyRostock, id: 'c1', @@ -524,7 +570,7 @@ import { gql } from '../jest/helpers' postId: 'p3', }), factory.create('Comment', { - author: bobDerBaumeister, + author: jennyRostock, id: 'c5', postId: 'p3', }), @@ -564,6 +610,7 @@ import { gql } from '../jest/helpers' postId: 'p15', }), ]) + const trollingComment = comments[0] await Promise.all([ democracy.relateTo(p3, 'post'), @@ -627,67 +674,339 @@ import { gql } from '../jest/helpers' louie.relateTo(p10, 'shouted'), ]) - const disableMutation = gql` - mutation($id: ID!) { - disable(id: $id) - } - ` - authenticatedUser = await bobDerBaumeister.toJson() - await Promise.all([ - mutate({ - mutation: disableMutation, - variables: { - id: 'p11', - }, - }), - mutate({ - mutation: disableMutation, - variables: { - id: 'c5', - }, - }), + const reports = await Promise.all([ + factory.create('Report'), + factory.create('Report'), + factory.create('Report'), + factory.create('Report'), ]) - authenticatedUser = null + const reportAgainstDagobert = reports[0] + const reportAgainstTrollingPost = reports[1] + const reportAgainstTrollingComment = reports[2] + const reportAgainstDewey = reports[3] - const reportMutation = gql` - mutation($id: ID!, $description: String!) { - report(description: $description, id: $id) { - id - } - } - ` - authenticatedUser = await huey.toJson() + // report resource first time await Promise.all([ - mutate({ - mutation: reportMutation, - variables: { - description: "I don't like this comment", - id: 'c1', - }, + reportAgainstDagobert.relateTo(jennyRostock, 'filed', { + resourceId: 'u7', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This user is harassing me with bigoted remarks!', }), - mutate({ - mutation: reportMutation, - variables: { - description: "I don't like this post", - id: 'p1', - }, + reportAgainstDagobert.relateTo(dagobert, 'belongsTo'), + reportAgainstTrollingPost.relateTo(jennyRostock, 'filed', { + resourceId: 'p2', + reasonCategory: 'doxing', + reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!", }), - mutate({ - mutation: reportMutation, - variables: { - description: "I don't like this user", - id: 'u1', - }, + reportAgainstTrollingPost.relateTo(p2, 'belongsTo'), + reportAgainstTrollingComment.relateTo(huey, 'filed', { + resourceId: 'c1', + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', }), + reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'), + reportAgainstDewey.relateTo(dagobert, 'filed', { + resourceId: 'u5', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This user is harassing me!', + }), + reportAgainstDewey.relateTo(dewey, 'belongsTo'), + ]) + + // report resource a second time + await Promise.all([ + reportAgainstDagobert.relateTo(louie, 'filed', { + resourceId: 'u7', + reasonCategory: 'discrimination_etc', + reasonDescription: 'this user is attacking me for who I am!', + }), + reportAgainstDagobert.relateTo(dagobert, 'belongsTo'), + reportAgainstTrollingPost.relateTo(peterLustig, 'filed', { + resourceId: 'p2', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This post is bigoted', + }), + reportAgainstTrollingPost.relateTo(p2, 'belongsTo'), + + reportAgainstTrollingComment.relateTo(bobDerBaumeister, 'filed', { + resourceId: 'c1', + reasonCategory: 'pornographic_content_links', + reasonDescription: 'This comment is porno!!!', + }), + reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'), + ]) + + const disableVariables = { + resourceId: 'undefined-resource', + disable: true, + closed: false, + } + + // review resource first time + await Promise.all([ + reportAgainstDagobert.relateTo(bobDerBaumeister, 'reviewed', { + ...disableVariables, + resourceId: 'u7', + }), + dagobert.update({ disabled: true, updatedAt: new Date().toISOString() }), + reportAgainstTrollingPost.relateTo(peterLustig, 'reviewed', { + ...disableVariables, + resourceId: 'p2', + }), + p2.update({ disabled: true, updatedAt: new Date().toISOString() }), + reportAgainstTrollingComment.relateTo(bobDerBaumeister, 'reviewed', { + ...disableVariables, + resourceId: 'c1', + }), + trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }), + ]) + + // second review of resource and close report + await Promise.all([ + reportAgainstDagobert.relateTo(peterLustig, 'reviewed', { + resourceId: 'u7', + disable: false, + closed: true, + }), + dagobert.update({ disabled: false, updatedAt: new Date().toISOString(), closed: true }), + reportAgainstTrollingPost.relateTo(bobDerBaumeister, 'reviewed', { + resourceId: 'p2', + disable: true, + closed: true, + }), + p2.update({ disabled: true, updatedAt: new Date().toISOString(), closed: true }), + reportAgainstTrollingComment.relateTo(peterLustig, 'reviewed', { + ...disableVariables, + resourceId: 'c1', + disable: true, + closed: true, + }), + trollingComment.update({ disabled: true, updatedAt: new Date().toISOString(), closed: true }), ]) - authenticatedUser = null await Promise.all( [...Array(30).keys()].map(i => { - return f.create('User') + return factory.create('User') }), ) + await Promise.all( + [...Array(30).keys()].map(() => { + return factory.create('Post', { + author: jennyRostock, + image: faker.image.unsplash.objects(), + }) + }), + ) + + await Promise.all( + [...Array(6).keys()].map(() => { + return factory.create('Comment', { + author: jennyRostock, + postId: 'p2', + }) + }), + ) + + await Promise.all( + [...Array(4).keys()].map(() => { + return factory.create('Comment', { + author: jennyRostock, + postId: 'p15', + }) + }), + ) + + await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('Comment', { + author: jennyRostock, + postId: 'p4', + }) + }), + ) + + await Promise.all( + [...Array(21).keys()].map(() => { + return factory.create('Post', { + author: peterLustig, + image: faker.image.unsplash.buildings(), + }) + }), + ) + + await Promise.all( + [...Array(3).keys()].map(() => { + return factory.create('Comment', { + author: peterLustig, + postId: 'p4', + }) + }), + ) + + await Promise.all( + [...Array(5).keys()].map(() => { + return factory.create('Comment', { + author: peterLustig, + postId: 'p14', + }) + }), + ) + + await Promise.all( + [...Array(6).keys()].map(() => { + return factory.create('Comment', { + author: peterLustig, + postId: 'p0', + }) + }), + ) + + await Promise.all( + [...Array(11).keys()].map(() => { + return factory.create('Post', { + author: dewey, + image: faker.image.unsplash.food(), + }) + }), + ) + + await Promise.all( + [...Array(7).keys()].map(() => { + return factory.create('Comment', { + author: dewey, + postId: 'p2', + }) + }), + ) + + await Promise.all( + [...Array(5).keys()].map(() => { + return factory.create('Comment', { + author: dewey, + postId: 'p6', + }) + }), + ) + + await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('Comment', { + author: dewey, + postId: 'p9', + }) + }), + ) + + await Promise.all( + [...Array(16).keys()].map(() => { + return factory.create('Post', { + author: louie, + image: faker.image.unsplash.technology(), + }) + }), + ) + + await Promise.all( + [...Array(4).keys()].map(() => { + return factory.create('Comment', { + author: louie, + postId: 'p1', + }) + }), + ) + + await Promise.all( + [...Array(8).keys()].map(() => { + return factory.create('Comment', { + author: louie, + postId: 'p10', + }) + }), + ) + + await Promise.all( + [...Array(5).keys()].map(() => { + return factory.create('Comment', { + author: louie, + postId: 'p13', + }) + }), + ) + + await Promise.all( + [...Array(45).keys()].map(() => { + return factory.create('Post', { + author: bobDerBaumeister, + image: faker.image.unsplash.people(), + }) + }), + ) + + await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('Comment', { + author: bobDerBaumeister, + postId: 'p2', + }) + }), + ) + + await Promise.all( + [...Array(3).keys()].map(() => { + return factory.create('Comment', { + author: bobDerBaumeister, + postId: 'p12', + }) + }), + ) + + await Promise.all( + [...Array(7).keys()].map(() => { + return factory.create('Comment', { + author: bobDerBaumeister, + postId: 'p13', + }) + }), + ) + + await Promise.all( + [...Array(8).keys()].map(() => { + return factory.create('Post', { + author: huey, + image: faker.image.unsplash.nature(), + }) + }), + ) + + await Promise.all( + [...Array(6).keys()].map(() => { + return factory.create('Comment', { + author: huey, + postId: 'p0', + }) + }), + ) + + await Promise.all( + [...Array(8).keys()].map(() => { + return factory.create('Comment', { + author: huey, + postId: 'p13', + }) + }), + ) + + await Promise.all( + [...Array(9).keys()].map(() => { + return factory.create('Comment', { + author: huey, + postId: 'p15', + }) + }), + ) + + await factory.create('Donations') /* eslint-disable-next-line no-console */ console.log('Seeded Data...') process.exit(0) diff --git a/backend/src/seed/factories/badges.js b/backend/src/factories/badges.js similarity index 100% rename from backend/src/seed/factories/badges.js rename to backend/src/factories/badges.js diff --git a/backend/src/seed/factories/categories.js b/backend/src/factories/categories.js similarity index 100% rename from backend/src/seed/factories/categories.js rename to backend/src/factories/categories.js diff --git a/backend/src/seed/factories/comments.js b/backend/src/factories/comments.js similarity index 100% rename from backend/src/seed/factories/comments.js rename to backend/src/factories/comments.js diff --git a/backend/src/seed/factories/emailAddresses.js b/backend/src/factories/donations.js similarity index 54% rename from backend/src/seed/factories/emailAddresses.js rename to backend/src/factories/donations.js index 0212dca53..e22cdb6d7 100644 --- a/backend/src/seed/factories/emailAddresses.js +++ b/backend/src/factories/donations.js @@ -1,17 +1,18 @@ -import faker from 'faker' +import uuid from 'uuid/v4' export default function create() { return { factory: async ({ args, neodeInstance }) => { const defaults = { - email: faker.internet.email(), - verifiedAt: new Date().toISOString(), + id: uuid(), + goal: 15000, + progress: 0, } args = { ...defaults, ...args, } - return neodeInstance.create('EmailAddress', args) + return neodeInstance.create('Donations', args) }, } } diff --git a/backend/src/factories/emailAddresses.js b/backend/src/factories/emailAddresses.js new file mode 100644 index 000000000..41b1fe96c --- /dev/null +++ b/backend/src/factories/emailAddresses.js @@ -0,0 +1,22 @@ +import faker from 'faker' + +export function defaults({ args }) { + const defaults = { + email: faker.internet.email(), + verifiedAt: new Date().toISOString(), + } + args = { + ...defaults, + ...args, + } + return args +} + +export default function create() { + return { + factory: async ({ args, neodeInstance }) => { + args = defaults({ args }) + return neodeInstance.create('EmailAddress', args) + }, + } +} diff --git a/backend/src/factories/index.js b/backend/src/factories/index.js new file mode 100644 index 000000000..c3ab14f64 --- /dev/null +++ b/backend/src/factories/index.js @@ -0,0 +1,63 @@ +import { getDriver, getNeode } from '../db/neo4j' + +const factories = { + Badge: require('./badges.js').default, + User: require('./users.js').default, + Post: require('./posts.js').default, + Comment: require('./comments.js').default, + Category: require('./categories.js').default, + Tag: require('./tags.js').default, + SocialMedia: require('./socialMedia.js').default, + Location: require('./locations.js').default, + EmailAddress: require('./emailAddresses.js').default, + UnverifiedEmailAddress: require('./unverifiedEmailAddresses.js').default, + Donations: require('./donations.js').default, + Report: require('./reports.js').default, +} + +export const cleanDatabase = async (options = {}) => { + const { driver = getDriver() } = options + const session = driver.session() + try { + await session.writeTransaction(transaction => { + return transaction.run( + ` + MATCH (everything) + DETACH DELETE everything + `, + ) + }) + } finally { + session.close() + } +} + +export default function Factory(options = {}) { + const { neo4jDriver = getDriver(), neodeInstance = getNeode() } = options + + const result = { + neo4jDriver, + factories, + lastResponse: null, + neodeInstance, + async create(node, args = {}) { + const { factory } = this.factories[node](args) + this.lastResponse = await factory({ + args, + neodeInstance, + factoryInstance: this, + }) + return this.lastResponse + }, + + async cleanDatabase() { + this.lastResponse = await cleanDatabase({ + driver: this.neo4jDriver, + }) + return this + }, + } + result.create.bind(result) + result.cleanDatabase.bind(result) + return result +} diff --git a/backend/src/seed/factories/locations.js b/backend/src/factories/locations.js similarity index 100% rename from backend/src/seed/factories/locations.js rename to backend/src/factories/locations.js diff --git a/backend/src/seed/factories/posts.js b/backend/src/factories/posts.js similarity index 79% rename from backend/src/seed/factories/posts.js rename to backend/src/factories/posts.js index 3058204a1..3295665b7 100644 --- a/backend/src/seed/factories/posts.js +++ b/backend/src/factories/posts.js @@ -19,11 +19,17 @@ export default function create() { visibility: 'public', deleted: false, categoryIds: [], + imageBlurred: false, + imageAspectRatio: 1.333, + pinned: null, } args = { ...defaults, ...args, } + // Convert false to null + args.pinned = args.pinned || null + args.slug = args.slug || slugify(args.title, { lower: true }) args.contentExcerpt = args.contentExcerpt || args.content @@ -34,7 +40,6 @@ export default function create() { if (categoryIds) categories = await Promise.all(categoryIds.map(id => neodeInstance.find('Category', id))) categories = categories || (await Promise.all([factoryInstance.create('Category')])) - const { tagIds = [] } = args delete args.tags const tags = await Promise.all( @@ -49,9 +54,21 @@ export default function create() { if (author && authorId) throw new Error('You provided both author and authorId') if (authorId) author = await neodeInstance.find('User', authorId) author = author || (await factoryInstance.create('User')) - const post = await neodeInstance.create('Post', args) await post.relateTo(author, 'author') + + if (args.pinned) { + args.pinnedAt = args.pinnedAt || new Date().toISOString() + if (!args.pinnedBy) { + const admin = await factoryInstance.create('User', { + role: 'admin', + updatedAt: new Date().toISOString(), + }) + await admin.relateTo(post, 'pinned') + args.pinnedBy = admin + } + } + await Promise.all(categories.map(c => c.relateTo(post, 'post'))) await Promise.all(tags.map(t => t.relateTo(post, 'post'))) return post diff --git a/backend/src/factories/reports.js b/backend/src/factories/reports.js new file mode 100644 index 000000000..e2d5ec4dc --- /dev/null +++ b/backend/src/factories/reports.js @@ -0,0 +1,7 @@ +export default function create() { + return { + factory: async ({ args, neodeInstance }) => { + return neodeInstance.create('Report', args) + }, + } +} diff --git a/backend/src/seed/factories/socialMedia.js b/backend/src/factories/socialMedia.js similarity index 100% rename from backend/src/seed/factories/socialMedia.js rename to backend/src/factories/socialMedia.js diff --git a/backend/src/seed/factories/tags.js b/backend/src/factories/tags.js similarity index 100% rename from backend/src/seed/factories/tags.js rename to backend/src/factories/tags.js diff --git a/backend/src/factories/unverifiedEmailAddresses.js b/backend/src/factories/unverifiedEmailAddresses.js new file mode 100644 index 000000000..94e32af6e --- /dev/null +++ b/backend/src/factories/unverifiedEmailAddresses.js @@ -0,0 +1,10 @@ +import { defaults } from './emailAddresses.js' + +export default function create() { + return { + factory: async ({ args, neodeInstance }) => { + args = defaults({ args }) + return neodeInstance.create('UnverifiedEmailAddress', args) + }, + } +} diff --git a/backend/src/seed/factories/users.js b/backend/src/factories/users.js similarity index 70% rename from backend/src/seed/factories/users.js rename to backend/src/factories/users.js index e0ec04fc0..57f69b76b 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/factories/users.js @@ -1,6 +1,6 @@ import faker from 'faker' import uuid from 'uuid/v4' -import encryptPassword from '../../helpers/encryptPassword' +import encryptPassword from '../helpers/encryptPassword' import slugify from 'slug' export default function create() { @@ -16,6 +16,9 @@ export default function create() { about: faker.lorem.paragraph(), termsAndConditionsAgreedVersion: '0.0.1', termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z', + allowEmbedIframes: false, + showShoutsPublicly: false, + locale: 'en', } defaults.slug = slugify(defaults.name, { lower: true }) args = { @@ -24,7 +27,15 @@ export default function create() { } args = await encryptPassword(args) const user = await neodeInstance.create('User', args) - const email = await factoryInstance.create('EmailAddress', { email: args.email }) + + let email + if (typeof args.email === 'object') { + // probably a neode node + email = args.email + } else { + email = await factoryInstance.create('EmailAddress', { email: args.email }) + } + await user.relateTo(email, 'primaryEmail') await email.relateTo(user, 'belongsTo') return user diff --git a/backend/src/helpers/jest.js b/backend/src/helpers/jest.js new file mode 100644 index 000000000..201d68c14 --- /dev/null +++ b/backend/src/helpers/jest.js @@ -0,0 +1,5 @@ +//* This is a fake ES2015 template string, just to benefit of syntax +// highlighting of `gql` template strings in certain editors. +export function gql(strings) { + return strings.join('') +} diff --git a/backend/src/index.js b/backend/src/index.js index 0e7fc233c..98354dc1f 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -2,7 +2,8 @@ import createServer from './server' import CONFIG from './config' const { app } = createServer() -app.listen({ port: CONFIG.GRAPHQL_PORT }, () => { +const url = new URL(CONFIG.GRAPHQL_URI) +app.listen({ port: url.port }, () => { /* eslint-disable-next-line no-console */ console.log(`GraphQLServer ready at ${CONFIG.GRAPHQL_URI} 🚀`) }) diff --git a/backend/src/jest/helpers.js b/backend/src/jest/helpers.js deleted file mode 100644 index 380aedd16..000000000 --- a/backend/src/jest/helpers.js +++ /dev/null @@ -1,23 +0,0 @@ -import { request } from 'graphql-request' - -// this is the to-be-tested server host -// not to be confused with the seeder host -export const host = 'http://127.0.0.1:4123' - -export async function login(variables) { - const mutation = ` - mutation($email: String!, $password: String!) { - login(email: $email, password: $password) - } - ` - const response = await request(host, mutation, variables) - return { - authorization: `Bearer ${response.login}`, - } -} - -//* This is a fake ES2015 template string, just to benefit of syntax -// highlighting of `gql` template strings in certain editors. -export function gql(strings) { - return strings.join('') -} diff --git a/backend/src/jest/snapshots/embeds/HumanConnectionOrg.html b/backend/src/jest/snapshots/embeds/HumanConnectionOrg.html deleted file mode 100644 index ed3f96cda..000000000 --- a/backend/src/jest/snapshots/embeds/HumanConnectionOrg.html +++ /dev/null @@ -1,38 +0,0 @@ - - -Human Connection - Startseite | Facebook - - - - - - - - - - - - - - - - - - - - - - - -
Mehr von Human Connection auf Facebook anzeigen
Mehr von Human Connection auf Facebook anzeigen
oder
Neues Konto erstellen
Willkommen bei Human Connection
Human Connection ist ein gemeinnütziges soziales Wissens- und Aktionsnetzwerk mit Sitz in Weilheim-T...
Mehr anzeigen
CommunityAlle ansehen
Highlights info row image
24.407 Personen gefällt das
Highlights info row image
25.652 Personen haben das abonniert
Highlights info row image
72 Besuche
Highlights info row image
Bahnhofstraße 11 (512,71 km)
73235 Weilheim an der Teck
Highlights info row image
01514 3804222
Highlights info row image
Preisklasse €
SeitentransparenzMehr anzeigen
Facebook liefert Informationen, mit denen du die Intention von Seiten besser verstehst. Hier erfährst du mehr zu den Personen, die die Seiten verwalten und Beiträge darin posten.
Seite erstellt – 21. Oktober 2015
Ähnliche Seiten
OrteWeilheim an der TeckHuman Connection
Beiträge

Human Connection soll mehr als nur ein einfacher Facebookersatz werden – nämlich vor allem lösungsorientiert und zukunftsfähig. Wir möchten mit unserem Netzwerk dazu beitragen, dass mehr Menschen aktiv werden und selbst etwas verändern. Vor allem möchten wir, dass sie nicht jede negative Neuigkeit in einem sozialen Netzwerk “schlucken” müssen. Der Gedanke man könne doch eh nichts ändern, stimmt nur dann, wenn andere Leser genauso denken. Gemeinsam ist so viel mehr möglich und... dafür braucht es zunächst erst einmal jeden Einzelnen.

Wie kann Human Connection dabei helfen?

Das erfahrt ihr im ganzen Beitrag:
https://human-connection.org/human-connection-loesungsorie…/

Mehr anzeigen
Bild könnte enthalten: Text

Human Connection ist unser Herzensprojekt, welches allerdings uns und auch euch sehr viel Geduld abfordert. Warum dauert das eigentlich so lange?

Hier erfahrt ihr die Antwort:

human-connection.org
Human Connection ist unser Herzensprojekt, welches allerdings uns und auch euch sehr viel Geduld abfordert. Warum dauert das eigentlich so lange?
Videos
Schau es an und Du weißt, was zu tun ist
10
2
Messe Fair Handeln: Human Connection beim Hackathon (Stuttgart 26.-28.04.2019)
9
1
In Nerds We Trust - Open Source - Wir lieben freie Software!
4
1
- - - - \ No newline at end of file diff --git a/backend/src/jest/snapshots/embeds/babyLovesCat.html b/backend/src/jest/snapshots/embeds/babyLovesCat.html deleted file mode 100644 index 88ce2a327..000000000 --- a/backend/src/jest/snapshots/embeds/babyLovesCat.html +++ /dev/null @@ -1,1545 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -Baby Loves Cat - YouTube - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
-
-
- DE -
-
-
- -
-
-
-

- - - -Wird geladen... - -

- -
-
-
- -
- -
-
-
- -Du siehst YouTube auf Deutsch. -Du kannst diese Einstellung unten ändern. -
-
- - You're viewing YouTube in German. - You can change this preference below. -
-
-
- -
-
- -
-
- - -
-
-
- -
-
-
-
- - -
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-

- - - - - Baby Loves Cat - - -

-
-
- - -
- - - - - -
-
30.160.931 Aufrufe
-
-
-
-
-
- - - - - -
-
- - - -
-
-
-
-

- - - -Wird geladen... - -

- -
-
-
- -
- -
-
-
-

- - - -Wird geladen... - -

- -
-
-
-

-Transkript -

-
- -
- - - -
-
-Das interaktive Transkript konnte nicht geladen werden. -
- - -
-
- -
- -
-
-

- - - -Wird geladen... - -

- -
-
- - -
-
- Die Bewertungsfunktion ist nach Ausleihen des Videos verfügbar. -
- -
- -
-
- Diese Funktion ist gerade nicht verfügbar. Bitte versuche es später noch einmal. -
-
- - -
- - -
- - -
Am 16.08.2015 veröffentlicht

She's incapable of controlling her limbs when her kitty is around. The obsession grows every day.

Ps. That's a sleep sack she's in. Not a starfish outfit. Although I wish I were cool enough to dress my daughter in a starfish outfit.

Jukin Media Verified (Original)
*For licensing / permission to use please contact licensing(at)jukinmediadotcom

-
    -
  • -

    - Kategorie -

    - -
  • - -
-
-
- -
- - -
-
-
- - -
- Kommentare sind für dieses Video deaktiviert. -
- -
- - - - -
-
-
-
- -
- - -
-
-
- - - -
-
- -
- -
-
-
-Anzeige -
-
-
-
- - -
-
-
-
-
- - - -Wenn Autoplay aktiviert ist, wird die Wiedergabe automatisch mit einem der aktuellen Videovorschläge fortgesetzt. - - - -
-

- Nächstes Video -

- - -
-
- - -
-
-
- -
-
- -
-
- -
-
-
- - -
- -
- -
-
- - -
-
- - -
- , um dieses Video zur Playlist "Später ansehen" hinzuzufügen. - -
-
-

-Hinzufügen -

-
-
-

- - - - Playlists werden geladen... - -

- -
-
- - - - - - - \ No newline at end of file diff --git a/backend/src/jwt/decode.js b/backend/src/jwt/decode.js index d022f3512..5433a8c76 100644 --- a/backend/src/jwt/decode.js +++ b/backend/src/jwt/decode.js @@ -12,19 +12,27 @@ export default async (driver, authorizationHeader) => { return null } const session = driver.session() - const query = ` - MATCH (user:User {id: $id, deleted: false, disabled: false }) - RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId} - LIMIT 1 - ` - const result = await session.run(query, { id }) - session.close() - const [currentUser] = await result.records.map(record => { - return record.get('user') + + const writeTxResultPromise = session.writeTransaction(async transaction => { + const updateUserLastActiveTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $id, deleted: false, disabled: false }) + SET user.lastActiveAt = toString(datetime()) + RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId} + LIMIT 1 + `, + { id }, + ) + return updateUserLastActiveTransactionResponse.records.map(record => record.get('user')) }) - if (!currentUser) return null - return { - token, - ...currentUser, + try { + const [currentUser] = await writeTxResultPromise + if (!currentUser) return null + return { + token, + ...currentUser, + } + } finally { + session.close() } } diff --git a/backend/src/jwt/decode.spec.js b/backend/src/jwt/decode.spec.js index df6914f25..71444a3e5 100644 --- a/backend/src/jwt/decode.spec.js +++ b/backend/src/jwt/decode.spec.js @@ -1,9 +1,10 @@ -import Factory from '../seed/factories/index' -import { getDriver } from '../bootstrap/neo4j' +import Factory from '../factories/index' +import { getDriver, getNeode } from '../db/neo4j' import decode from './decode' const factory = Factory() const driver = getDriver() +const neode = getNeode() // here is the decoded JWT token: // { @@ -85,6 +86,33 @@ describe('decode', () => { }) }) + it('sets `lastActiveAt`', async () => { + let user = await neode.first('User', { id: 'u3' }) + await expect(user.toJson()).resolves.not.toHaveProperty('lastActiveAt') + await decode(driver, authorizationHeader) + user = await neode.first('User', { id: 'u3' }) + await expect(user.toJson()).resolves.toMatchObject({ + lastActiveAt: expect.any(String), + }) + }) + + it('updates `lastActiveAt` for every authenticated request', async () => { + let user = await neode.first('User', { id: 'u3' }) + await user.update({ + updatedAt: new Date().toISOString(), + lastActiveAt: '2019-10-03T23:33:08.598Z', + }) + await expect(user.toJson()).resolves.toMatchObject({ + lastActiveAt: '2019-10-03T23:33:08.598Z', + }) + await decode(driver, authorizationHeader) + user = await neode.first('User', { id: 'u3' }) + await expect(user.toJson()).resolves.toMatchObject({ + // should be a different time by now ;) + lastActiveAt: expect.not.stringContaining('2019-10-03T23:33'), + }) + }) + describe('but user is deleted', () => { beforeEach(async () => { await user.update({ updatedAt: new Date().toISOString(), deleted: true }) @@ -92,6 +120,7 @@ describe('decode', () => { it('returns null', returnsNull) }) + describe('but user is disabled', () => { beforeEach(async () => { await user.update({ updatedAt: new Date().toISOString(), disabled: true }) diff --git a/backend/src/middleware/activityPubMiddleware.js b/backend/src/middleware/activityPubMiddleware.js index e6fb2385c..712ca6c8a 100644 --- a/backend/src/middleware/activityPubMiddleware.js +++ b/backend/src/middleware/activityPubMiddleware.js @@ -1,51 +1,51 @@ import { generateRsaKeyPair } from '../activitypub/security' import { activityPub } from '../activitypub/ActivityPub' -import as from 'activitystrea.ms' +// import as from 'activitystrea.ms' -const debug = require('debug')('backend:schema') +// const debug = require('debug')('backend:schema') export default { Mutation: { - CreatePost: async (resolve, root, args, context, info) => { - args.activityId = activityPub.generateStatusId(context.user.slug) - args.objectId = activityPub.generateStatusId(context.user.slug) + // CreatePost: async (resolve, root, args, context, info) => { + // args.activityId = activityPub.generateStatusId(context.user.slug) + // args.objectId = activityPub.generateStatusId(context.user.slug) - const post = await resolve(root, args, context, info) + // const post = await resolve(root, args, context, info) - const { user: author } = context - const actorId = author.actorId - debug(`actorId = ${actorId}`) - const createActivity = await new Promise((resolve, reject) => { - as.create() - .id(`${actorId}/status/${args.activityId}`) - .actor(`${actorId}`) - .object( - as - .article() - .id(`${actorId}/status/${post.id}`) - .content(post.content) - .to('https://www.w3.org/ns/activitystreams#Public') - .publishedNow() - .attributedTo(`${actorId}`), - ) - .prettyWrite((err, doc) => { - if (err) { - reject(err) - } else { - debug(doc) - const parsedDoc = JSON.parse(doc) - parsedDoc.send = true - resolve(JSON.stringify(parsedDoc)) - } - }) - }) - try { - await activityPub.sendActivity(createActivity) - } catch (e) { - debug(`error sending post activity\n${e}`) - } - return post - }, + // const { user: author } = context + // const actorId = author.actorId + // debug(`actorId = ${actorId}`) + // const createActivity = await new Promise((resolve, reject) => { + // as.create() + // .id(`${actorId}/status/${args.activityId}`) + // .actor(`${actorId}`) + // .object( + // as + // .article() + // .id(`${actorId}/status/${post.id}`) + // .content(post.content) + // .to('https://www.w3.org/ns/activitystreams#Public') + // .publishedNow() + // .attributedTo(`${actorId}`), + // ) + // .prettyWrite((err, doc) => { + // if (err) { + // reject(err) + // } else { + // debug(doc) + // const parsedDoc = JSON.parse(doc) + // parsedDoc.send = true + // resolve(JSON.stringify(parsedDoc)) + // } + // }) + // }) + // try { + // await activityPub.sendActivity(createActivity) + // } catch (e) { + // debug(`error sending post activity\n${e}`) + // } + // return post + // }, SignupVerification: async (resolve, root, args, context, info) => { const keys = generateRsaKeyPair() Object.assign(args, keys) diff --git a/backend/src/middleware/email/emailMiddleware.js b/backend/src/middleware/email/emailMiddleware.js index 809ca4072..f92da3368 100644 --- a/backend/src/middleware/email/emailMiddleware.js +++ b/backend/src/middleware/email/emailMiddleware.js @@ -1,36 +1,45 @@ import CONFIG from '../../config' import nodemailer from 'nodemailer' -import { resetPasswordMail, wrongAccountMail } from './templates/passwordReset' -import { signupTemplate } from './templates/signup' +import { htmlToText } from 'nodemailer-html-to-text' +import { + signupTemplate, + resetPasswordTemplate, + wrongAccountTemplate, + emailVerificationTemplate, +} from './templateBuilder' -let sendMail -if (CONFIG.SMTP_HOST && CONFIG.SMTP_PORT) { - sendMail = async templateArgs => { - await transporter().sendMail({ - from: '"Human Connection" ', - ...templateArgs, - }) - } -} else { - sendMail = () => {} +const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT +const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD + +let sendMail = () => {} +if (!hasEmailConfig) { if (process.env.NODE_ENV !== 'test') { // eslint-disable-next-line no-console console.log('Warning: Email middleware will not try to send mails.') } -} +} else { + sendMail = async templateArgs => { + const transporter = nodemailer.createTransport({ + host: CONFIG.SMTP_HOST, + port: CONFIG.SMTP_PORT, + ignoreTLS: CONFIG.SMTP_IGNORE_TLS === 'true', + secure: false, // true for 465, false for other ports + auth: hasAuthData && { + user: CONFIG.SMTP_USERNAME, + pass: CONFIG.SMTP_PASSWORD, + }, + }) -const transporter = () => { - const configs = { - host: CONFIG.SMTP_HOST, - port: CONFIG.SMTP_PORT, - ignoreTLS: CONFIG.SMTP_IGNORE_TLS, - secure: false, // true for 465, false for other ports + transporter.use( + 'compile', + htmlToText({ + ignoreImage: true, + wordwrap: false, + }), + ) + + await transporter.sendMail(templateArgs) } - const { SMTP_USERNAME: user, SMTP_PASSWORD: pass } = CONFIG - if (user && pass) { - configs.auth = { user, pass } - } - return nodemailer.createTransport(configs) } const sendSignupMail = async (resolve, root, args, context, resolveInfo) => { @@ -41,15 +50,26 @@ const sendSignupMail = async (resolve, root, args, context, resolveInfo) => { return response } +const sendPasswordResetMail = async (resolve, root, args, context, resolveInfo) => { + const { email } = args + const { email: userFound, nonce, name } = await resolve(root, args, context, resolveInfo) + const template = userFound ? resetPasswordTemplate : wrongAccountTemplate + await sendMail(template({ email, nonce, name })) + return true +} + +const sendEmailVerificationMail = async (resolve, root, args, context, resolveInfo) => { + const response = await resolve(root, args, context, resolveInfo) + const { email, nonce, name } = response + await sendMail(emailVerificationTemplate({ email, nonce, name })) + delete response.nonce + return response +} + export default { Mutation: { - requestPasswordReset: async (resolve, root, args, context, resolveInfo) => { - const { email } = args - const { email: emailFound, nonce, name } = await resolve(root, args, context, resolveInfo) - const mailTemplate = emailFound ? resetPasswordMail : wrongAccountMail - await sendMail(mailTemplate({ email, nonce, name })) - return true - }, + AddEmailAddress: sendEmailVerificationMail, + requestPasswordReset: sendPasswordResetMail, Signup: sendSignupMail, SignupByInvitation: sendSignupMail, }, diff --git a/backend/src/middleware/email/templateBuilder.js b/backend/src/middleware/email/templateBuilder.js new file mode 100644 index 000000000..b4b7b78ca --- /dev/null +++ b/backend/src/middleware/email/templateBuilder.js @@ -0,0 +1,77 @@ +import mustache from 'mustache' +import CONFIG from '../../config' + +import * as templates from './templates' + +const from = '"Human Connection" ' +const supportUrl = 'https://human-connection.org/en/contact' + +export const signupTemplate = ({ email, nonce }) => { + const subject = 'Willkommen, Bienvenue, Welcome to Human Connection!' + const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI) + actionUrl.searchParams.set('nonce', nonce) + actionUrl.searchParams.set('email', email) + + return { + from, + to: email, + subject, + html: mustache.render( + templates.layout, + { actionUrl, nonce, supportUrl, subject }, + { content: templates.signup }, + ), + } +} + +export const emailVerificationTemplate = ({ email, nonce, name }) => { + const subject = 'Neue E-Mail Adresse | New E-Mail Address' + const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI) + actionUrl.searchParams.set('nonce', nonce) + actionUrl.searchParams.set('email', email) + + return { + from, + to: email, + subject, + html: mustache.render( + templates.layout, + { actionUrl, name, nonce, supportUrl, subject }, + { content: templates.emailVerification }, + ), + } +} + +export const resetPasswordTemplate = ({ email, nonce, name }) => { + const subject = 'Neues Passwort | Reset Password' + const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI) + actionUrl.searchParams.set('nonce', nonce) + actionUrl.searchParams.set('email', email) + + return { + from, + to: email, + subject, + html: mustache.render( + templates.layout, + { actionUrl, name, nonce, supportUrl, subject }, + { content: templates.passwordReset }, + ), + } +} + +export const wrongAccountTemplate = ({ email }) => { + const subject = 'Falsche Mailadresse? | Wrong E-mail?' + const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI) + + return { + from, + to: email, + subject, + html: mustache.render( + templates.layout, + { actionUrl, supportUrl }, + { content: templates.wrongAccount }, + ), + } +} diff --git a/backend/src/middleware/email/templates/emailVerification.html b/backend/src/middleware/email/templates/emailVerification.html new file mode 100644 index 000000000..939ceccdb --- /dev/null +++ b/backend/src/middleware/email/templates/emailVerification.html @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/email/templates/index.js b/backend/src/middleware/email/templates/index.js new file mode 100644 index 000000000..594cae334 --- /dev/null +++ b/backend/src/middleware/email/templates/index.js @@ -0,0 +1,11 @@ +import fs from 'fs' +import path from 'path' + +const readFile = fileName => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') + +export const signup = readFile('./signup.html') +export const passwordReset = readFile('./resetPassword.html') +export const wrongAccount = readFile('./wrongAccount.html') +export const emailVerification = readFile('./emailVerification.html') + +export const layout = readFile('./layout.html') diff --git a/backend/src/middleware/email/templates/layout.html b/backend/src/middleware/email/templates/layout.html new file mode 100644 index 000000000..7e1c39ace --- /dev/null +++ b/backend/src/middleware/email/templates/layout.html @@ -0,0 +1,196 @@ + + + + + + + + + + {{ subject }} + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + diff --git a/backend/src/middleware/email/templates/passwordReset.js b/backend/src/middleware/email/templates/passwordReset.js deleted file mode 100644 index c977594b5..000000000 --- a/backend/src/middleware/email/templates/passwordReset.js +++ /dev/null @@ -1,83 +0,0 @@ -import CONFIG from '../../../config' - -export const resetPasswordMail = options => { - const { - name, - email, - nonce, - subject = 'Use this link to reset your password. The link is only valid for 24 hours.', - supportUrl = 'https://human-connection.org/en/contact/', - } = options - const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('nonce', nonce) - actionUrl.searchParams.set('email', email) - - return { - to: email, - subject, - text: ` -Hi ${name}! - -You recently requested to reset your password for your Human Connection account. -Use the link below to reset it. This password reset is only valid for the next -24 hours. - -${actionUrl} - -If you did not request a password reset, please ignore this email or contact -support if you have questions: - -${supportUrl} - -Thanks, -The Human Connection Team - -If you're having trouble with the link above, you can manually copy and -paste the following code into your browser window: - -${nonce} - -Human Connection gemeinnützige GmbH -Bahnhofstr. 11 -73235 Weilheim / Teck -Deutschland - `, - } -} - -export const wrongAccountMail = options => { - const { - email, - subject = `We received a request to reset your password with this email address (${email})`, - supportUrl = 'https://human-connection.org/en/contact/', - } = options - const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI) - return { - to: email, - subject, - text: ` -We received a request to reset the password to access Human Connection with your -email address, but we were unable to find an account associated with this -address. - -If you use Human Connection and were expecting this email, consider trying to -request a password reset using the email address associated with your account. -Try a different email: - -${actionUrl} - -If you do not use Human Connection or did not request a password reset, please -ignore this email. Feel free to contact support if you have further questions: - -${supportUrl} - -Thanks, -The Human Connection Team - -Human Connection gemeinnützige GmbH -Bahnhofstr. 11 -73235 Weilheim / Teck -Deutschland - `, - } -} diff --git a/backend/src/middleware/email/templates/resetPassword.html b/backend/src/middleware/email/templates/resetPassword.html new file mode 100644 index 000000000..768051070 --- /dev/null +++ b/backend/src/middleware/email/templates/resetPassword.html @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/email/templates/signup.html b/backend/src/middleware/email/templates/signup.html new file mode 100644 index 000000000..ad60d9323 --- /dev/null +++ b/backend/src/middleware/email/templates/signup.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/email/templates/signup.js b/backend/src/middleware/email/templates/signup.js deleted file mode 100644 index 54cc51be2..000000000 --- a/backend/src/middleware/email/templates/signup.js +++ /dev/null @@ -1,62 +0,0 @@ -import CONFIG from '../../../config' - -export const signupTemplate = options => { - const { - email, - nonce, - subject = 'Welcome to Human Connection! Here is your signup link.', - supportUrl = 'https://human-connection.org/en/contact/', - } = options - const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('nonce', nonce) - actionUrl.searchParams.set('email', email) - - return { - to: email, - subject, - text: ` -Willkommen bei Human Connection! Klick auf diesen Link, um den -Registrierungsprozess abzuschließen und um ein Benutzerkonto zu erstellen! - -${actionUrl} - -Alternativ kannst du diesen Code auch kopieren und im Browserfenster einfügen: - -${nonce} - -Bitte ignoriere diese Mail, falls du dich nicht bei Human Connection angemeldet -hast. Bei Fragen kontaktiere gerne unseren Support: - -${supportUrl} - -Danke, -Das Human Connection Team - - -English Version -=============== - -Welcome to Human Connection! Use this link to complete the registration process -and create a user account: - -${actionUrl} - -You can also copy+paste this verification nonce in your browser window: - -${nonce} - -If you did not signed up for Human Connection, please ignore this email or -contact support if you have questions: - -${supportUrl} - -Thanks, -The Human Connection Team - -Human Connection gemeinnützige GmbH -Bahnhofstr. 11 -73235 Weilheim / Teck -Deutschland - `, - } -} diff --git a/backend/src/middleware/email/templates/wrongAccount.html b/backend/src/middleware/email/templates/wrongAccount.html new file mode 100644 index 000000000..cbf6e3cbb --- /dev/null +++ b/backend/src/middleware/email/templates/wrongAccount.html @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/middleware/hashtags/extractHashtags.js b/backend/src/middleware/hashtags/extractHashtags.js index c7782e59d..9a903e4fa 100644 --- a/backend/src/middleware/hashtags/extractHashtags.js +++ b/backend/src/middleware/hashtags/extractHashtags.js @@ -4,23 +4,23 @@ import { exec, build } from 'xregexp/xregexp-all.js' // https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style // here: // 0. Search for whole string. -// 1. Hashtag has only all unicode characters and '0-9'. -// 2. If it starts with a digit '0-9' than a unicode character has to follow. -const regX = build('^/search/hashtag/((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$') +// 1. Hashtag has only all unicode letters and '0-9'. +// 2. If it starts with a digit '0-9' than a unicode letter has to follow. +const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$') 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 as well, so we search for the 'a' html-tag. - const urls = $('a') + const ids = $('a[data-hashtag-id]') .map((_, el) => { - return $(el).attr('href') + return $(el).attr('data-hashtag-id') }) .get() const hashtags = [] - urls.forEach(url => { - const match = exec(url, regX) + ids.forEach(id => { + const match = exec(id, regX) if (match != null) { hashtags.push(match[1]) } diff --git a/backend/src/middleware/hashtags/extractHashtags.spec.js b/backend/src/middleware/hashtags/extractHashtags.spec.js index 2e1761718..739c7de54 100644 --- a/backend/src/middleware/hashtags/extractHashtags.spec.js +++ b/backend/src/middleware/hashtags/extractHashtags.spec.js @@ -8,9 +8,24 @@ describe('extractHashtags', () => { }) describe('searches through links', () => { - it('finds links with and without ".hashtag" class and extracts Hashtag names', () => { - const content = - '

#Elections#Democracy

' + it('without `class="hashtag"` but `data-hashtag-id="something"`, and extracts the Hashtag to make a Hashtag link', () => { + const content = ` +

+ + #Elections + + + #Democracy + +

+ ` expect(extractHashtags(content)).toEqual(['Elections', 'Democracy']) }) @@ -20,23 +35,57 @@ describe('extractHashtags', () => { 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', () => { - // Allowed are all unicode letters '\pL' and all digits '0-9'. There haveto be at least one letter in it. - const content = - '

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

' - expect(extractHashtags(content).sort()).toEqual([ - '0123456789a', - 'AbcDefXyz0123456789', - 'λαπ', - ]) - }) + it('ignores hashtag links with unsupported character combinations', () => { + // Allowed are all unicode letters '\pL' and all digits '0-9'. There haveto be at least one letter in it. + const content = ` +

+ Something inspirational about + + #AbcDefXyz0123456789!*(),2 + , + + #0123456789 + , + + #0123456789a + , + + #AbcDefXyz0123456789 + , and + + #ħπαλ + . +

+ ` + expect(extractHashtags(content).sort()).toEqual([ + '0123456789a', + 'AbcDefXyz0123456789', + 'ħπαλ', + ]) }) describe('does not crash if', () => { diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.js b/backend/src/middleware/hashtags/hashtagsMiddleware.js index c9156398d..7d8593fd5 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.js +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.js @@ -2,31 +2,27 @@ import extractHashtags from '../hashtags/extractHashtags' const updateHashtagsOfPost = async (postId, hashtags, context) => { if (!hashtags.length) return - 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, disabled: false, deleted: false }) - MERGE (p)-[:TAGGED]->(t) - RETURN p, t - ` - await session.run(cypherDeletePreviousRelations, { - postId, - }) - await session.run(cypherCreateNewTagsAndRelations, { - postId, - hashtags, - }) - session.close() + + try { + await session.writeTransaction(txc => { + return txc.run( + ` + MATCH (post:Post { id: $postId}) + OPTIONAL MATCH (post)-[previousRelations:TAGGED]->(tag:Tag) + DELETE previousRelations + WITH post + UNWIND $hashtags AS tagName + MERGE (tag:Tag {id: tagName, disabled: false, deleted: false }) + MERGE (post)-[:TAGGED]->(tag) + RETURN post, tag + `, + { postId, hashtags }, + ) + }) + } finally { + session.close() + } } const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js index 3f101f778..2247e692d 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js @@ -1,7 +1,7 @@ -import { gql } from '../../jest/helpers' -import Factory from '../../seed/factories' +import { gql } from '../../helpers/jest' +import Factory from '../../factories' import { createTestClient } from 'apollo-server-testing' -import { neode, getDriver } from '../../bootstrap/neo4j' +import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' let server @@ -11,7 +11,7 @@ let hashtagingUser let authenticatedUser const factory = Factory() const driver = getDriver() -const instance = neode() +const neode = getNeode() const categoryIds = ['cat9'] const createPostMutation = gql` mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) { @@ -36,7 +36,7 @@ beforeAll(() => { context: () => { return { user: authenticatedUser, - neode: instance, + neode, driver, } }, @@ -48,14 +48,14 @@ beforeAll(() => { }) beforeEach(async () => { - hashtagingUser = await instance.create('User', { + hashtagingUser = await neode.create('User', { id: 'you', name: 'Al Capone', slug: 'al-capone', email: 'test@example.org', password: '1234', }) - await instance.create('Category', { + await neode.create('Category', { id: 'cat9', name: 'Democracy & Politics', icon: 'university', @@ -69,8 +69,27 @@ afterEach(async () => { describe('hashtags', () => { const id = 'p135' const title = '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 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) { @@ -129,10 +148,29 @@ describe('hashtags', () => { ) }) - 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 postContent = - '

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

' + describe('updates the Post by removing, keeping and adding one hashtag respectively', () => { + // The already existing hashtag has no class at this point. + const postContent = ` +

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

+ ` it('only one previous Hashtag and the new Hashtag exists', async () => { await mutate({ diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index d09a96475..25195b1b5 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -7,7 +7,6 @@ import sluggify from './sluggifyMiddleware' import excerpt from './excerptMiddleware' import xss from './xssMiddleware' import permissions from './permissionsMiddleware' -import user from './userMiddleware' import includedFields from './includedFieldsMiddleware' import orderBy from './orderByMiddleware' import validation from './validation/validationMiddleware' @@ -18,25 +17,25 @@ import sentry from './sentryMiddleware' export default schema => { const middlewares = { - permissions, sentry, + permissions, + xss, activityPub, validation, sluggify, excerpt, + email, notifications, hashtags, - xss, softDelete, - user, includedFields, orderBy, - email, } let order = [ 'sentry', 'permissions', + 'xss', // 'activityPub', disabled temporarily 'validation', 'sluggify', @@ -44,9 +43,7 @@ export default schema => { 'email', 'notifications', 'hashtags', - 'xss', 'softDelete', - 'user', 'includedFields', 'orderBy', ] diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index a494783cf..837193773 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -1,135 +1,121 @@ import extractMentionedUsers from './mentions/extractMentionedUsers' +import { validateNotifyUsers } from '../validation/validationMiddleware' -const notifyUsers = async (label, id, idsOfUsers, reason, context) => { - if (!idsOfUsers.length) return +const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { + const idsOfUsers = extractMentionedUsers(args.content) + const post = await resolve(root, args, context, resolveInfo) + if (post && idsOfUsers && idsOfUsers.length) + await notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context) + return post +} - // Checked here, because it does not go through GraphQL checks at all in this file. - const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post'] - if (!reasonsAllowed.includes(reason)) { - throw new Error('Notification reason is not allowed!') - } - if ( - (label === 'Post' && reason !== 'mentioned_in_post') || - (label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason)) - ) { - throw new Error('Notification does not fit the reason!') - } +const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => { + const { content } = args + let idsOfUsers = extractMentionedUsers(content) + const comment = await resolve(root, args, context, resolveInfo) + const [postAuthor] = await postAuthorOfComment(comment.id, { context }) + idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id) + if (idsOfUsers && idsOfUsers.length) + await notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) + if (context.user.id !== postAuthor.id) + await notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context) + return comment +} +const postAuthorOfComment = async (commentId, { context }) => { const session = context.driver.session() - let cypher + let postAuthorId + try { + postAuthorId = await session.readTransaction(transaction => { + return transaction.run( + ` + MATCH (author:User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) + RETURN author { .id } as authorId + `, + { commentId }, + ) + }) + return postAuthorId.records.map(record => record.get('authorId')) + } finally { + session.close() + } +} + +const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { + await validateNotifyUsers(label, reason) + let mentionedCypher switch (reason) { case 'mentioned_in_post': { - cypher = ` + mentionedCypher = ` MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) MATCH (user: User) WHERE user.id in $idsOfUsers AND NOT (user)<-[:BLOCKED]-(author) MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) - SET notification.read = FALSE - SET ( - CASE - WHEN notification.createdAt IS NULL - THEN notification END ).createdAt = toString(datetime()) - SET notification.updatedAt = toString(datetime()) ` break } case 'mentioned_in_comment': { - cypher = ` - MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - AND NOT (user)<-[:BLOCKED]-(postAuthor) - MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) - SET notification.read = FALSE - SET ( - CASE - WHEN notification.createdAt IS NULL - THEN notification END ).createdAt = toString(datetime()) - SET notification.updatedAt = toString(datetime()) - ` - break - } - case 'commented_on_post': { - cypher = ` - MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - AND NOT (author)<-[:BLOCKED]-(user) - MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) - SET notification.read = FALSE - SET ( - CASE - WHEN notification.createdAt IS NULL - THEN notification END ).createdAt = toString(datetime()) - SET notification.updatedAt = toString(datetime()) + mentionedCypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (user)<-[:BLOCKED]-(postAuthor) + MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) ` break } } - await session.run(cypher, { - id, - idsOfUsers, - reason, - }) - session.close() -} - -const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { - const idsOfUsers = extractMentionedUsers(args.content) - - const post = await resolve(root, args, context, resolveInfo) - - if (post) { - await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context) - } - - return post -} - -const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => { - const idsOfUsers = extractMentionedUsers(args.content) - - const comment = await resolve(root, args, context, resolveInfo) - - if (comment) { - await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) - } - - return comment -} - -const handleCreateComment = async (resolve, root, args, context, resolveInfo) => { - const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo) - - if (comment) { - const session = context.driver.session() - const cypherFindUser = ` - MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) - RETURN user { .id } - ` - const result = await session.run(cypherFindUser, { - commentId: comment.id, + mentionedCypher += ` + SET notification.read = FALSE + SET ( + CASE + WHEN notification.createdAt IS NULL + THEN notification END ).createdAt = toString(datetime()) + SET notification.updatedAt = toString(datetime()) + ` + const session = context.driver.session() + try { + await session.writeTransaction(transaction => { + return transaction.run(mentionedCypher, { id, idsOfUsers, reason }) }) + } finally { session.close() - const [postAuthor] = await result.records.map(record => { - return record.get('user') - }) - if (context.user.id !== postAuthor.id) { - await notifyUsers('Comment', comment.id, [postAuthor.id], 'commented_on_post', context) - } } +} - return comment +const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => { + await validateNotifyUsers(label, reason) + const session = context.driver.session() + + try { + await session.writeTransaction(async transaction => { + await transaction.run( + ` + MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User) + WHERE NOT (postAuthor)-[:BLOCKED]-(commenter) + MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor) + SET notification.read = FALSE + SET ( + CASE + WHEN notification.createdAt IS NULL + THEN notification END ).createdAt = toString(datetime()) + SET notification.updatedAt = toString(datetime()) + `, + { commentId, postAuthorId, reason }, + ) + }) + } finally { + session.close() + } } export default { Mutation: { CreatePost: handleContentDataOfPost, UpdatePost: handleContentDataOfPost, - CreateComment: handleCreateComment, + CreateComment: handleContentDataOfComment, UpdateComment: handleContentDataOfComment, }, } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js index 88f91d688..136388b88 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js @@ -1,17 +1,13 @@ -import { gql } from '../../jest/helpers' -import Factory from '../../seed/factories' +import { gql } from '../../helpers/jest' +import Factory from '../../factories' import { createTestClient } from 'apollo-server-testing' -import { neode, getDriver } from '../../bootstrap/neo4j' +import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' -let server -let query -let mutate -let notifiedUser -let authenticatedUser +let server, query, mutate, notifiedUser, authenticatedUser const factory = Factory() const driver = getDriver() -const instance = neode() +const neode = getNeode() const categoryIds = ['cat9'] const createPostMutation = gql` mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) { @@ -39,12 +35,13 @@ const createCommentMutation = gql` } ` -beforeAll(() => { +beforeAll(async () => { + await factory.cleanDatabase() const createServerResult = createServer({ context: () => { return { user: authenticatedUser, - neode: instance, + neode: neode, driver, } }, @@ -56,14 +53,14 @@ beforeAll(() => { }) beforeEach(async () => { - notifiedUser = await instance.create('User', { + notifiedUser = await neode.create('User', { id: 'you', name: 'Al Capone', slug: 'al-capone', email: 'test@example.org', password: '1234', }) - await instance.create('Category', { + await neode.create('Category', { id: 'cat9', name: 'Democracy & Politics', icon: 'university', @@ -105,6 +102,7 @@ describe('notifications', () => { let title let postContent let postAuthor + const createPostAction = async () => { authenticatedUser = await postAuthor.toJson() await mutate({ @@ -145,7 +143,7 @@ describe('notifications', () => { describe('commenter is not me', () => { beforeEach(async () => { commentContent = 'Commenters comment.' - commentAuthor = await instance.create('User', { + commentAuthor = await neode.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -172,7 +170,6 @@ describe('notifications', () => { ], }, }) - const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -189,7 +186,7 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { notifications: [] }, }) - const { query } = createTestClient(server) + await expect( query({ query: notificationQuery, @@ -213,7 +210,7 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { notifications: [] }, }) - const { query } = createTestClient(server) + await expect( query({ query: notificationQuery, @@ -227,7 +224,7 @@ describe('notifications', () => { }) beforeEach(async () => { - postAuthor = await instance.create('User', { + postAuthor = await neode.create('User', { id: 'postAuthor', name: 'Mrs Post', slug: 'mrs-post', @@ -239,6 +236,7 @@ describe('notifications', () => { describe('mentions me in a post', () => { beforeEach(async () => { title = 'Mentioning Al Capone' + postContent = 'Hey @al-capone how do you do?' }) @@ -263,7 +261,7 @@ describe('notifications', () => { ], }, }) - const { query } = createTestClient(server) + await expect( query({ query: notificationQuery, @@ -369,7 +367,7 @@ describe('notifications', () => { expect(readAfter).toEqual(false) }) - it('updates the `createdAt` attribute', async () => { + it('does not update the `createdAt` attribute', async () => { await createPostAction() await markAsReadAction() const { @@ -407,7 +405,7 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { notifications: [] }, }) - const { query } = createTestClient(server) + await expect( query({ query: notificationQuery, @@ -430,7 +428,7 @@ describe('notifications', () => { beforeEach(async () => { commentContent = 'One mention about me with @al-capone.' - commentAuthor = await instance.create('User', { + commentAuthor = await neode.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -439,7 +437,15 @@ describe('notifications', () => { }) }) - it('sends a notification', async () => { + it('sends only one notification with reason mentioned_in_comment', async () => { + postAuthor = await neode.create('User', { + id: 'MrPostAuthor', + name: 'Mr Author', + slug: 'mr-author', + email: 'post-author@example.org', + password: '1234', + }) + await createCommentOnPostAction() const expected = expect.objectContaining({ data: { @@ -457,7 +463,41 @@ describe('notifications', () => { ], }, }) - const { query } = createTestClient(server) + + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + + beforeEach(async () => { + title = "Post where I'm the author and I get mentioned in a comment" + postContent = 'Content of post where I get mentioned in a comment.' + postAuthor = notifiedUser + }) + it('sends only one notification with reason commented_on_post, no notification with reason mentioned_in_comment', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + notifications: [ + { + read: false, + createdAt: expect.any(String), + reason: 'commented_on_post', + from: { + __typename: 'Comment', + id: 'c47', + content: commentContent, + }, + }, + ], + }, + }) + await expect( query({ query: notificationQuery, @@ -474,7 +514,7 @@ describe('notifications', () => { await postAuthor.relateTo(notifiedUser, 'blocked') commentContent = 'One mention about me with @al-capone.' - commentAuthor = await instance.create('User', { + commentAuthor = await neode.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -488,7 +528,7 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { notifications: [] }, }) - const { query } = createTestClient(server) + await expect( query({ query: notificationQuery, diff --git a/backend/src/middleware/orderByMiddleware.spec.js b/backend/src/middleware/orderByMiddleware.spec.js index 7a42166cf..8d92a5b5d 100644 --- a/backend/src/middleware/orderByMiddleware.spec.js +++ b/backend/src/middleware/orderByMiddleware.spec.js @@ -1,6 +1,6 @@ -import Factory from '../seed/factories' -import { gql } from '../jest/helpers' -import { neode as getNeode, getDriver } from '../bootstrap/neo4j' +import { gql } from '../helpers/jest' +import Factory from '../factories' +import { getNeode, getDriver } from '../db/neo4j' import { createTestClient } from 'apollo-server-testing' import createServer from '../server' diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 116794fda..50ec5aa75 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -1,11 +1,11 @@ -import { rule, shield, deny, allow, and, or, not } from 'graphql-shield' -import { neode } from '../bootstrap/neo4j' +import { rule, shield, deny, allow, or } from 'graphql-shield' +import { getNeode } from '../db/neo4j' import CONFIG from '../config' const debug = !!CONFIG.DEBUG const allowExternalErrors = true -const instance = neode() +const neode = getNeode() const isAuthenticated = rule({ cache: 'contextual', @@ -36,67 +36,33 @@ const isMyOwn = rule({ const isMySocialMedia = rule({ cache: 'no_cache', })(async (_, args, { user }) => { - let socialMedia = await instance.find('SocialMedia', args.id) + let socialMedia = await neode.find('SocialMedia', args.id) socialMedia = await socialMedia.toJson() return socialMedia.ownedBy.node.id === user.id }) -/* TODO: decide if we want to remove this check: the check - * `onlyEnabledContent` throws authorization errors only if you have - * arguments for `disabled` or `deleted` assuming these are filter - * parameters. Soft-delete middleware obfuscates data on its way out - * anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for - * data so I believe, this is not a good check anyways. - */ -const onlyEnabledContent = rule({ - cache: 'strict', -})(async (parent, args, ctx, info) => { - const { disabled, deleted } = args - return !(disabled || deleted) -}) - -const invitationLimitReached = rule({ - cache: 'no_cache', -})(async (parent, args, { user, driver }) => { - const session = driver.session() - try { - const result = await session.run( - ` - MATCH (user:User {id:$id})-[:GENERATED]->(i:InvitationCode) - RETURN COUNT(i) >= 3 as limitReached - `, - { id: user.id }, - ) - const [limitReached] = result.records.map(record => { - return record.get('limitReached') - }) - return limitReached - } finally { - session.close() - } -}) - const isAuthor = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { if (!user) return false - const session = driver.session() const { id: resourceId } = args - const result = await session.run( - ` - MATCH (resource {id: $resourceId})<-[:WROTE]-(author) - RETURN author - `, - { - resourceId, - }, - ) - session.close() - const [author] = result.records.map(record => { - return record.get('author') + const session = driver.session() + const authorReadTxPromise = session.readTransaction(async transaction => { + const authorTransactionResponse = await transaction.run( + ` + MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId}) + RETURN author + `, + { resourceId, userId: user.id }, + ) + return authorTransactionResponse.records.map(record => record.get('author')) }) - const authorId = author && author.properties && author.properties.id - return authorId === user.id + try { + const [author] = await authorReadTxPromise + return !!author + } finally { + session.close() + } }) const isDeletingOwnAccount = rule({ @@ -111,40 +77,45 @@ const noEmailFilter = rule({ return !('email' in args) }) +const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION) + // Permissions -const permissions = shield( +export default shield( { Query: { '*': deny, findPosts: allow, + findUsers: allow, + findResources: allow, embed: allow, Category: allow, Tag: allow, - Report: isModerator, + reports: isModerator, statistics: allow, currentUser: allow, - Post: or(onlyEnabledContent, isModerator), + Post: allow, + profilePagePosts: allow, Comment: allow, User: or(noEmailFilter, isAdmin), isLoggedIn: allow, Badge: allow, PostsEmotionsCountByEmotion: allow, PostsEmotionsByCurrentUser: isAuthenticated, - blockedUsers: isAuthenticated, + mutedUsers: isAuthenticated, notifications: isAuthenticated, + Donations: isAuthenticated, }, Mutation: { '*': deny, login: allow, SignupByInvitation: allow, - Signup: isAdmin, + Signup: or(publicRegistration, isAdmin), SignupVerification: allow, - CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)), UpdateUser: onlyYourself, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, - report: isAuthenticated, + fileReport: isAuthenticated, CreateSocialMedia: isAuthenticated, UpdateSocialMedia: isMySocialMedia, DeleteSocialMedia: isMySocialMedia, @@ -152,13 +123,12 @@ const permissions = shield( // RemoveBadgeRewarded: isAdmin, reward: isAdmin, unreward: isAdmin, - follow: isAuthenticated, - unfollow: isAuthenticated, + followUser: isAuthenticated, + unfollowUser: isAuthenticated, shout: isAuthenticated, unshout: isAuthenticated, changePassword: isAuthenticated, - enable: isModerator, - disable: isModerator, + review: isModerator, CreateComment: isAuthenticated, UpdateComment: isAuthor, DeleteComment: isAuthor, @@ -167,12 +137,17 @@ const permissions = shield( resetPassword: allow, AddPostEmotions: isAuthenticated, RemovePostEmotions: isAuthenticated, - block: isAuthenticated, - unblock: isAuthenticated, + muteUser: isAuthenticated, + unmuteUser: isAuthenticated, markAsRead: isAuthenticated, + AddEmailAddress: isAuthenticated, + VerifyEmailAddress: isAuthenticated, + pinPost: isAdmin, + unpinPost: isAdmin, + UpdateDonations: isAdmin, }, User: { - email: isMyOwn, + email: or(isMyOwn, isAdmin), }, }, { @@ -181,5 +156,3 @@ const permissions = shield( fallbackRule: allow, }, ) - -export default permissions diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js index 6cf9dc302..a4f13ea0c 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.js +++ b/backend/src/middleware/permissionsMiddleware.spec.js @@ -1,22 +1,63 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../seed/factories' -import { host, login } from '../jest/helpers' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../server' +import Factory from '../factories' +import { gql } from '../helpers/jest' +import { getDriver, getNeode } from '../db/neo4j' const factory = Factory() +const instance = getNeode() +const driver = getDriver() + +let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator + +const userQuery = gql` + query($name: String) { + User(name: $name) { + email + } + } +` describe('authorization', () => { + beforeAll(async () => { + await factory.cleanDatabase() + const { server } = createServer({ + context: () => ({ + driver, + instance, + user: authenticatedUser, + }), + }) + query = createTestClient(server).query + }) + describe('given two existing users', () => { beforeEach(async () => { - await factory.create('User', { - email: 'owner@example.org', - name: 'Owner', - password: 'iamtheowner', - }) - await factory.create('User', { - email: 'someone@example.org', - name: 'Someone else', - password: 'else', - }) + ;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([ + factory.create('User', { + email: 'owner@example.org', + name: 'Owner', + password: 'iamtheowner', + }), + factory.create('User', { + email: 'another.regular.user@example.org', + name: 'Another Regular User', + password: 'else', + }), + factory.create('User', { + email: 'admin@example.org', + name: 'Admin', + password: 'admin', + role: 'admin', + }), + factory.create('User', { + email: 'moderator@example.org', + name: 'Moderator', + password: 'moderator', + role: 'moderator', + }), + ]) + variables = {} }) afterEach(async () => { @@ -24,66 +65,77 @@ describe('authorization', () => { }) describe('access email address', () => { - let headers = {} - let loginCredentials = null - const action = async () => { - if (loginCredentials) { - headers = await login(loginCredentials) - } - const graphQLClient = new GraphQLClient(host, { headers }) - return graphQLClient.request('{User(name: "Owner") { email } }') - } - - describe('not logged in', () => { - it('rejects', async () => { - await expect(action()).rejects.toThrow('Not Authorised!') - }) - - it("does not expose the owner's email address", async () => { - let response = {} - try { - await action() - } catch (error) { - response = error.response.data - } finally { - expect(response).toEqual({ User: [null] }) - } - }) - }) - - describe('as owner', () => { + describe('unauthenticated', () => { beforeEach(() => { - loginCredentials = { - email: 'owner@example.org', - password: 'iamtheowner', - } + authenticatedUser = null }) - - it("exposes the owner's email address", async () => { - await expect(action()).resolves.toEqual({ User: [{ email: 'owner@example.org' }] }) + it("throws an error and does not expose the owner's email address", async () => { + await expect( + query({ query: userQuery, variables: { name: 'Owner' } }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { User: [null] }, + }) }) }) - describe('authenticated as another user', () => { - beforeEach(async () => { - loginCredentials = { - email: 'someone@example.org', - password: 'else', - } + describe('authenticated', () => { + describe('as the owner', () => { + beforeEach(async () => { + authenticatedUser = await owner.toJson() + }) + + it("exposes the owner's email address", async () => { + variables = { name: 'Owner' } + await expect(query({ query: userQuery, variables })).resolves.toMatchObject({ + data: { User: [{ email: 'owner@example.org' }] }, + errors: undefined, + }) + }) }) - it('rejects', async () => { - await expect(action()).rejects.toThrow('Not Authorised!') + describe('as another regular user', () => { + beforeEach(async () => { + authenticatedUser = await anotherRegularUser.toJson() + }) + + it("throws an error and does not expose the owner's email address", async () => { + await expect( + query({ query: userQuery, variables: { name: 'Owner' } }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { User: [null] }, + }) + }) }) - it("does not expose the owner's email address", async () => { - let response - try { - await action() - } catch (error) { - response = error.response.data - } - expect(response).toEqual({ User: [null] }) + describe('as a moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + }) + + it("throws an error and does not expose the owner's email address", async () => { + await expect( + query({ query: userQuery, variables: { name: 'Owner' } }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { User: [null] }, + }) + }) + }) + + describe('administrator', () => { + beforeEach(async () => { + authenticatedUser = await administrator.toJson() + }) + + it("exposes the owner's email address", async () => { + variables = { name: 'Owner' } + await expect(query({ query: userQuery, variables })).resolves.toMatchObject({ + data: { User: [{ email: 'owner@example.org' }] }, + errors: undefined, + }) + }) }) }) }) diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js index 03d7f8584..1cd3c0b9c 100644 --- a/backend/src/middleware/sluggifyMiddleware.js +++ b/backend/src/middleware/sluggifyMiddleware.js @@ -3,11 +3,20 @@ import uniqueSlug from './slugify/uniqueSlug' const isUniqueFor = (context, type) => { return async slug => { const session = context.driver.session() - const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, { - slug, - }) - session.close() - return response.records.length === 0 + try { + const existingSlug = await session.readTransaction(transaction => { + return transaction.run( + ` + MATCH(p:${type} {slug: $slug }) + RETURN p.slug + `, + { slug }, + ) + }) + return existingSlug.records.length === 0 + } finally { + session.close() + } } } @@ -25,9 +34,5 @@ export default { args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post'))) return resolve(root, args, context, info) }, - CreateCategory: async (resolve, root, args, context, info) => { - args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Category'))) - return resolve(root, args, context, info) - }, }, } diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index e65b7ba6c..cf9f0941c 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -1,6 +1,6 @@ -import Factory from '../seed/factories' -import { gql } from '../jest/helpers' -import { neode as getNeode, getDriver } from '../bootstrap/neo4j' +import Factory from '../factories' +import { gql } from '../helpers/jest' +import { getNeode, getDriver } from '../db/neo4j' import createServer from '../server' import { createTestClient } from 'apollo-server-testing' diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.js b/backend/src/middleware/softDelete/softDeleteMiddleware.js index d3fd13cfc..8be8c3d39 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.js +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.js @@ -3,9 +3,7 @@ const isModerator = ({ user }) => { } const setDefaultFilters = (resolve, root, args, context, info) => { - if (typeof args.deleted !== 'boolean') { - args.deleted = false - } + args.deleted = false if (!isModerator(context)) { args.disabled = false @@ -32,6 +30,7 @@ export default { Post: setDefaultFilters, Comment: setDefaultFilters, User: setDefaultFilters, + profilePagePosts: setDefaultFilters, }, Mutation: async (resolve, root, args, context, info) => { args.disabled = false diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js index 5b04abebd..6e1735af2 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js @@ -1,6 +1,6 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -8,14 +8,8 @@ const factory = Factory() const neode = getNeode() const driver = getDriver() -let query -let mutate -let graphqlQuery const categoryIds = ['cat9'] -let authenticatedUser -let user -let moderator -let troll +let query, graphqlQuery, authenticatedUser, user, moderator, troll const action = () => { return query({ query: graphqlQuery }) @@ -38,18 +32,17 @@ beforeAll(async () => { avatar: '/some/offensive/avatar.jpg', about: 'This self description is very offensive', }), + neode.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }), ]) user = users[0] moderator = users[1] troll = users[2] - await neode.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) - await Promise.all([ user.relateTo(troll, 'following'), factory.create('Post', { @@ -70,33 +63,32 @@ beforeAll(async () => { }), ]) - await Promise.all([ + const resources = await Promise.all([ factory.create('Comment', { author: user, id: 'c2', postId: 'p3', content: 'Enabled comment on public post', }), + factory.create('Post', { + id: 'p2', + author: troll, + title: 'Disabled post', + content: 'This is an offensive post content', + contentExcerpt: 'This is an offensive post content', + image: '/some/offensive/image.jpg', + deleted: false, + categoryIds, + }), + factory.create('Comment', { + id: 'c1', + author: troll, + postId: 'p3', + content: 'Disabled comment', + contentExcerpt: 'Disabled comment', + }), ]) - await factory.create('Post', { - id: 'p2', - author: troll, - title: 'Disabled post', - content: 'This is an offensive post content', - contentExcerpt: 'This is an offensive post content', - image: '/some/offensive/image.jpg', - deleted: false, - categoryIds, - }) - await factory.create('Comment', { - id: 'c1', - author: troll, - postId: 'p3', - content: 'Disabled comment', - contentExcerpt: 'Disabled comment', - }) - const { server } = createServer({ context: () => { return { @@ -108,20 +100,57 @@ beforeAll(async () => { }) const client = createTestClient(server) query = client.query - mutate = client.mutate - authenticatedUser = await moderator.toJson() - const disableMutation = gql` - mutation($id: ID!) { - disable(id: $id) - } - ` - await Promise.all([ - mutate({ mutation: disableMutation, variables: { id: 'c1' } }), - mutate({ mutation: disableMutation, variables: { id: 'u2' } }), - mutate({ mutation: disableMutation, variables: { id: 'p2' } }), + const trollingPost = resources[1] + const trollingComment = resources[2] + + const reports = await Promise.all([ + factory.create('Report'), + factory.create('Report'), + factory.create('Report'), + ]) + const reportAgainstTroll = reports[0] + const reportAgainstTrollingPost = reports[1] + const reportAgainstTrollingComment = reports[2] + + const reportVariables = { + resourceId: 'undefined-resource', + reasonCategory: 'discrimination_etc', + reasonDescription: 'I am what I am !!!', + } + + await Promise.all([ + reportAgainstTroll.relateTo(user, 'filed', { ...reportVariables, resourceId: 'u2' }), + reportAgainstTroll.relateTo(troll, 'belongsTo'), + reportAgainstTrollingPost.relateTo(user, 'filed', { ...reportVariables, resourceId: 'p2' }), + reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'), + reportAgainstTrollingComment.relateTo(moderator, 'filed', { + ...reportVariables, + resourceId: 'c1', + }), + reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'), + ]) + + const disableVariables = { + resourceId: 'undefined-resource', + disable: true, + closed: false, + } + + await Promise.all([ + reportAgainstTroll.relateTo(moderator, 'reviewed', { ...disableVariables, resourceId: 'u2' }), + troll.update({ disabled: true, updatedAt: new Date().toISOString() }), + reportAgainstTrollingPost.relateTo(moderator, 'reviewed', { + ...disableVariables, + resourceId: 'p2', + }), + trollingPost.update({ disabled: true, updatedAt: new Date().toISOString() }), + reportAgainstTrollingComment.relateTo(moderator, 'reviewed', { + ...disableVariables, + resourceId: 'c1', + }), + trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }), ]) - authenticatedUser = null }) afterAll(async () => { @@ -341,76 +370,6 @@ describe('softDeleteMiddleware', () => { }) }) }) - - describe('filter (deleted: true)', () => { - beforeEach(() => { - graphqlQuery = gql` - { - Post(deleted: true) { - title - } - } - ` - }) - - describe('as user', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('throws authorisation error', async () => { - const { data, errors } = await action() - expect(data).toEqual({ Post: null }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('as moderator', () => { - beforeEach(async () => { - authenticatedUser = await moderator.toJson() - }) - - it('does not show deleted posts', async () => { - const expected = { data: { Post: [{ title: 'UNAVAILABLE' }] } } - await expect(action()).resolves.toMatchObject(expected) - }) - }) - }) - - describe('filter (disabled: true)', () => { - beforeEach(() => { - graphqlQuery = gql` - { - Post(disabled: true) { - title - } - } - ` - }) - - describe('as user', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('throws authorisation error', async () => { - const { data, errors } = await action() - expect(data).toEqual({ Post: null }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('as moderator', () => { - beforeEach(async () => { - authenticatedUser = await moderator.toJson() - }) - - it('shows disabled posts', async () => { - const expected = { data: { Post: [{ title: 'Disabled post' }] } } - await expect(action()).resolves.toMatchObject(expected) - }) - }) - }) }) }) }) diff --git a/backend/src/middleware/userMiddleware.js b/backend/src/middleware/userMiddleware.js deleted file mode 100644 index fafbd44e5..000000000 --- a/backend/src/middleware/userMiddleware.js +++ /dev/null @@ -1,16 +0,0 @@ -import createOrUpdateLocations from './nodes/locations' - -export default { - Mutation: { - SignupVerification: async (resolve, root, args, context, info) => { - const result = await resolve(root, args, context, info) - await createOrUpdateLocations(args.id, args.locationName, context.driver) - return result - }, - UpdateUser: async (resolve, root, args, context, info) => { - const result = await resolve(root, args, context, info) - await createOrUpdateLocations(args.id, args.locationName, context.driver) - return result - }, - }, -} diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js index 0ecb6c115..948e1a73a 100644 --- a/backend/src/middleware/validation/validationMiddleware.js +++ b/backend/src/middleware/validation/validationMiddleware.js @@ -4,8 +4,8 @@ const COMMENT_MIN_LENGTH = 1 const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!' const NO_CATEGORIES_ERR_MESSAGE = 'You cannot save a post without at least one category or more than three' - -const validateCommentCreation = async (resolve, root, args, context, info) => { +const USERNAME_MIN_LENGTH = 3 +const validateCreateComment = async (resolve, root, args, context, info) => { const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim() const { postId } = args @@ -13,28 +13,31 @@ const validateCommentCreation = async (resolve, root, args, context, info) => { throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) } const session = context.driver.session() - const postQueryRes = await session.run( - ` - MATCH (post:Post {id: $postId}) - RETURN post`, - { - postId, - }, - ) - session.close() - const [post] = postQueryRes.records.map(record => { - return record.get('post') - }) + try { + const postQueryRes = await session.readTransaction(transaction => { + return transaction.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) - } else { - return resolve(root, args, context, info) + if (!post) { + throw new UserInputError(NO_POST_ERR_MESSAGE) + } else { + return resolve(root, args, context, info) + } + } finally { + session.close() } } const validateUpdateComment = async (resolve, root, args, context, info) => { - const COMMENT_MIN_LENGTH = 1 const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim() if (!args.content || content.length < COMMENT_MIN_LENGTH) { throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) @@ -57,11 +60,88 @@ const validateUpdatePost = async (resolve, root, args, context, info) => { return validatePost(resolve, root, args, context, info) } +const validateReport = async (resolve, root, args, context, info) => { + const { resourceId } = args + const { user } = context + if (resourceId === user.id) throw new Error('You cannot report yourself!') + return resolve(root, args, context, info) +} + +const validateReview = async (resolve, root, args, context, info) => { + const { resourceId } = args + let existingReportedResource + const { user, driver } = context + if (resourceId === user.id) throw new Error('You cannot review yourself!') + const session = driver.session() + const reportReadTxPromise = session.readTransaction(async transaction => { + const validateReviewTransactionResponse = await transaction.run( + ` + MATCH (resource {id: $resourceId}) + WHERE resource:User OR resource:Post OR resource:Comment + OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource) + OPTIONAL MATCH (resource)<-[:WROTE]-(author:User) + RETURN labels(resource)[0] AS label, author, filed + `, + { + resourceId, + submitterId: user.id, + }, + ) + return validateReviewTransactionResponse.records.map(record => ({ + label: record.get('label'), + author: record.get('author'), + filed: record.get('filed'), + })) + }) + try { + const txResult = await reportReadTxPromise + existingReportedResource = txResult + if (!existingReportedResource || !existingReportedResource.length) + throw new Error(`Resource not found or is not a Post|Comment|User!`) + existingReportedResource = existingReportedResource[0] + if (!existingReportedResource.filed) + throw new Error( + `Before starting the review process, please report the ${existingReportedResource.label}!`, + ) + const authorId = + existingReportedResource.label !== 'User' && existingReportedResource.author + ? existingReportedResource.author.properties.id + : null + if (authorId && authorId === user.id) + throw new Error(`You cannot review your own ${existingReportedResource.label}!`) + } finally { + session.close() + } + + return resolve(root, args, context, info) +} + +export const validateNotifyUsers = async (label, reason) => { + const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post'] + if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!') + if ( + (label === 'Post' && reason !== 'mentioned_in_post') || + (label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason)) + ) { + throw new Error('Notification does not fit the reason!') + } +} + +const validateUpdateUser = async (resolve, root, params, context, info) => { + const { name } = params + if (typeof name === 'string' && name.trim().length < USERNAME_MIN_LENGTH) + throw new UserInputError(`Username must be at least ${USERNAME_MIN_LENGTH} character long!`) + return resolve(root, params, context, info) +} + export default { Mutation: { - CreateComment: validateCommentCreation, + CreateComment: validateCreateComment, UpdateComment: validateUpdateComment, CreatePost: validatePost, UpdatePost: validateUpdatePost, + UpdateUser: validateUpdateUser, + fileReport: validateReport, + review: validateReview, }, } diff --git a/backend/src/middleware/validation/validationMiddleware.spec.js b/backend/src/middleware/validation/validationMiddleware.spec.js new file mode 100644 index 000000000..38cd010b4 --- /dev/null +++ b/backend/src/middleware/validation/validationMiddleware.spec.js @@ -0,0 +1,437 @@ +import { gql } from '../../helpers/jest' +import Factory from '../../factories' +import { getNeode, getDriver } from '../../db/neo4j' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../../server' + +const factory = Factory() +const neode = getNeode() +const driver = getDriver() +let authenticatedUser, + mutate, + users, + offensivePost, + reportVariables, + disableVariables, + reportingUser, + moderatingUser, + commentingUser + +const createCommentMutation = gql` + mutation($id: ID, $postId: ID!, $content: String!) { + CreateComment(id: $id, postId: $postId, content: $content) { + id + } + } +` +const updateCommentMutation = gql` + mutation($content: String!, $id: ID!) { + UpdateComment(content: $content, id: $id) { + id + } + } +` +const createPostMutation = gql` + mutation($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) { + CreatePost( + id: $id + title: $title + content: $content + language: $language + categoryIds: $categoryIds + ) { + id + } + } +` + +const updatePostMutation = gql` + mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) { + UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) { + id + } + } +` +const reportMutation = gql` + mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { + fileReport( + resourceId: $resourceId + reasonCategory: $reasonCategory + reasonDescription: $reasonDescription + ) { + id + } + } +` +const reviewMutation = gql` + mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) { + review(resourceId: $resourceId, disable: $disable, closed: $closed) { + createdAt + updatedAt + } + } +` + +const updateUserMutation = gql` + mutation($id: ID!, $name: String) { + UpdateUser(id: $id, name: $name) { + name + } + } +` +beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + user: authenticatedUser, + neode, + driver, + } + }, + }) + mutate = createTestClient(server).mutate +}) + +beforeEach(async () => { + users = await Promise.all([ + factory.create('User', { + id: 'reporting-user', + }), + factory.create('User', { + id: 'moderating-user', + role: 'moderator', + }), + factory.create('User', { + id: 'commenting-user', + }), + ]) + reportVariables = { + resourceId: 'whatever', + reasonCategory: 'other', + reasonDescription: 'Violates code of conduct !!!', + } + disableVariables = { + resourceId: 'undefined-resource', + disable: true, + closed: false, + } + reportingUser = users[0] + moderatingUser = users[1] + commentingUser = users[2] + const posts = await Promise.all([ + factory.create('Post', { + id: 'offensive-post', + authorId: 'moderating-user', + }), + factory.create('Post', { + id: 'post-4-commenting', + authorId: 'commenting-user', + }), + ]) + offensivePost = posts[0] +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('validateCreateComment', () => { + let createCommentVariables + beforeEach(async () => { + createCommentVariables = { + postId: 'whatever', + content: '', + } + authenticatedUser = await commentingUser.toJson() + }) + + it('throws an error if content is empty', async () => { + createCommentVariables = { ...createCommentVariables, postId: 'post-4-commenting' } + await expect( + mutate({ mutation: createCommentMutation, variables: createCommentVariables }), + ).resolves.toMatchObject({ + data: { CreateComment: null }, + errors: [{ message: 'Comment must be at least 1 character long!' }], + }) + }) + + it('sanitizes content and throws an error if not longer than 1 character', async () => { + createCommentVariables = { postId: 'post-4-commenting', content: '' } + await expect( + mutate({ mutation: createCommentMutation, variables: createCommentVariables }), + ).resolves.toMatchObject({ + data: { CreateComment: null }, + errors: [{ message: 'Comment must be at least 1 character long!' }], + }) + }) + + it('throws an error if there is no post with given id in the database', async () => { + createCommentVariables = { + ...createCommentVariables, + postId: 'non-existent-post', + content: 'valid content', + } + await expect( + mutate({ mutation: createCommentMutation, variables: createCommentVariables }), + ).resolves.toMatchObject({ + data: { CreateComment: null }, + errors: [{ message: 'Comment cannot be created without a post!' }], + }) + }) + + describe('validateUpdateComment', () => { + let updateCommentVariables + beforeEach(async () => { + await factory.create('Comment', { + id: 'comment-id', + authorId: 'commenting-user', + }) + updateCommentVariables = { + id: 'whatever', + content: '', + } + authenticatedUser = await commentingUser.toJson() + }) + + it('throws an error if content is empty', async () => { + updateCommentVariables = { ...updateCommentVariables, id: 'comment-id' } + await expect( + mutate({ mutation: updateCommentMutation, variables: updateCommentVariables }), + ).resolves.toMatchObject({ + data: { UpdateComment: null }, + errors: [{ message: 'Comment must be at least 1 character long!' }], + }) + }) + + it('sanitizes content and throws an error if not longer than 1 character', async () => { + updateCommentVariables = { id: 'comment-id', content: '' } + await expect( + mutate({ mutation: updateCommentMutation, variables: updateCommentVariables }), + ).resolves.toMatchObject({ + data: { UpdateComment: null }, + errors: [{ message: 'Comment must be at least 1 character long!' }], + }) + }) + }) + + describe('validatePost', () => { + let createPostVariables + beforeEach(async () => { + createPostVariables = { + title: 'I am a title', + content: 'Some content', + } + authenticatedUser = await commentingUser.toJson() + }) + + describe('categories', () => { + describe('null', () => { + it('throws UserInputError', async () => { + createPostVariables = { ...createPostVariables, categoryIds: null } + await expect( + mutate({ mutation: createPostMutation, variables: createPostVariables }), + ).resolves.toMatchObject({ + data: { CreatePost: null }, + errors: [ + { + message: 'You cannot save a post without at least one category or more than three', + }, + ], + }) + }) + }) + + describe('empty', () => { + it('throws UserInputError', async () => { + createPostVariables = { ...createPostVariables, categoryIds: [] } + await expect( + mutate({ mutation: createPostMutation, variables: createPostVariables }), + ).resolves.toMatchObject({ + data: { CreatePost: null }, + errors: [ + { + message: 'You cannot save a post without at least one category or more than three', + }, + ], + }) + }) + }) + + describe('more than 3 categoryIds', () => { + it('throws UserInputError', async () => { + createPostVariables = { + ...createPostVariables, + categoryIds: ['cat9', 'cat27', 'cat15', 'cat4'], + } + await expect( + mutate({ mutation: createPostMutation, variables: createPostVariables }), + ).resolves.toMatchObject({ + data: { CreatePost: null }, + errors: [ + { + message: 'You cannot save a post without at least one category or more than three', + }, + ], + }) + }) + }) + }) + }) + + describe('validateUpdatePost', () => { + describe('post created without categories somehow', () => { + let owner, updatePostVariables + beforeEach(async () => { + const postSomehowCreated = await neode.create('Post', { + id: 'how-was-this-created', + }) + owner = await neode.create('User', { + id: 'author-of-post-without-category', + slug: 'hacker', + }) + await postSomehowCreated.relateTo(owner, 'author') + authenticatedUser = await owner.toJson() + updatePostVariables = { + id: 'how-was-this-created', + title: 'I am a title', + content: 'Some content', + categoryIds: [], + } + }) + + it('requires at least one category for successful update', async () => { + await expect( + mutate({ mutation: updatePostMutation, variables: updatePostVariables }), + ).resolves.toMatchObject({ + data: { UpdatePost: null }, + errors: [ + { message: 'You cannot save a post without at least one category or more than three' }, + ], + }) + }) + }) + }) +}) + +describe('validateReport', () => { + it('throws an error if a user tries to report themself', async () => { + authenticatedUser = await reportingUser.toJson() + reportVariables = { ...reportVariables, resourceId: 'reporting-user' } + await expect( + mutate({ mutation: reportMutation, variables: reportVariables }), + ).resolves.toMatchObject({ + data: { fileReport: null }, + errors: [{ message: 'You cannot report yourself!' }], + }) + }) +}) + +describe('validateReview', () => { + beforeEach(async () => { + const reportAgainstModerator = await factory.create('Report') + await Promise.all([ + reportAgainstModerator.relateTo(reportingUser, 'filed', { + ...reportVariables, + resourceId: 'moderating-user', + }), + reportAgainstModerator.relateTo(moderatingUser, 'belongsTo'), + ]) + authenticatedUser = await moderatingUser.toJson() + }) + + it('throws an error if a user tries to review a report against them', async () => { + disableVariables = { ...disableVariables, resourceId: 'moderating-user' } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: null }, + errors: [{ message: 'You cannot review yourself!' }], + }) + }) + + it('throws an error for invaild resource', async () => { + disableVariables = { ...disableVariables, resourceId: 'non-existent-resource' } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: null }, + errors: [{ message: 'Resource not found or is not a Post|Comment|User!' }], + }) + }) + + it('throws an error if no report exists', async () => { + disableVariables = { ...disableVariables, resourceId: 'offensive-post' } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: null }, + errors: [{ message: 'Before starting the review process, please report the Post!' }], + }) + }) + + it('throws an error if a moderator tries to review their own resource(Post|Comment)', async () => { + const reportAgainstOffensivePost = await factory.create('Report') + await Promise.all([ + reportAgainstOffensivePost.relateTo(reportingUser, 'filed', { + ...reportVariables, + resourceId: 'offensive-post', + }), + reportAgainstOffensivePost.relateTo(offensivePost, 'belongsTo'), + ]) + disableVariables = { ...disableVariables, resourceId: 'offensive-post' } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: null }, + errors: [{ message: 'You cannot review your own Post!' }], + }) + }) + + describe('moderate a resource that is not a (Comment|Post|User) ', () => { + beforeEach(async () => { + await Promise.all([factory.create('Tag', { id: 'tag-id' })]) + }) + + it('returns null', async () => { + disableVariables = { + ...disableVariables, + resourceId: 'tag-id', + } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: null }, + errors: [{ message: 'Resource not found or is not a Post|Comment|User!' }], + }) + }) + }) + + describe('validateUpdateUser', () => { + let userParams, variables, updatingUser + + beforeEach(async () => { + userParams = { + id: 'updating-user', + name: 'John Doe', + } + + variables = { + id: 'updating-user', + name: 'John Doughnut', + } + updatingUser = await factory.create('User', userParams) + authenticatedUser = await updatingUser.toJson() + }) + + it('with name too short', async () => { + variables = { + ...variables, + name: ' ', + } + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ + data: { UpdateUser: null }, + errors: [{ message: 'Username must be at least 3 character long!' }], + }) + }) + }) +}) diff --git a/backend/src/middleware/xssMiddleware.js b/backend/src/middleware/xssMiddleware.js index f98ab9d61..9b4e3e759 100644 --- a/backend/src/middleware/xssMiddleware.js +++ b/backend/src/middleware/xssMiddleware.js @@ -85,7 +85,7 @@ function clean(dirty) { return dirty } -const fields = ['content', 'contentExcerpt'] +const fields = ['content', 'contentExcerpt', 'reasonDescription'] export default { Mutation: async (resolve, root, args, context, info) => { diff --git a/backend/src/models/Badge.js b/backend/src/models/Badge.js index 6968a056b..9c4831041 100644 --- a/backend/src/models/Badge.js +++ b/backend/src/models/Badge.js @@ -1,4 +1,4 @@ -module.exports = { +export default { id: { type: 'string', primary: true, lowercase: true }, status: { type: 'string', valid: ['permanent', 'temporary'] }, type: { type: 'string', valid: ['role', 'crowdfunding'] }, diff --git a/backend/src/models/Category.js b/backend/src/models/Category.js index d8f1bff6f..223bb4f87 100644 --- a/backend/src/models/Category.js +++ b/backend/src/models/Category.js @@ -1,9 +1,9 @@ import uuid from 'uuid/v4' -module.exports = { +export default { id: { type: 'string', primary: true, default: uuid }, name: { type: 'string', required: true, default: false }, - slug: { type: 'string' }, + slug: { type: 'string', unique: 'true' }, icon: { type: 'string', required: true, default: false }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { diff --git a/backend/src/models/Comment.js b/backend/src/models/Comment.js index c89103e5d..773152541 100644 --- a/backend/src/models/Comment.js +++ b/backend/src/models/Comment.js @@ -1,6 +1,6 @@ import uuid from 'uuid/v4' -module.exports = { +export default { id: { type: 'string', primary: true, default: uuid }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { @@ -25,12 +25,6 @@ module.exports = { target: 'User', direction: 'in', }, - disabledBy: { - type: 'relationship', - relationship: 'DISABLED', - target: 'User', - direction: 'in', - }, notified: { type: 'relationship', relationship: 'NOTIFIED', diff --git a/backend/src/models/Donations.js b/backend/src/models/Donations.js new file mode 100644 index 000000000..45d737f85 --- /dev/null +++ b/backend/src/models/Donations.js @@ -0,0 +1,14 @@ +import uuid from 'uuid/v4' + +export default { + id: { type: 'string', primary: true, default: uuid }, + goal: { type: 'number' }, + progress: { type: 'number' }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + updatedAt: { + type: 'string', + isoDate: true, + required: true, + default: () => new Date().toISOString(), + }, +} diff --git a/backend/src/models/EmailAddress.js b/backend/src/models/EmailAddress.js index 6afccd1ed..fa2aa0b27 100644 --- a/backend/src/models/EmailAddress.js +++ b/backend/src/models/EmailAddress.js @@ -1,4 +1,4 @@ -module.exports = { +export default { email: { type: 'string', primary: true, lowercase: true, email: true }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, verifiedAt: { type: 'string', isoDate: true }, diff --git a/backend/src/models/InvitationCode.js b/backend/src/models/InvitationCode.js index f137f6c15..138289faf 100644 --- a/backend/src/models/InvitationCode.js +++ b/backend/src/models/InvitationCode.js @@ -1,4 +1,4 @@ -module.exports = { +export default { createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, token: { type: 'string', primary: true, token: true }, generatedBy: { diff --git a/backend/src/models/Location.js b/backend/src/models/Location.js index bd6e0b5d9..d33186da4 100644 --- a/backend/src/models/Location.js +++ b/backend/src/models/Location.js @@ -1,4 +1,4 @@ -module.exports = { +export default { id: { type: 'string', primary: true }, lat: { type: 'number' }, lng: { type: 'number' }, @@ -12,6 +12,7 @@ module.exports = { nameDE: { type: 'string' }, nameNL: { type: 'string' }, namePL: { type: 'string' }, + nameRU: { type: 'string' }, isIn: { type: 'relationship', relationship: 'IS_IN', diff --git a/backend/src/models/Migration.js b/backend/src/models/Migration.js new file mode 100644 index 000000000..8f16b800a --- /dev/null +++ b/backend/src/models/Migration.js @@ -0,0 +1,5 @@ +export default { + title: { type: 'string', primary: true, token: true }, + description: { type: 'string' }, + migratedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, +} diff --git a/backend/src/models/Post.js b/backend/src/models/Post.js index 5ac8378c2..2b553232e 100644 --- a/backend/src/models/Post.js +++ b/backend/src/models/Post.js @@ -1,6 +1,6 @@ import uuid from 'uuid/v4' -module.exports = { +export default { id: { type: 'string', primary: true, default: uuid }, activityId: { type: 'string', allow: [null] }, objectId: { type: 'string', allow: [null] }, @@ -11,18 +11,12 @@ module.exports = { direction: 'in', }, title: { type: 'string', disallow: [null], min: 3 }, - slug: { type: 'string', allow: [null] }, + slug: { type: 'string', allow: [null], unique: 'true' }, content: { type: 'string', disallow: [null], min: 3 }, contentExcerpt: { type: 'string', allow: [null] }, image: { type: 'string', allow: [null] }, deleted: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, - disabledBy: { - type: 'relationship', - relationship: 'DISABLED', - target: 'User', - direction: 'in', - }, notified: { type: 'relationship', relationship: 'NOTIFIED', @@ -45,4 +39,8 @@ module.exports = { default: () => new Date().toISOString(), }, language: { type: 'string', allow: [null] }, + imageBlurred: { type: 'boolean', default: false }, + imageAspectRatio: { type: 'float', default: 1.0 }, + pinned: { type: 'boolean', default: null, valid: [null, true] }, + pinnedAt: { type: 'string', isoDate: true }, } diff --git a/backend/src/models/Report.js b/backend/src/models/Report.js new file mode 100644 index 000000000..93876f404 --- /dev/null +++ b/backend/src/models/Report.js @@ -0,0 +1,52 @@ +import uuid from 'uuid/v4' + +export default { + id: { type: 'string', primary: true, default: uuid }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + rule: { type: 'string', default: 'latestReviewUpdatedAtRules' }, + closed: { type: 'boolean', default: false }, + belongsTo: { + type: 'relationship', + relationship: 'BELONGS_TO', + target: ['User', 'Comment', 'Post'], + direction: 'out', + }, + filed: { + type: 'relationship', + relationship: 'FILED', + target: 'User', + direction: 'in', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + resourceId: { type: 'string', primary: true, default: uuid }, + reasonCategory: { + type: 'string', + valid: [ + 'other', + 'discrimination_etc', + 'pornographic_content_links', + 'glorific_trivia_of_cruel_inhuman_acts', + 'doxing', + 'intentional_intimidation_stalking_persecution', + 'advert_products_services_commercial', + 'criminal_behavior_violation_german_law', + ], + invalid: [null], + }, + reasonDescription: { type: 'string', allow: [null] }, + }, + }, + reviewed: { + type: 'relationship', + relationship: 'REVIEWED', + target: 'User', + direction: 'in', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + disable: { type: 'boolean', default: false }, + closed: { type: 'boolean', default: false }, + }, + }, +} diff --git a/backend/src/models/SocialMedia.js b/backend/src/models/SocialMedia.js index 42d2da30e..abdb12b02 100644 --- a/backend/src/models/SocialMedia.js +++ b/backend/src/models/SocialMedia.js @@ -1,6 +1,6 @@ import uuid from 'uuid/v4' -module.exports = { +export default { id: { type: 'string', primary: true, default: uuid }, url: { type: 'string', uri: true, required: true }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, diff --git a/backend/src/models/Tag.js b/backend/src/models/Tag.js index 90b5f8772..8df60c761 100644 --- a/backend/src/models/Tag.js +++ b/backend/src/models/Tag.js @@ -1,4 +1,4 @@ -module.exports = { +export default { id: { type: 'string', primary: true }, deleted: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, diff --git a/backend/src/models/UnverifiedEmailAddress.js b/backend/src/models/UnverifiedEmailAddress.js new file mode 100644 index 000000000..c582ed011 --- /dev/null +++ b/backend/src/models/UnverifiedEmailAddress.js @@ -0,0 +1,12 @@ +export default { + email: { type: 'string', lowercase: true, email: true }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + nonce: { type: 'string', token: true }, + belongsTo: { + type: 'relationship', + relationship: 'BELONGS_TO', + target: 'User', + direction: 'out', + eager: true, + }, +} diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 465abfb65..c3ac434ec 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -1,10 +1,10 @@ import uuid from 'uuid/v4' -module.exports = { +export default { id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests actorId: { type: 'string', allow: [null] }, name: { type: 'string', disallow: [null], min: 3 }, - slug: 'string', + slug: { type: 'string', unique: 'true', regex: /^[a-z0-9_-]+$/, lowercase: true }, encryptedPassword: 'string', avatar: { type: 'string', allow: [null] }, coverImg: { type: 'string', allow: [null] }, @@ -16,7 +16,7 @@ module.exports = { wasInvited: 'boolean', wasSeeded: 'boolean', locationName: { type: 'string', allow: [null] }, - about: { type: 'string', allow: [null] }, + about: { type: 'string', allow: [null, ''] }, primaryEmail: { type: 'relationship', relationship: 'PRIMARY_EMAIL', @@ -28,20 +28,20 @@ module.exports = { relationship: 'FOLLOWS', target: 'User', direction: 'out', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, }, followedBy: { type: 'relationship', relationship: 'FOLLOWS', target: 'User', direction: 'in', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, }, friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' }, - disabledBy: { - type: 'relationship', - relationship: 'DISABLED', - target: 'User', - direction: 'in', - }, rewarded: { type: 'relationship', relationship: 'REWARDED', @@ -49,6 +49,7 @@ module.exports = { direction: 'in', }, invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' }, + lastActiveAt: { type: 'string', isoDate: true }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { type: 'string', @@ -77,6 +78,12 @@ module.exports = { target: 'User', direction: 'out', }, + muted: { + type: 'relationship', + relationship: 'MUTED', + target: 'User', + direction: 'out', + }, notifications: { type: 'relationship', relationship: 'NOTIFIED', @@ -97,6 +104,9 @@ module.exports = { relationship: 'SHOUTED', target: 'Post', direction: 'out', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, }, isIn: { type: 'relationship', @@ -104,4 +114,25 @@ module.exports = { target: 'Location', direction: 'out', }, + pinned: { + type: 'relationship', + relationship: 'PINNED', + target: 'Post', + direction: 'out', + properties: { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, + }, + allowEmbedIframes: { + type: 'boolean', + default: false, + }, + showShoutsPublicly: { + type: 'boolean', + default: false, + }, + locale: { + type: 'string', + allow: [null], + }, } diff --git a/backend/src/models/User.spec.js b/backend/src/models/User.spec.js index e00136970..7bdde7014 100644 --- a/backend/src/models/User.spec.js +++ b/backend/src/models/User.spec.js @@ -1,8 +1,8 @@ -import Factory from '../seed/factories' -import { neode } from '../bootstrap/neo4j' +import Factory from '../factories' +import { getNeode } from '../db/neo4j' const factory = Factory() -const instance = neode() +const neode = getNeode() afterEach(async () => { await factory.cleanDatabase() @@ -10,7 +10,7 @@ afterEach(async () => { describe('role', () => { it('defaults to `user`', async () => { - const user = await instance.create('User', { name: 'John' }) + const user = await neode.create('User', { name: 'John' }) await expect(user.toJson()).resolves.toEqual( expect.objectContaining({ role: 'user', @@ -18,3 +18,63 @@ describe('role', () => { ) }) }) + +describe('slug', () => { + it('normalizes to lowercase letters', async () => { + const user = await neode.create('User', { slug: 'Matt' }) + await expect(user.toJson()).resolves.toEqual( + expect.objectContaining({ + slug: 'matt', + }), + ) + }) + + it('must be unique', async done => { + await neode.create('User', { slug: 'Matt' }) + try { + await expect(neode.create('User', { slug: 'Matt' })).rejects.toThrow('already exists') + done() + } catch (error) { + throw new Error(` + ${error} + + Probably your database has no unique constraints! + + To see all constraints go to http://localhost:7474/browser/ and + paste the following: + \`\`\` + CALL db.constraints(); + \`\`\` + + Learn how to setup the database here: + https://docs.human-connection.org/human-connection/neo4j + `) + } + }) + + describe('characters', () => { + const createUser = attrs => { + return neode.create('User', attrs).then(user => user.toJson()) + } + + it('-', async () => { + await expect(createUser({ slug: 'matt-rider' })).resolves.toMatchObject({ + slug: 'matt-rider', + }) + }) + + it('_', async () => { + await expect(createUser({ slug: 'matt_rider' })).resolves.toMatchObject({ + slug: 'matt_rider', + }) + }) + + it(' ', async () => { + await expect(createUser({ slug: 'matt rider' })).rejects.toThrow('ERROR_VALIDATION') + }) + + it('ä', async () => { + await expect(createUser({ slug: 'mätt' })).rejects.toThrow('ERROR_VALIDATION') + }) + }) +}) diff --git a/backend/src/models/index.js b/backend/src/models/index.js index a7d3c8252..dbb6a927e 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -1,14 +1,17 @@ // NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm // module that is not browser-compatible. Node's `fs` module is server-side only export default { - Badge: require('./Badge.js'), - User: require('./User.js'), - InvitationCode: require('./InvitationCode.js'), - EmailAddress: require('./EmailAddress.js'), - SocialMedia: require('./SocialMedia.js'), - Post: require('./Post.js'), - Comment: require('./Comment.js'), - Category: require('./Category.js'), - Tag: require('./Tag.js'), - Location: require('./Location.js'), + Badge: require('./Badge.js').default, + User: require('./User.js').default, + EmailAddress: require('./EmailAddress.js').default, + UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js').default, + SocialMedia: require('./SocialMedia.js').default, + Post: require('./Post.js').default, + Comment: require('./Comment.js').default, + Category: require('./Category.js').default, + Tag: require('./Tag.js').default, + Location: require('./Location.js').default, + Donations: require('./Donations.js').default, + Report: require('./Report.js').default, + Migration: require('./Migration.js').default, } diff --git a/backend/src/schema/helpers.js b/backend/src/schema/helpers.js deleted file mode 100644 index fe61ccf57..000000000 --- a/backend/src/schema/helpers.js +++ /dev/null @@ -1,9 +0,0 @@ -export const undefinedToNull = list => { - const resolvers = {} - list.forEach(key => { - resolvers[key] = async (parent, params, context, resolveInfo) => { - return typeof parent[key] === 'undefined' ? null : parent[key] - } - }) - return resolvers -} diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js index 4d1b9574e..274697238 100644 --- a/backend/src/schema/index.js +++ b/backend/src/schema/index.js @@ -1,53 +1,28 @@ import { makeAugmentedSchema } from 'neo4j-graphql-js' -import CONFIG from './../config' -import applyScalars from './../bootstrap/scalars' -import applyDirectives from './../bootstrap/directives' import typeDefs from './types' import resolvers from './resolvers' -export default applyScalars( - applyDirectives( - makeAugmentedSchema({ - typeDefs, - resolvers, - config: { - query: { - exclude: [ - 'Badge', - 'Embed', - 'InvitationCode', - 'EmailAddress', - 'Notfication', - 'Statistics', - 'LoggedInUser', - 'Location', - 'SocialMedia', - 'NOTIFIED', - ], - // add 'User' here as soon as possible - }, - mutation: { - exclude: [ - 'Badge', - 'Embed', - 'InvitationCode', - 'EmailAddress', - 'Notfication', - 'Post', - 'Comment', - 'Report', - 'Statistics', - 'LoggedInUser', - 'Location', - 'SocialMedia', - 'User', - 'EMOTED', - 'NOTIFIED', - ], - // add 'User' here as soon as possible - }, - debug: !!CONFIG.DEBUG, - }, - }), - ), -) +export default makeAugmentedSchema({ + typeDefs, + resolvers, + config: { + query: { + exclude: [ + 'Badge', + 'Embed', + 'EmailAddress', + 'Notfication', + 'Statistics', + 'LoggedInUser', + 'Location', + 'SocialMedia', + 'NOTIFIED', + 'FILED', + 'REVIEWED', + 'Report', + 'Donations', + ], + }, + mutation: false, + }, +}) diff --git a/backend/src/schema/resolvers/badges.js b/backend/src/schema/resolvers/badges.js index 19bc24fd6..d10d6b482 100644 --- a/backend/src/schema/resolvers/badges.js +++ b/backend/src/schema/resolvers/badges.js @@ -3,7 +3,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { Query: { Badge: async (object, args, context, resolveInfo) => { - return neo4jgraphql(object, args, context, resolveInfo, false) + return neo4jgraphql(object, args, context, resolveInfo) }, }, } diff --git a/backend/src/schema/resolvers/comments.js b/backend/src/schema/resolvers/comments.js index 7378238bb..864d9412c 100644 --- a/backend/src/schema/resolvers/comments.js +++ b/backend/src/schema/resolvers/comments.js @@ -5,6 +5,7 @@ export default { Mutation: { CreateComment: async (object, params, context, resolveInfo) => { const { postId } = params + const { user, driver } = context // 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 @@ -12,55 +13,79 @@ export default { delete params.postId params.id = params.id || uuid() - const session = context.driver.session() - const createCommentCypher = ` - MATCH (post:Post {id: $postId}) - MATCH (author:User {id: $userId}) - WITH post, author - CREATE (comment:Comment {params}) - SET comment.createdAt = toString(datetime()) - SET comment.updatedAt = toString(datetime()) - MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author) - RETURN comment - ` - const transactionRes = await session.run(createCommentCypher, { - userId: context.user.id, - postId, - params, + const session = driver.session() + + const writeTxResultPromise = session.writeTransaction(async transaction => { + const createCommentTransactionResponse = await transaction.run( + ` + MATCH (post:Post {id: $postId}) + MATCH (author:User {id: $userId}) + WITH post, author + CREATE (comment:Comment {params}) + SET comment.createdAt = toString(datetime()) + SET comment.updatedAt = toString(datetime()) + MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author) + RETURN comment + `, + { userId: user.id, postId, params }, + ) + return createCommentTransactionResponse.records.map( + record => record.get('comment').properties, + ) }) - session.close() - - const [comment] = transactionRes.records.map(record => record.get('comment').properties) - - return comment + try { + const [comment] = await writeTxResultPromise + return comment + } finally { + session.close() + } }, UpdateComment: async (_parent, params, context, _resolveInfo) => { const session = context.driver.session() - const updateCommentCypher = ` - MATCH (comment:Comment {id: $params.id}) - SET comment += $params - SET comment.updatedAt = toString(datetime()) - RETURN comment - ` - const transactionRes = await session.run(updateCommentCypher, { params }) - session.close() - const [comment] = transactionRes.records.map(record => record.get('comment').properties) - return comment + const writeTxResultPromise = session.writeTransaction(async transaction => { + const updateCommentTransactionResponse = await transaction.run( + ` + MATCH (comment:Comment {id: $params.id}) + SET comment += $params + SET comment.updatedAt = toString(datetime()) + RETURN comment + `, + { params }, + ) + return updateCommentTransactionResponse.records.map( + record => record.get('comment').properties, + ) + }) + try { + const [comment] = await writeTxResultPromise + return comment + } finally { + session.close() + } }, DeleteComment: async (_parent, args, context, _resolveInfo) => { const session = context.driver.session() - const transactionRes = await session.run( - ` - MATCH (comment:Comment {id: $commentId}) - SET comment.deleted = TRUE - SET comment.content = 'UNAVAILABLE' - SET comment.contentExcerpt = 'UNAVAILABLE' - RETURN comment - `, - { commentId: args.id }, - ) - const [comment] = transactionRes.records.map(record => record.get('comment').properties) - return comment + const writeTxResultPromise = session.writeTransaction(async transaction => { + const deleteCommentTransactionResponse = await transaction.run( + ` + MATCH (comment:Comment {id: $commentId}) + SET comment.deleted = TRUE + SET comment.content = 'UNAVAILABLE' + SET comment.contentExcerpt = 'UNAVAILABLE' + RETURN comment + `, + { commentId: args.id }, + ) + return deleteCommentTransactionResponse.records.map( + record => record.get('comment').properties, + ) + }) + try { + const [comment] = await writeTxResultPromise + return comment + } finally { + session.close() + } }, }, Comment: { @@ -68,7 +93,6 @@ export default { hasOne: { author: '<-[:WROTE]-(related:User)', post: '-[:COMMENTS]->(related:Post)', - disabledBy: '<-[:DISABLED]-(related:User)', }, }), }, diff --git a/backend/src/schema/resolvers/comments.spec.js b/backend/src/schema/resolvers/comments.spec.js index ba7364a7f..9877161db 100644 --- a/backend/src/schema/resolvers/comments.spec.js +++ b/backend/src/schema/resolvers/comments.spec.js @@ -1,8 +1,8 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' import { createTestClient } from 'apollo-server-testing' import createServer from '../../server' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import { getNeode, getDriver } from '../../db/neo4j' const driver = getDriver() const neode = getNeode() @@ -10,7 +10,8 @@ const factory = Factory() let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment -beforeAll(() => { +beforeAll(async () => { + await factory.cleanDatabase() const { server } = createServer({ context: () => { return { @@ -19,8 +20,7 @@ beforeAll(() => { } }, }) - const client = createTestClient(server) - mutate = client.mutate + mutate = createTestClient(server).mutate }) beforeEach(async () => { @@ -100,6 +100,7 @@ describe('CreateComment', () => { await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject( { data: { CreateComment: { content: "I'm authorised to comment" } }, + errors: undefined, }, ) }) @@ -108,45 +109,10 @@ describe('CreateComment', () => { await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject( { data: { CreateComment: { author: { name: 'Author' } } }, + errors: undefined, }, ) }) - - describe('comment content is empty', () => { - beforeEach(() => { - variables = { ...variables, content: '

' } - }) - - it('throw UserInput error', async () => { - const { data, errors } = await mutate({ mutation: createCommentMutation, variables }) - expect(data).toEqual({ CreateComment: null }) - expect(errors[0]).toHaveProperty('message', 'Comment must be at least 1 character long!') - }) - }) - - describe('comment content contains only whitespaces', () => { - beforeEach(() => { - variables = { ...variables, content: '

' } - }) - - it('throw UserInput error', async () => { - const { data, errors } = await mutate({ mutation: createCommentMutation, variables }) - expect(data).toEqual({ CreateComment: null }) - expect(errors[0]).toHaveProperty('message', 'Comment must be at least 1 character long!') - }) - }) - - describe('invalid post id', () => { - beforeEach(() => { - variables = { ...variables, postId: 'does-not-exist' } - }) - - it('throw UserInput error', async () => { - const { data, errors } = await mutate({ mutation: createCommentMutation, variables }) - expect(data).toEqual({ CreateComment: null }) - expect(errors[0]).toHaveProperty('message', 'Comment cannot be created without a post!') - }) - }) }) }) }) @@ -193,6 +159,7 @@ describe('UpdateComment', () => { it('updates the comment', async () => { const expected = { data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } }, + errors: undefined, } await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject( expected, @@ -208,6 +175,7 @@ describe('UpdateComment', () => { createdAt: expect.any(String), }, }, + errors: undefined, } await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject( expected, @@ -226,17 +194,6 @@ describe('UpdateComment', () => { expect(newlyCreatedComment.updatedAt).not.toEqual(UpdateComment.updatedAt) }) - describe('if `content` empty', () => { - beforeEach(() => { - variables = { ...variables, content: '

' } - }) - - it('throws InputError', async () => { - const { errors } = await mutate({ mutation: updateCommentMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Comment must be at least 1 character long!') - }) - }) - describe('if comment does not exist for given id', () => { beforeEach(() => { variables = { ...variables, id: 'does-not-exist' } diff --git a/backend/src/schema/resolvers/donations.js b/backend/src/schema/resolvers/donations.js new file mode 100644 index 000000000..3052ff13d --- /dev/null +++ b/backend/src/schema/resolvers/donations.js @@ -0,0 +1,32 @@ +export default { + Mutation: { + UpdateDonations: async (_parent, params, context, _resolveInfo) => { + const { driver } = context + let donations + const session = driver.session() + const writeTxResultPromise = session.writeTransaction(async txc => { + const updateDonationsTransactionResponse = await txc.run( + ` + MATCH (donations:Donations) + WITH donations LIMIT 1 + SET donations += $params + SET donations.updatedAt = toString(datetime()) + RETURN donations + `, + { params }, + ) + return updateDonationsTransactionResponse.records.map( + record => record.get('donations').properties, + ) + }) + try { + const txResult = await writeTxResultPromise + if (!txResult[0]) return null + donations = txResult[0] + } finally { + session.close() + } + return donations + }, + }, +} diff --git a/backend/src/schema/resolvers/donations.spec.js b/backend/src/schema/resolvers/donations.spec.js new file mode 100644 index 000000000..c382eb475 --- /dev/null +++ b/backend/src/schema/resolvers/donations.spec.js @@ -0,0 +1,174 @@ +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' + +let mutate, query, authenticatedUser, variables +const factory = Factory() +const instance = getNeode() +const driver = getDriver() + +const updateDonationsMutation = gql` + mutation($goal: Int, $progress: Int) { + UpdateDonations(goal: $goal, progress: $progress) { + id + goal + progress + createdAt + updatedAt + } + } +` +const donationsQuery = gql` + query { + Donations { + id + goal + progress + } + } +` + +describe('donations', () => { + let currentUser, newlyCreatedDonations + beforeAll(async () => { + await factory.cleanDatabase() + authenticatedUser = undefined + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate + query = createTestClient(server).query + }) + + beforeEach(async () => { + variables = {} + newlyCreatedDonations = await factory.create('Donations') + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('query for donations', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = undefined + await expect(query({ query: donationsQuery, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + currentUser = await factory.create('User', { + id: 'normal-user', + role: 'user', + }) + authenticatedUser = await currentUser.toJson() + }) + + it('returns the current Donations info', async () => { + await expect(query({ query: donationsQuery, variables })).resolves.toMatchObject({ + data: { Donations: [{ goal: 15000, progress: 0 }] }, + }) + }) + }) + }) + }) + + describe('update donations', () => { + beforeEach(() => { + variables = { goal: 20000, progress: 3000 } + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = undefined + await expect( + mutate({ mutation: updateDonationsMutation, variables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + + describe('authenticated', () => { + describe('as a normal user', () => { + beforeEach(async () => { + currentUser = await factory.create('User', { + id: 'normal-user', + role: 'user', + }) + authenticatedUser = await currentUser.toJson() + }) + + it('throws authorization error', async () => { + await expect( + mutate({ mutation: updateDonationsMutation, variables }), + ).resolves.toMatchObject({ + data: { UpdateDonations: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('as a moderator', () => { + beforeEach(async () => { + currentUser = await factory.create('User', { + id: 'moderator', + role: 'moderator', + }) + authenticatedUser = await currentUser.toJson() + }) + + it('throws authorization error', async () => { + await expect( + mutate({ mutation: updateDonationsMutation, variables }), + ).resolves.toMatchObject({ + data: { UpdateDonations: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('as an admin', () => { + beforeEach(async () => { + currentUser = await factory.create('User', { + id: 'admin', + role: 'admin', + }) + authenticatedUser = await currentUser.toJson() + }) + + it('updates Donations info', async () => { + await expect( + mutate({ mutation: updateDonationsMutation, variables }), + ).resolves.toMatchObject({ + data: { UpdateDonations: { goal: 20000, progress: 3000 } }, + errors: undefined, + }) + }) + + it('updates the updatedAt attribute', async () => { + newlyCreatedDonations = await newlyCreatedDonations.toJson() + const { + data: { UpdateDonations }, + } = await mutate({ mutation: updateDonationsMutation, variables }) + expect(newlyCreatedDonations.updatedAt).toBeTruthy() + expect(Date.parse(newlyCreatedDonations.updatedAt)).toEqual(expect.any(Number)) + expect(UpdateDonations.updatedAt).toBeTruthy() + expect(Date.parse(UpdateDonations.updatedAt)).toEqual(expect.any(Number)) + expect(newlyCreatedDonations.updatedAt).not.toEqual(UpdateDonations.updatedAt) + }) + }) + }) + }) + }) +}) diff --git a/backend/src/schema/resolvers/emails.js b/backend/src/schema/resolvers/emails.js new file mode 100644 index 000000000..8711a5996 --- /dev/null +++ b/backend/src/schema/resolvers/emails.js @@ -0,0 +1,95 @@ +import generateNonce from './helpers/generateNonce' +import Resolver from './helpers/Resolver' +import existingEmailAddress from './helpers/existingEmailAddress' +import { UserInputError } from 'apollo-server' +import Validator from 'neode/build/Services/Validator.js' +import normalizeEmail from './helpers/normalizeEmail' + +export default { + Mutation: { + AddEmailAddress: async (_parent, args, context, _resolveInfo) => { + let response + args.email = normalizeEmail(args.email) + + try { + const { neode } = context + await new Validator(neode, neode.model('UnverifiedEmailAddress'), args) + } catch (e) { + throw new UserInputError('must be a valid email') + } + + // check email does not belong to anybody + await existingEmailAddress({ args, context }) + + const nonce = generateNonce() + const { + user: { id: userId }, + } = context + + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async txc => { + const result = await txc.run( + ` + MATCH (user:User {id: $userId}) + MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce}) + SET email.createdAt = toString(datetime()) + RETURN email, user + `, + { userId, email: args.email, nonce }, + ) + return result.records.map(record => ({ + name: record.get('user').properties.name, + ...record.get('email').properties, + })) + }) + try { + const txResult = await writeTxResultPromise + response = txResult[0] + } finally { + session.close() + } + return response + }, + VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => { + let response + const { + user: { id: userId }, + } = context + const { nonce, email } = args + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async txc => { + const result = await txc.run( + ` + MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress) + MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce}) + MERGE (user)-[:PRIMARY_EMAIL]->(email) + SET email:EmailAddress + SET email.verifiedAt = toString(datetime()) + REMOVE email:UnverifiedEmailAddress + DETACH DELETE previous + RETURN email + `, + { userId, email, nonce }, + ) + return result.records.map(record => record.get('email').properties) + }) + try { + const txResult = await writeTxResultPromise + response = txResult[0] + } catch (e) { + if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') + throw new UserInputError('A user account with this email already exists.') + throw new Error(e) + } finally { + session.close() + } + if (!response) throw new UserInputError('Invalid nonce or no email address found.') + return response + }, + }, + EmailAddress: { + ...Resolver('EmailAddress', { + undefinedToNull: ['verifiedAt'], + }), + }, +} diff --git a/backend/src/schema/resolvers/emails.spec.js b/backend/src/schema/resolvers/emails.spec.js new file mode 100644 index 000000000..97a1f0c29 --- /dev/null +++ b/backend/src/schema/resolvers/emails.spec.js @@ -0,0 +1,298 @@ +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getDriver, getNeode } from '../../db/neo4j' +import createServer from '../../server' +import { createTestClient } from 'apollo-server-testing' + +const factory = Factory() +const neode = getNeode() + +let mutate +let authenticatedUser +let user +let variables +const driver = getDriver() + +beforeEach(async () => { + variables = {} +}) + +beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('AddEmailAddress', () => { + const mutation = gql` + mutation($email: String!) { + AddEmailAddress(email: $email) { + email + verifiedAt + createdAt + } + } + ` + beforeEach(() => { + variables = { ...variables, email: 'new-email@example.org' } + }) + + describe('unauthenticated', () => { + beforeEach(() => { + authenticatedUser = null + }) + + it('throws AuthorizationError', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { AddEmailAddress: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + user = await factory.create('User', { id: '567', email: 'user@example.org' }) + authenticatedUser = await user.toJson() + }) + + describe('email attribute is not a valid email', () => { + beforeEach(() => { + variables = { ...variables, email: 'foobar' } + }) + + it('throws UserInputError', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { AddEmailAddress: null }, + errors: [{ message: 'must be a valid email' }], + }) + }) + }) + + describe('email attribute is a valid email', () => { + it('creates a new unverified `EmailAddress` node', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { + AddEmailAddress: { + email: 'new-email@example.org', + verifiedAt: null, + createdAt: expect.any(String), + }, + }, + errors: undefined, + }) + }) + + it('connects `UnverifiedEmailAddress` to the authenticated user', async () => { + await mutate({ mutation, variables }) + const result = await neode.cypher(` + MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"}) + MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"}) + RETURN e + `) + const email = neode.hydrateFirst(result, 'e', neode.model('UnverifiedEmailAddress')) + await expect(email.toJson()).resolves.toMatchObject({ + email: 'new-email@example.org', + nonce: expect.any(String), + }) + }) + + describe('if another `UnverifiedEmailAddress` node already exists with that email', () => { + it('throws no unique constraint violation error', async () => { + await factory.create('UnverifiedEmailAddress', { + createdAt: '2019-09-24T14:00:01.565Z', + email: 'new-email@example.org', + }) + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { + AddEmailAddress: { + email: 'new-email@example.org', + verifiedAt: null, + }, + }, + errors: undefined, + }) + }) + }) + + describe('but if another user owns an `EmailAddress` already with that email', () => { + it('throws UserInputError because of unique constraints', async () => { + await factory.create('User', { email: 'new-email@example.org' }) + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { AddEmailAddress: null }, + errors: [{ message: 'A user account with this email already exists.' }], + }) + }) + }) + }) + }) +}) + +describe('VerifyEmailAddress', () => { + const mutation = gql` + mutation($email: String!, $nonce: String!) { + VerifyEmailAddress(email: $email, nonce: $nonce) { + email + createdAt + verifiedAt + } + } + ` + + beforeEach(() => { + variables = { ...variables, email: 'to-be-verified@example.org', nonce: '123456' } + }) + + describe('unauthenticated', () => { + beforeEach(() => { + authenticatedUser = null + }) + + it('throws AuthorizationError', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + user = await factory.create('User', { id: '567', email: 'user@example.org' }) + authenticatedUser = await user.toJson() + }) + + describe('if no unverified `EmailAddress` node exists', () => { + it('throws UserInputError', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'Invalid nonce or no email address found.' }], + }) + }) + }) + + describe('given a `UnverifiedEmailAddress`', () => { + let emailAddress + beforeEach(async () => { + emailAddress = await factory.create('UnverifiedEmailAddress', { + nonce: 'abcdef', + verifiedAt: null, + createdAt: new Date().toISOString(), + email: 'to-be-verified@example.org', + }) + }) + + describe('given invalid nonce', () => { + it('throws UserInputError', async () => { + variables.nonce = 'asdfgh' + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'Invalid nonce or no email address found.' }], + }) + }) + }) + + describe('given valid nonce for `UnverifiedEmailAddress` node', () => { + beforeEach(() => { + variables = { ...variables, nonce: 'abcdef' } + }) + + describe('but the address does not belong to the authenticated user', () => { + it('throws UserInputError', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'Invalid nonce or no email address found.' }], + }) + }) + }) + + describe('and the `UnverifiedEmailAddress` belongs to the authenticated user', () => { + beforeEach(async () => { + await emailAddress.relateTo(user, 'belongsTo') + }) + + it('adds `verifiedAt`', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { + VerifyEmailAddress: { + email: 'to-be-verified@example.org', + verifiedAt: expect.any(String), + createdAt: expect.any(String), + }, + }, + errors: undefined, + }) + }) + + it('connects the new `EmailAddress` as PRIMARY', async () => { + await mutate({ mutation, variables }) + const result = await neode.cypher(` + MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) + RETURN e + `) + const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email.toJson()).resolves.toMatchObject({ + email: 'to-be-verified@example.org', + }) + }) + + it('removes previous PRIMARY relationship', async () => { + const cypherStatement = ` + MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"}) + RETURN e + ` + let result = await neode.cypher(cypherStatement) + let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email.toJson()).resolves.toMatchObject({ + email: 'user@example.org', + }) + await mutate({ mutation, variables }) + result = await neode.cypher(cypherStatement) + email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email).toBe(false) + }) + + it('removes previous `EmailAddress` node', async () => { + const cypherStatement = ` + MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"}) + RETURN e + ` + let result = await neode.cypher(cypherStatement) + let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email.toJson()).resolves.toMatchObject({ + email: 'user@example.org', + }) + await mutate({ mutation, variables }) + result = await neode.cypher(cypherStatement) + email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email).toBe(false) + }) + + describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email', () => { + beforeEach(async () => { + await factory.create('EmailAddress', { email: 'to-be-verified@example.org' }) + }) + + it('throws UserInputError because of unique constraints', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'A user account with this email already exists.' }], + }) + }) + }) + }) + }) + }) + }) +}) diff --git a/backend/src/schema/resolvers/embeds.spec.js b/backend/src/schema/resolvers/embeds.spec.js index eabd2553e..7683505eb 100644 --- a/backend/src/schema/resolvers/embeds.spec.js +++ b/backend/src/schema/resolvers/embeds.spec.js @@ -3,7 +3,7 @@ import fs from 'fs' import path from 'path' import { createTestClient } from 'apollo-server-testing' import createServer from '../../server' -import { gql } from '../../jest/helpers' +import { gql } from '../../helpers/jest' jest.mock('node-fetch') const { Response } = jest.requireActual('node-fetch') @@ -15,15 +15,12 @@ afterEach(() => { let variables = {} const HumanConnectionOrg = fs.readFileSync( - path.join(__dirname, '../../jest/snapshots/embeds/HumanConnectionOrg.html'), - 'utf8', -) -const pr960 = fs.readFileSync( - path.join(__dirname, '../../jest/snapshots/embeds/pr960.html'), + path.join(__dirname, '../../../snapshots/embeds/HumanConnectionOrg.html'), 'utf8', ) +const pr960 = fs.readFileSync(path.join(__dirname, '../../../snapshots/embeds/pr960.html'), 'utf8') const babyLovesCat = fs.readFileSync( - path.join(__dirname, '../../jest/snapshots/embeds/babyLovesCat.html'), + path.join(__dirname, '../../../snapshots/embeds/babyLovesCat.html'), 'utf8', ) @@ -88,7 +85,7 @@ describe('Query', () => { }) it('shows some default data', async () => { - const expected = expect.objectContaining({ + await expect(embedAction(variables)).resolves.toMatchObject({ data: { embed: { audio: null, @@ -98,7 +95,7 @@ describe('Query', () => { html: null, image: null, lang: null, - publisher: 'YouTube', + publisher: null, sources: ['resource'], title: null, type: 'link', @@ -106,8 +103,8 @@ describe('Query', () => { video: null, }, }, + errors: undefined, }) - await expect(embedAction(variables)).resolves.toEqual(expected) }) }) @@ -120,7 +117,7 @@ describe('Query', () => { }) it('does not crash if embed provider returns invalid JSON', async () => { - const expected = expect.objectContaining({ + await expect(embedAction(variables)).resolves.toMatchObject({ data: { embed: { audio: null, @@ -140,8 +137,8 @@ describe('Query', () => { video: null, }, }, + errors: undefined, }) - await expect(embedAction(variables)).resolves.toEqual(expected) }) }) @@ -154,7 +151,7 @@ describe('Query', () => { }) it('returns meta data even if no embed html can be retrieved', async () => { - const expected = expect.objectContaining({ + await expect(embedAction(variables)).resolves.toMatchObject({ data: { embed: { type: 'link', @@ -174,8 +171,8 @@ describe('Query', () => { html: null, }, }, + errors: undefined, }) - await expect(embedAction(variables)).resolves.toEqual(expected) }) }) @@ -188,7 +185,7 @@ describe('Query', () => { }) it('returns meta data plus youtube iframe html', async () => { - const expected = expect.objectContaining({ + await expect(embedAction(variables)).resolves.toMatchObject({ data: { embed: { type: 'video', @@ -208,8 +205,8 @@ describe('Query', () => { '', }, }, + errors: undefined, }) - await expect(embedAction(variables)).resolves.toEqual(expected) }) }) }) diff --git a/backend/src/schema/resolvers/embeds/findProvider.js b/backend/src/schema/resolvers/embeds/findProvider.js index 491cbb9e8..8575599e1 100644 --- a/backend/src/schema/resolvers/embeds/findProvider.js +++ b/backend/src/schema/resolvers/embeds/findProvider.js @@ -2,7 +2,11 @@ import fs from 'fs' import path from 'path' import minimatch from 'minimatch' -let oEmbedProvidersFile = fs.readFileSync(path.join(__dirname, './providers.json'), 'utf8') +let oEmbedProvidersFile = fs.readFileSync( + path.join(__dirname, '../../../../public/providers.json'), + 'utf8', +) + // some providers allow a format parameter // we need JSON oEmbedProvidersFile = oEmbedProvidersFile.replace(/\{format\}/g, 'json') diff --git a/backend/src/schema/resolvers/embeds/findProvider.spec.js b/backend/src/schema/resolvers/embeds/findProvider.spec.js index cc8cdcb70..963b255ab 100644 --- a/backend/src/schema/resolvers/embeds/findProvider.spec.js +++ b/backend/src/schema/resolvers/embeds/findProvider.spec.js @@ -8,14 +8,102 @@ describe('Vimeo', () => { }) }) -describe('RiffReporter', () => { - it('matches `https://www.riffreporter.de/flugbegleiter-koralle/`', () => { - expect(findProvider('https://www.riffreporter.de/flugbegleiter-koralle/')).toEqual( - 'https://www.riffreporter.de/service/oembed', +describe('D.Tube', () => { + it('matches `https://d.tube/v/alexshumsky/q4D-hIOjknY`', () => { + expect(findProvider('https://d.tube/v/alexshumsky/q4D-hIOjknY')).toEqual( + 'https://api.d.tube/oembed', ) }) }) +describe('GIPHY', () => { + it('matches `https://giphy.com/gifs/KRB0DCpSFQeT6/html5`', () => { + expect(findProvider('https://giphy.com/gifs/KRB0DCpSFQeT6/html5')).toEqual( + 'https://giphy.com/services/oembed', + ) + }) +}) + +describe('Flicker', () => { + it('matches `https://flic.kr/p/VT2HCQ`', () => { + expect(findProvider('https://flic.kr/p/VT2HCQ')).toEqual( + 'https://www.flickr.com/services/oembed/', + ) + }) +}) + +describe('Codepen', () => { + it('matches `https://codepen.io/goodkatz/pen/LYPGxQz`', () => { + expect(findProvider('https://codepen.io/goodkatz/pen/LYPGxQz')).toEqual( + 'http://codepen.io/api/oembed', + ) + }) +}) + +describe('Meetup', () => { + it('matches `https://www.meetup.com/de-DE/spielego/events/ctdplqyzmbfc/`', () => { + expect(findProvider('https://www.meetup.com/de-DE/spielego/events/ctdplqyzmbfc/')).toEqual( + 'https://api.meetup.com/oembed', + ) + }) +}) + +describe('Mixcloud', () => { + it('matches `https://www.mixcloud.com/diffrent/giraffecast025/`', () => { + expect(findProvider('https://www.mixcloud.com/diffrent/giraffecast025/')).toEqual( + 'https://www.mixcloud.com/oembed/', + ) + }) +}) + +describe('Reddit', () => { + it('matches `https://www.reddit.com/r/LivestreamFail/comments/d6a2ge/greek_banned/`', () => { + expect( + findProvider('https://www.reddit.com/r/LivestreamFail/comments/d6a2ge/greek_banned/'), + ).toEqual('https://www.reddit.com/oembed') + }) +}) + +describe('Slideshare', () => { + it('matches `https://www.slideshare.net/ma6/lets-build-an-airport-how-to-estimate-large-scale-projects`', () => { + expect( + findProvider( + 'https://www.slideshare.net/ma6/lets-build-an-airport-how-to-estimate-large-scale-projects', + ), + ).toEqual('http://www.slideshare.net/api/oembed/2') + }) +}) + +describe('Soundcloud', () => { + it('matches `https://soundcloud.com/placid-records/zangenhand-live-altes-wettb-ro`', () => { + expect( + findProvider('https://soundcloud.com/placid-records/zangenhand-live-altes-wettb-ro'), + ).toEqual('https://soundcloud.com/oembed') + }) +}) + +describe('Twitch', () => { + it('matches `https://www.twitch.tv/gtimetv`', () => { + expect(findProvider('https://www.twitch.tv/gtimetv')).toEqual('https://api.twitch.tv/v4/oembed') + }) +}) + +describe('Twitter', () => { + it('matches `https://twitter.com/kenfm/status/1168682881524232194`', () => { + expect(findProvider('https://twitter.com/kenfm/status/1168682881524232194')).toEqual( + 'https://publish.twitter.com/oembed', + ) + }) +}) + +describe('Facebook', () => { + it('matches `https://www.facebook.com/FacebookDeutschland/videos/1960353927603280/`', () => { + expect( + findProvider('https://www.facebook.com/FacebookDeutschland/videos/1960353927603280/'), + ).toEqual('https://www.facebook.com/plugins/post/oembed.json') + }) +}) + describe('Youtube', () => { it('matches `https://www.youtube.com/watch?v=qkdXAtO40Fo`', () => { expect(findProvider('https://www.youtube.com/watch?v=qkdXAtO40Fo')).toEqual( diff --git a/backend/src/schema/resolvers/embeds/providers.json b/backend/src/schema/resolvers/embeds/providers.json deleted file mode 100644 index 873ac2bf8..000000000 --- a/backend/src/schema/resolvers/embeds/providers.json +++ /dev/null @@ -1,3014 +0,0 @@ -[ - { - "provider_name": "23HQ", - "provider_url": "http:\/\/www.23hq.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.23hq.com\/*\/photo\/*" - ], - "url": "http:\/\/www.23hq.com\/23\/oembed" - } - ] - }, - { - "provider_name": "Adways", - "provider_url": "http:\/\/www.adways.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/play.adpaths.com\/experience\/*" - ], - "url": "http:\/\/play.adpaths.com\/oembed\/*" - } - ] - }, - { - "provider_name": "Alpha App Net", - "provider_url": "https:\/\/alpha.app.net\/browse\/posts\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/alpha.app.net\/*\/post\/*", - "https:\/\/photos.app.net\/*\/*" - ], - "url": "https:\/\/alpha-api.app.net\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Altru", - "provider_url": "https:\/\/www.altrulabs.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/app.altrulabs.com\/*\/*?answer_id=*" - ], - "url": "https:\/\/api.altrulabs.com\/social\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "amCharts Live Editor", - "provider_url": "https:\/\/live.amcharts.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/live.amcharts.com\/*", - "https:\/\/live.amcharts.com\/*" - ], - "url": "https:\/\/live.amcharts.com\/oembed" - } - ] - }, - { - "provider_name": "Animatron", - "provider_url": "https:\/\/www.animatron.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.animatron.com\/project\/*", - "https:\/\/animatron.com\/project\/*" - ], - "url": "https:\/\/animatron.com\/oembed\/json", - "discovery": true - } - ] - }, - { - "provider_name": "Animoto", - "provider_url": "http:\/\/animoto.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/animoto.com\/play\/*" - ], - "url": "http:\/\/animoto.com\/oembeds\/create" - } - ] - }, - { - "provider_name": "Apester", - "provider_url": "https:\/\/www.apester.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/renderer.apester.com\/v2\/*?preview=true&iframe_preview=true" - ], - "url": "https:\/\/display.apester.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Archivos", - "provider_url": "https:\/\/app.archivos.digital", - "endpoints": [ - { - "schemes": [ - "https:\/\/app.archivos.digital\/app\/view\/*" - ], - "url": "https:\/\/app.archivos.digital\/oembed\/" - } - ] - }, - { - "provider_name": "AudioClip", - "provider_url": "https:\/\/audioclip.naver.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/audioclip.naver.com\/channels\/*\/clips\/*", - "https:\/\/audioclip.naver.com\/audiobooks\/*" - ], - "url": "https:\/\/audioclip.naver.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Audiomack", - "provider_url": "https:\/\/www.audiomack.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.audiomack.com\/song\/*", - "https:\/\/www.audiomack.com\/album\/*", - "https:\/\/www.audiomack.com\/playlist\/*" - ], - "url": "https:\/\/www.audiomack.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "AudioSnaps", - "provider_url": "http:\/\/audiosnaps.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/audiosnaps.com\/k\/*" - ], - "url": "http:\/\/audiosnaps.com\/service\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Backtracks", - "provider_url": "https:\/\/backtracks.fm", - "endpoints": [ - { - "schemes": [ - "https:\/\/backtracks.fm\/*\/*\/e\/*", - "https:\/\/backtracks.fm\/*", - "http:\/\/backtracks.fm\/*" - ], - "url": "https:\/\/backtracks.fm\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Beautiful.AI", - "provider_url": "https:\/\/www.beautiful.ai\/", - "endpoints": [ - { - "url": "https:\/\/www.beautiful.ai\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Blackfire.io", - "provider_url": "https:\/\/blackfire.io", - "endpoints": [ - { - "schemes": [ - "https:\/\/blackfire.io\/profiles\/*\/graph", - "https:\/\/blackfire.io\/profiles\/compare\/*\/graph" - ], - "url": "https:\/\/blackfire.io\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Box Office Buz", - "provider_url": "http:\/\/boxofficebuz.com", - "endpoints": [ - { - "url": "http:\/\/boxofficebuz.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "BrioVR", - "provider_url": "https:\/\/view.briovr.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/view.briovr.com\/api\/v1\/worlds\/oembed\/*" - ], - "url": "https:\/\/view.briovr.com\/api\/v1\/worlds\/oembed\/" - } - ] - }, - { - "provider_name": "Buttondown", - "provider_url": "https:\/\/buttondown.email\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/buttondown.email\/*" - ], - "url": "https:\/\/buttondown.email\/embed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Byzart Project", - "provider_url": "https:\/\/cmc.byzart.eu", - "endpoints": [ - { - "schemes": [ - "https:\/\/cmc.byzart.eu\/files\/*" - ], - "url": "https:\/\/cmc.byzart.eu\/oembed\/", - "discovery": false - } - ] - }, - { - "provider_name": "Cacoo", - "provider_url": "https:\/\/cacoo.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/cacoo.com\/diagrams\/*" - ], - "url": "http:\/\/cacoo.com\/oembed.{format}" - } - ] - }, - { - "provider_name": "Carbon Health", - "provider_url": "https:\/\/carbonhealth.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/carbonhealth.com\/practice\/*" - ], - "url": "http:\/\/carbonhealth.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "CatBoat", - "provider_url": "http:\/\/img.catbo.at\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/img.catbo.at\/*" - ], - "url": "http:\/\/img.catbo.at\/oembed.json", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Ceros", - "provider_url": "http:\/\/www.ceros.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/view.ceros.com\/*" - ], - "url": "http:\/\/view.ceros.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "ChartBlocks", - "provider_url": "http:\/\/www.chartblocks.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/public.chartblocks.com\/c\/*" - ], - "url": "http:\/\/embed.chartblocks.com\/1.0\/oembed" - } - ] - }, - { - "provider_name": "chirbit.com", - "provider_url": "http:\/\/www.chirbit.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/chirb.it\/*" - ], - "url": "http:\/\/chirb.it\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "CircuitLab", - "provider_url": "https:\/\/www.circuitlab.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.circuitlab.com\/circuit\/*" - ], - "url": "https:\/\/www.circuitlab.com\/circuit\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Clipland", - "provider_url": "http:\/\/www.clipland.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.clipland.com\/v\/*", - "https:\/\/www.clipland.com\/v\/*" - ], - "url": "https:\/\/www.clipland.com\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Clyp", - "provider_url": "http:\/\/clyp.it\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/clyp.it\/*", - "http:\/\/clyp.it\/playlist\/*" - ], - "url": "http:\/\/api.clyp.it\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "CodeHS", - "provider_url": "http:\/\/www.codehs.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/codehs.com\/editor\/share_abacus\/*" - ], - "url": "https:\/\/codehs.com\/api\/sharedprogram\/*\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Codepen", - "provider_url": "https:\/\/codepen.io", - "endpoints": [ - { - "schemes": [ - "http:\/\/codepen.io\/*", - "https:\/\/codepen.io\/*" - ], - "url": "http:\/\/codepen.io\/api\/oembed" - } - ] - }, - { - "provider_name": "Codepoints", - "provider_url": "https:\/\/codepoints.net", - "endpoints": [ - { - "schemes": [ - "http:\/\/codepoints.net\/*", - "https:\/\/codepoints.net\/*", - "http:\/\/www.codepoints.net\/*", - "https:\/\/www.codepoints.net\/*" - ], - "url": "https:\/\/codepoints.net\/api\/v1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "CodeSandbox", - "provider_url": "https:\/\/codesandbox.io", - "endpoints": [ - { - "schemes": [ - "https:\/\/codesandbox.io\/s\/*", - "https:\/\/codesandbox.io\/embed\/*" - ], - "url": "https:\/\/codesandbox.io\/oembed" - } - ] - }, - { - "provider_name": "CollegeHumor", - "provider_url": "http:\/\/www.collegehumor.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.collegehumor.com\/video\/*" - ], - "url": "http:\/\/www.collegehumor.com\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Commaful", - "provider_url": "https:\/\/commaful.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/commaful.com\/play\/*" - ], - "url": "https:\/\/commaful.com\/api\/oembed\/" - } - ] - }, - { - "provider_name": "Coub", - "provider_url": "http:\/\/coub.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/coub.com\/view\/*", - "http:\/\/coub.com\/embed\/*" - ], - "url": "http:\/\/coub.com\/api\/oembed.{format}" - } - ] - }, - { - "provider_name": "Crowd Ranking", - "provider_url": "http:\/\/crowdranking.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/crowdranking.com\/*\/*" - ], - "url": "http:\/\/crowdranking.com\/api\/oembed.{format}" - } - ] - }, - { - "provider_name": "Cyrano Systems", - "provider_url": "http:\/\/www.cyranosystems.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/staging.cyranosystems.com\/msg\/*", - "https:\/\/app.cyranosystems.com\/msg\/*" - ], - "url": "https:\/\/staging.cyranosystems.com\/oembed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Daily Mile", - "provider_url": "http:\/\/www.dailymile.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.dailymile.com\/people\/*\/entries\/*" - ], - "url": "http:\/\/api.dailymile.com\/oembed?format=json", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Dailymotion", - "provider_url": "https:\/\/www.dailymotion.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.dailymotion.com\/video\/*" - ], - "url": "https:\/\/www.dailymotion.com\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Deseretnews.com", - "provider_url": "https:\/\/www.deseretnews.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.deseretnews.com\/*" - ], - "url": "https:\/\/embed.deseretnews.com\/" - } - ] - }, - { - "provider_name": "Deviantart.com", - "provider_url": "http:\/\/www.deviantart.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.deviantart.com\/art\/*", - "http:\/\/*.deviantart.com\/*#\/d*", - "http:\/\/fav.me\/*", - "http:\/\/sta.sh\/*", - "https:\/\/*.deviantart.com\/art\/*", - "https:\/\/*.deviantart.com\/*\/art\/*", - "https:\/\/sta.sh\/*\",", - "https:\/\/*.deviantart.com\/*#\/d*\"" - ], - "url": "http:\/\/backend.deviantart.com\/oembed" - } - ] - }, - { - "provider_name": "Didacte", - "provider_url": "https:\/\/www.didacte.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.didacte.com\/a\/course\/*" - ], - "url": "https:\/\/*.didacte.com\/cards\/oembed'", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Digiteka", - "provider_url": "https:\/\/www.ultimedia.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.ultimedia.com\/central\/video\/edit\/id\/*\/topic_id\/*\/", - "https:\/\/www.ultimedia.com\/default\/index\/videogeneric\/id\/*\/showtitle\/1\/viewnc\/1", - "https:\/\/www.ultimedia.com\/default\/index\/videogeneric\/id\/*" - ], - "url": "https:\/\/www.ultimedia.com\/api\/search\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Dipity", - "provider_url": "http:\/\/www.dipity.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.dipity.com\/*\/*\/" - ], - "url": "http:\/\/www.dipity.com\/oembed\/timeline\/" - } - ] - }, - { - "provider_name": "DocDroid", - "provider_url": "https:\/\/www.docdroid.net\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.docdroid.net\/*", - "http:\/\/*.docdroid.net\/*", - "https:\/\/docdro.id\/*", - "http:\/\/docdro.id\/*" - ], - "url": "https:\/\/www.docdroid.net\/api\/oembed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Dotsub", - "provider_url": "http:\/\/dotsub.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/dotsub.com\/view\/*" - ], - "url": "http:\/\/dotsub.com\/services\/oembed" - } - ] - }, - { - "provider_name": "DTube", - "provider_url": "https:\/\/d.tube\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/d.tube\/v\/*" - ], - "url": "https:\/\/api.d.tube\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "edocr", - "provider_url": "http:\/\/www.edocr.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/edocr.com\/docs\/*" - ], - "url": "http:\/\/edocr.com\/api\/oembed" - } - ] - }, - { - "provider_name": "eduMedia", - "provider_url": "https:\/\/www.edumedia-sciences.com\/", - "endpoints": [ - { - "url": "https:\/\/www.edumedia-sciences.com\/oembed.json", - "discovery": true - }, - { - "url": "https:\/\/www.edumedia-sciences.com\/oembed.xml", - "discovery": true - } - ] - }, - { - "provider_name": "EgliseInfo", - "provider_url": "http:\/\/egliseinfo.catholique.fr\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/egliseinfo.catholique.fr\/*" - ], - "url": "http:\/\/egliseinfo.catholique.fr\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Embed Articles", - "provider_url": "http:\/\/embedarticles.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/embedarticles.com\/*" - ], - "url": "http:\/\/embedarticles.com\/oembed\/" - } - ] - }, - { - "provider_name": "Embedly", - "provider_url": "http:\/\/api.embed.ly\/", - "endpoints": [ - { - "url": "http:\/\/api.embed.ly\/1\/oembed" - } - ] - }, - { - "provider_name": "Ethfiddle", - "provider_url": "https:\/\/www.ethfiddle.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/ethfiddle.com\/*" - ], - "url": "https:\/\/ethfiddle.com\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Eyrie", - "provider_url": "https:\/\/eyrie.io\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/eyrie.io\/board\/*", - "https:\/\/eyrie.io\/sparkfun\/*" - ], - "url": "https:\/\/eyrie.io\/v1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Facebook (Post)", - "provider_url": "https:\/\/www.facebook.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.facebook.com\/*\/posts\/*", - "https:\/\/www.facebook.com\/photos\/*", - "https:\/\/www.facebook.com\/*\/photos\/*", - "https:\/\/www.facebook.com\/photo.php*", - "https:\/\/www.facebook.com\/photo.php", - "https:\/\/www.facebook.com\/*\/activity\/*", - "https:\/\/www.facebook.com\/permalink.php", - "https:\/\/www.facebook.com\/media\/set?set=*", - "https:\/\/www.facebook.com\/questions\/*", - "https:\/\/www.facebook.com\/notes\/*\/*\/*" - ], - "url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json", - "discovery": true - } - ] - }, - { - "provider_name": "Facebook (Video)", - "provider_url": "https:\/\/www.facebook.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.facebook.com\/*\/videos\/*", - "https:\/\/www.facebook.com\/video.php" - ], - "url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json", - "discovery": true - } - ] - }, - { - "provider_name": "Fader", - "provider_url": "https:\/\/app.getfader.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/app.getfader.com\/projects\/*\/publish" - ], - "url": "https:\/\/app.getfader.com\/api\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Faithlife TV", - "provider_url": "https:\/\/faithlifetv.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/faithlifetv.com\/items\/*", - "https:\/\/faithlifetv.com\/items\/resource\/*\/*", - "https:\/\/faithlifetv.com\/media\/*", - "https:\/\/faithlifetv.com\/media\/assets\/*", - "https:\/\/faithlifetv.com\/media\/resource\/*\/*" - ], - "url": "https:\/\/faithlifetv.com\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "FITE", - "provider_url": "https:\/\/www.fite.tv\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.fite.tv\/watch\/*" - ], - "url": "https:\/\/www.fite.tv\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Flat", - "provider_url": "https:\/\/flat.io", - "endpoints": [ - { - "schemes": [ - "https:\/\/flat.io\/score\/*", - "https:\/\/*.flat.io\/score\/*" - ], - "url": "https:\/\/flat.io\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Flickr", - "provider_url": "https:\/\/www.flickr.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.flickr.com\/photos\/*", - "http:\/\/flic.kr\/p\/*", - "https:\/\/*.flickr.com\/photos\/*", - "https:\/\/flic.kr\/p\/*" - ], - "url": "https:\/\/www.flickr.com\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Flourish", - "provider_url": "https:\/\/flourish.studio\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/public.flourish.studio\/visualisation\/*", - "https:\/\/public.flourish.studio\/story\/*" - ], - "url": "https:\/\/app.flourish.studio\/api\/v1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Fontself", - "provider_url": "https:\/\/www.fontself.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/catapult.fontself.com\/*" - ], - "url": "https:\/\/oembed.fontself.com\/" - } - ] - }, - { - "provider_name": "FOX SPORTS Australia", - "provider_url": "http:\/\/www.foxsports.com.au", - "endpoints": [ - { - "schemes": [ - "http:\/\/fiso.foxsports.com.au\/isomorphic-widget\/*", - "https:\/\/fiso.foxsports.com.au\/isomorphic-widget\/*" - ], - "url": "https:\/\/fiso.foxsports.com.au\/oembed" - } - ] - }, - { - "provider_name": "FrameBuzz", - "provider_url": "https:\/\/framebuzz.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/framebuzz.com\/v\/*", - "https:\/\/framebuzz.com\/v\/*" - ], - "url": "https:\/\/framebuzz.com\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "FunnyOrDie", - "provider_url": "http:\/\/www.funnyordie.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.funnyordie.com\/videos\/*" - ], - "url": "http:\/\/www.funnyordie.com\/oembed.{format}" - } - ] - }, - { - "provider_name": "Geograph Britain and Ireland", - "provider_url": "https:\/\/www.geograph.org.uk\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.geograph.org.uk\/*", - "http:\/\/*.geograph.co.uk\/*", - "http:\/\/*.geograph.ie\/*", - "http:\/\/*.wikimedia.org\/*_geograph.org.uk_*" - ], - "url": "http:\/\/api.geograph.org.uk\/api\/oembed" - } - ] - }, - { - "provider_name": "Geograph Channel Islands", - "provider_url": "http:\/\/channel-islands.geograph.org\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.geograph.org.gg\/*", - "http:\/\/*.geograph.org.je\/*", - "http:\/\/channel-islands.geograph.org\/*", - "http:\/\/channel-islands.geographs.org\/*", - "http:\/\/*.channel.geographs.org\/*" - ], - "url": "http:\/\/www.geograph.org.gg\/api\/oembed" - } - ] - }, - { - "provider_name": "Geograph Germany", - "provider_url": "http:\/\/geo-en.hlipp.de\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/geo-en.hlipp.de\/*", - "http:\/\/geo.hlipp.de\/*", - "http:\/\/germany.geograph.org\/*" - ], - "url": "http:\/\/geo.hlipp.de\/restapi.php\/api\/oembed" - } - ] - }, - { - "provider_name": "Getty Images", - "provider_url": "http:\/\/www.gettyimages.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/gty.im\/*" - ], - "url": "http:\/\/embed.gettyimages.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Gfycat", - "provider_url": "https:\/\/gfycat.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/gfycat.com\/*", - "http:\/\/www.gfycat.com\/*", - "https:\/\/gfycat.com\/*", - "https:\/\/www.gfycat.com\/*" - ], - "url": "https:\/\/api.gfycat.com\/v1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Gifnote", - "provider_url": "https:\/\/www.gifnote.com\/", - "endpoints": [ - { - "url": "https:\/\/www.gifnote.com\/services\/oembed", - "schemes": [ - "https:\/\/www.gifnote.com\/play\/*" - ], - "discovery": true - } - ] - }, - { - "provider_name": "GIPHY", - "provider_url": "https:\/\/giphy.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/giphy.com\/gifs\/*", - "http:\/\/gph.is\/*", - "https:\/\/media.giphy.com\/media\/*\/giphy.gif" - ], - "url": "https:\/\/giphy.com\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "GloriaTV", - "provider_url": "https:\/\/gloria.tv\/", - "endpoints": [ - { - "url": "https:\/\/gloria.tv\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "GT Channel", - "provider_url": "https:\/\/gtchannel.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/gtchannel.com\/watch\/*" - ], - "url": "https:\/\/api.luminery.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Gyazo", - "provider_url": "https:\/\/gyazo.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/gyazo.com\/*" - ], - "url": "https:\/\/api.gyazo.com\/api\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "hearthis.at", - "provider_url": "https:\/\/hearthis.at\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/hearthis.at\/*\/*\/" - ], - "url": "https:\/\/hearthis.at\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "HuffDuffer", - "provider_url": "http:\/\/huffduffer.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/huffduffer.com\/*\/*" - ], - "url": "http:\/\/huffduffer.com\/oembed" - } - ] - }, - { - "provider_name": "Hulu", - "provider_url": "http:\/\/www.hulu.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.hulu.com\/watch\/*" - ], - "url": "http:\/\/www.hulu.com\/api\/oembed.{format}" - } - ] - }, - { - "provider_name": "iFixit", - "provider_url": "http:\/\/www.iFixit.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.ifixit.com\/Guide\/View\/*" - ], - "url": "http:\/\/www.ifixit.com\/Embed" - } - ] - }, - { - "provider_name": "IFTTT", - "provider_url": "http:\/\/www.ifttt.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/ifttt.com\/recipes\/*" - ], - "url": "http:\/\/www.ifttt.com\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Indaco", - "provider_url": "https:\/\/player.indacolive.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/player.indacolive.com\/player\/jwp\/clients\/*" - ], - "url": "https:\/\/player.indacolive.com\/services\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Infogram", - "provider_url": "https:\/\/infogr.am\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/infogr.am\/*" - ], - "url": "https:\/\/infogr.am\/oembed" - } - ] - }, - { - "provider_name": "Infoveave", - "provider_url": "https:\/\/infoveave.net\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.infoveave.net\/E\/*", - "https:\/\/*.infoveave.net\/P\/*" - ], - "url": "https:\/\/infoveave.net\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Injurymap", - "provider_url": "https:\/\/www.injurymap.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.injurymap.com\/exercises\/*" - ], - "url": "https:\/\/www.injurymap.com\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Inoreader", - "provider_url": "https:\/\/www.inoreader.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.inoreader.com\/oembed\/" - ], - "url": "https:\/\/www.inoreader.com\/oembed\/api\/", - "discovery": true - } - ] - }, - { - "provider_name": "inphood", - "provider_url": "http:\/\/inphood.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.inphood.com\/*" - ], - "url": "http:\/\/api.inphood.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Instagram", - "provider_url": "https:\/\/instagram.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/instagram.com\/p\/*", - "http:\/\/instagr.am\/p\/*", - "http:\/\/www.instagram.com\/p\/*", - "http:\/\/www.instagr.am\/p\/*", - "https:\/\/instagram.com\/p\/*", - "https:\/\/instagr.am\/p\/*", - "https:\/\/www.instagram.com\/p\/*", - "https:\/\/www.instagr.am\/p\/*" - ], - "url": "https:\/\/api.instagram.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "iSnare Articles", - "provider_url": "https:\/\/www.isnare.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.isnare.com\/*" - ], - "url": "https:\/\/www.isnare.com\/oembed\/" - } - ] - }, - { - "provider_name": "Issuu", - "provider_url": "https:\/\/issuu.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/issuu.com\/*\/docs\/*" - ], - "url": "https:\/\/issuu.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "ivlismusic", - "provider_url": "https:\/\/music.ivlis.kr\/", - "endpoints": [ - { - "url": "https:\/\/music.ivlis.kr\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "KakaoTv", - "provider_url": "https:\/\/tv.kakao.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/tv.kakao.com\/channel\/*\/cliplink\/*", - "https:\/\/tv.kakao.com\/channel\/v\/*", - "https:\/\/tv.kakao.com\/channel\/*\/livelink\/*", - "https:\/\/tv.kakao.com\/channel\/l\/*" - ], - "url": "https:\/\/tv.kakao.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Kickstarter", - "provider_url": "http:\/\/www.kickstarter.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.kickstarter.com\/projects\/*" - ], - "url": "http:\/\/www.kickstarter.com\/services\/oembed" - } - ] - }, - { - "provider_name": "Kidoju", - "provider_url": "https:\/\/www.kidoju.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.kidoju.com\/en\/x\/*\/*", - "https:\/\/www.kidoju.com\/fr\/x\/*\/*" - ], - "url": "https:\/\/www.kidoju.com\/api\/oembed" - } - ] - }, - { - "provider_name": "Kit", - "provider_url": "https:\/\/kit.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/kit.com\/*\/*", - "https:\/\/kit.com\/*\/*" - ], - "url": "https:\/\/embed.kit.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Kitchenbowl", - "provider_url": "http:\/\/www.kitchenbowl.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.kitchenbowl.com\/recipe\/*" - ], - "url": "http:\/\/www.kitchenbowl.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Knacki", - "provider_url": "http:\/\/jdr.knacki.info", - "endpoints": [ - { - "schemes": [ - "http:\/\/jdr.knacki.info\/meuh\/*", - "https:\/\/jdr.knacki.info\/meuh\/*" - ], - "url": "https:\/\/jdr.knacki.info\/oembed" - } - ] - }, - { - "provider_name": "LearningApps.org", - "provider_url": "http:\/\/learningapps.org\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/learningapps.org\/*" - ], - "url": "http:\/\/learningapps.org\/oembed.php", - "discovery": true - } - ] - }, - { - "provider_name": "Lille.Pod", - "provider_url": "https:\/\/pod.univ-lille.fr\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/pod.univ-lille.fr\/video\/*" - ], - "url": "https:\/\/pod.univ-lille.fr\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Livestream", - "provider_url": "https:\/\/livestream.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/livestream.com\/accounts\/*\/events\/*", - "https:\/\/livestream.com\/accounts\/*\/events\/*\/videos\/*", - "https:\/\/livestream.com\/*\/events\/*", - "https:\/\/livestream.com\/*\/events\/*\/videos\/*", - "https:\/\/livestream.com\/*\/*", - "https:\/\/livestream.com\/*\/*\/videos\/*" - ], - "url": "https:\/\/livestream.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Ludus", - "provider_url": "https:\/\/ludus.one", - "endpoints": [ - { - "schemes": [ - "https:\/\/app.ludus.one\/*" - ], - "url": "https:\/\/app.ludus.one\/oembed", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "MathEmbed", - "provider_url": "http:\/\/mathembed.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/mathembed.com\/latex?inputText=*", - "http:\/\/mathembed.com\/latex?inputText=*" - ], - "url": "http:\/\/mathembed.com\/oembed" - } - ] - }, - { - "provider_name": "Matterport", - "provider_url": "https:\/\/matterport.com\/", - "endpoints": [ - { - "url": "https:\/\/my.matterport.com\/api\/v1\/models\/oembed\/", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "me.me", - "provider_url": "https:\/\/me.me\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/me.me\/i\/*" - ], - "url": "https:\/\/me.me\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Medienarchiv der K\u00fcnste - Z\u00fcrcher Hochschule der K\u00fcnste", - "provider_url": "https:\/\/medienarchiv.zhdk.ch\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/medienarchiv.zhdk.ch\/entries\/*" - ], - "url": "https:\/\/medienarchiv.zhdk.ch\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Meetup", - "provider_url": "http:\/\/www.meetup.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/meetup.com\/*", - "https:\/\/www.meetup.com\/*", - "https:\/\/meetup.com\/*", - "http:\/\/meetu.ps\/*" - ], - "url": "https:\/\/api.meetup.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "MixCloud", - "provider_url": "https:\/\/mixcloud.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.mixcloud.com\/*\/*\/", - "https:\/\/www.mixcloud.com\/*\/*\/" - ], - "url": "https:\/\/www.mixcloud.com\/oembed\/" - } - ] - }, - { - "provider_name": "Moby Picture", - "provider_url": "http:\/\/www.mobypicture.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.mobypicture.com\/user\/*\/view\/*", - "http:\/\/moby.to\/*" - ], - "url": "http:\/\/api.mobypicture.com\/oEmbed" - } - ] - }, - { - "provider_name": "Modelo", - "provider_url": "http:\/\/modelo.io\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/beta.modelo.io\/embedded\/*" - ], - "url": "https:\/\/portal.modelo.io\/oembed", - "discovery": false - } - ] - }, - { - "provider_name": "MorphCast", - "provider_url": "https:\/\/www.morphcast.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/m-roll.morphcast.com\/mroll\/*" - ], - "url": "https:\/\/m-roll.morphcast.com\/service\/oembed", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Music Box Maniacs", - "provider_url": "https:\/\/musicboxmaniacs.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/musicboxmaniacs.com\/explore\/melody\/*" - ], - "url": "https:\/\/musicboxmaniacs.com\/embed\/", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "myBeweeg", - "provider_url": "https:\/\/mybeweeg.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/mybeweeg.com\/w\/*" - ], - "url": "https:\/\/mybeweeg.com\/services\/oembed" - } - ] - }, - { - "provider_name": "Namchey", - "provider_url": "https:\/\/namchey.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/namchey.com\/embeds\/*" - ], - "url": "https:\/\/namchey.com\/api\/oembed", - "formats": [ - "json", - "xml" - ], - "discovery": true - } - ] - }, - { - "provider_name": "nanoo.tv", - "provider_url": "https:\/\/www.nanoo.tv\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.nanoo.tv\/link\/*", - "http:\/\/nanoo.tv\/link\/*", - "http:\/\/*.nanoo.pro\/link\/*", - "http:\/\/nanoo.pro\/link\/*", - "https:\/\/*.nanoo.tv\/link\/*", - "https:\/\/nanoo.tv\/link\/*", - "https:\/\/*.nanoo.pro\/link\/*", - "https:\/\/nanoo.pro\/link\/*", - "http:\/\/media.zhdk.ch\/signatur\/*", - "http:\/\/new.media.zhdk.ch\/signatur\/*", - "https:\/\/media.zhdk.ch\/signatur\/*", - "https:\/\/new.media.zhdk.ch\/signatur\/*" - ], - "url": "https:\/\/www.nanoo.tv\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Nasjonalbiblioteket", - "provider_url": "https:\/\/www.nb.no\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.nb.no\/items\/*" - ], - "url": "https:\/\/api.nb.no\/catalog\/v1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Natural Atlas", - "provider_url": "https:\/\/naturalatlas.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/naturalatlas.com\/*", - "https:\/\/naturalatlas.com\/*\/*", - "https:\/\/naturalatlas.com\/*\/*\/*", - "https:\/\/naturalatlas.com\/*\/*\/*\/*" - ], - "url": "https:\/\/naturalatlas.com\/oembed.{format}", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "nfb.ca", - "provider_url": "http:\/\/www.nfb.ca\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.nfb.ca\/film\/*" - ], - "url": "http:\/\/www.nfb.ca\/remote\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Odds.com.au", - "provider_url": "https:\/\/www.odds.com.au", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.odds.com.au\/*", - "https:\/\/odds.com.au\/*" - ], - "url": "https:\/\/www.odds.com.au\/api\/oembed\/" - } - ] - }, - { - "provider_name": "Official FM", - "provider_url": "http:\/\/official.fm", - "endpoints": [ - { - "schemes": [ - "http:\/\/official.fm\/tracks\/*", - "http:\/\/official.fm\/playlists\/*" - ], - "url": "http:\/\/official.fm\/services\/oembed.{format}" - } - ] - }, - { - "provider_name": "Omniscope", - "provider_url": "https:\/\/omniscope.me\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/omniscope.me\/*" - ], - "url": "https:\/\/omniscope.me\/_global_\/oembed\/json", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "On Aol", - "provider_url": "http:\/\/on.aol.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/on.aol.com\/video\/*" - ], - "url": "http:\/\/on.aol.com\/api" - } - ] - }, - { - "provider_name": "Ora TV", - "provider_url": "http:\/\/www.ora.tv\/", - "endpoints": [ - { - "discovery": true, - "url": "https:\/\/www.ora.tv\/oembed\/*?format={format}" - } - ] - }, - { - "provider_name": "Orbitvu", - "provider_url": "https:\/\/orbitvu.co", - "endpoints": [ - { - "schemes": [ - "https:\/\/orbitvu.co\/001\/*\/ov3601\/view", - "https:\/\/orbitvu.co\/001\/*\/ov3601\/*\/view", - "https:\/\/orbitvu.co\/001\/*\/ov3602\/*\/view", - "https:\/\/orbitvu.co\/001\/*\/2\/orbittour\/*\/view", - "https:\/\/orbitvu.co\/001\/*\/1\/2\/orbittour\/*\/view", - "http:\/\/orbitvu.co\/001\/*\/ov3601\/view", - "http:\/\/orbitvu.co\/001\/*\/ov3601\/*\/view", - "http:\/\/orbitvu.co\/001\/*\/ov3602\/*\/view", - "http:\/\/orbitvu.co\/001\/*\/2\/orbittour\/*\/view", - "http:\/\/orbitvu.co\/001\/*\/1\/2\/orbittour\/*\/view" - ], - "url": "http:\/\/orbitvu.co\/service\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Oumy", - "provider_url": "https:\/\/www.oumy.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.oumy.com\/v\/*" - ], - "url": "https:\/\/www.oumy.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Outplayed.tv", - "provider_url": "https:\/\/outplayed.tv\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/outplayed.tv\/media\/*" - ], - "url": "https:\/\/outplayed.tv\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Overflow", - "provider_url": "https:\/\/overflow.io", - "endpoints": [ - { - "schemes": [ - "https:\/\/overflow.io\/s\/*", - "https:\/\/overflow.io\/embed\/*" - ], - "url": "https:\/\/overflow.io\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Pastery", - "provider_url": "https:\/\/www.pastery.net", - "endpoints": [ - { - "schemes": [ - "http:\/\/pastery.net\/*", - "https:\/\/pastery.net\/*", - "http:\/\/www.pastery.net\/*", - "https:\/\/www.pastery.net\/*" - ], - "url": "https:\/\/www.pastery.net\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "PingVP", - "provider_url": "https:\/\/www.pingvp.com\/", - "endpoints": [ - { - "url": "https:\/\/beta.pingvp.com.kpnis.nl\/p\/oembed.php", - "discovery": true - } - ] - }, - { - "provider_name": "Pixdor", - "provider_url": "http:\/\/www.pixdor.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/store.pixdor.com\/place-marker-widget\/*\/show", - "https:\/\/store.pixdor.com\/map\/*\/show" - ], - "url": "https:\/\/store.pixdor.com\/oembed", - "formats": [ - "json", - "xml" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Podbean", - "provider_url": "http:\/\/podbean.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.podbean.com\/e\/*", - "http:\/\/*.podbean.com\/e\/*" - ], - "url": "https:\/\/api.podbean.com\/v1\/oembed" - } - ] - }, - { - "provider_name": "Poll Daddy", - "provider_url": "http:\/\/polldaddy.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.polldaddy.com\/s\/*", - "http:\/\/*.polldaddy.com\/poll\/*", - "http:\/\/*.polldaddy.com\/ratings\/*" - ], - "url": "http:\/\/polldaddy.com\/oembed\/" - } - ] - }, - { - "provider_name": "Port", - "provider_url": "http:\/\/www.sellwithport.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/app.sellwithport.com\/#\/buyer\/*" - ], - "url": "https:\/\/api.sellwithport.com\/v1.0\/buyer\/oembed" - } - ] - }, - { - "provider_name": "Portfolium", - "provider_url": "https:\/\/portfolium.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/portfolium.com\/entry\/*" - ], - "url": "https:\/\/api.portfolium.com\/oembed" - } - ] - }, - { - "provider_name": "posiXion", - "provider_url": "https:\/\/posixion.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/posixion.com\/question\/*", - "https:\/\/posixion.com\/*\/question\/*" - ], - "url": "http:\/\/posixion.com\/services\/oembed\/" - } - ] - }, - { - "provider_name": "Quiz.biz", - "provider_url": "http:\/\/www.quiz.biz\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.quiz.biz\/quizz-*.html" - ], - "url": "http:\/\/www.quiz.biz\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Quizz.biz", - "provider_url": "http:\/\/www.quizz.biz\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.quizz.biz\/quizz-*.html" - ], - "url": "http:\/\/www.quizz.biz\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "RapidEngage", - "provider_url": "https:\/\/rapidengage.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/rapidengage.com\/s\/*" - ], - "url": "https:\/\/rapidengage.com\/api\/oembed" - } - ] - }, - { - "provider_name": "Reddit", - "provider_url": "https:\/\/reddit.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/reddit.com\/r\/*\/comments\/*\/*", - "https:\/\/www.reddit.com\/r\/*\/comments\/*\/*" - ], - "url": "https:\/\/www.reddit.com\/oembed" - } - ] - }, - { - "provider_name": "ReleaseWire", - "provider_url": "http:\/\/www.releasewire.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/rwire.com\/*" - ], - "url": "http:\/\/publisher.releasewire.com\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Replit", - "provider_url": "https:\/\/repl.it\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/repl.it\/@*\/*" - ], - "url": "https:\/\/repl.it\/data\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "RepubHub", - "provider_url": "http:\/\/repubhub.icopyright.net\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/repubhub.icopyright.net\/freePost.act?*" - ], - "url": "http:\/\/repubhub.icopyright.net\/oembed.act", - "discovery": true - } - ] - }, - { - "provider_name": "ReverbNation", - "provider_url": "https:\/\/www.reverbnation.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.reverbnation.com\/*", - "https:\/\/www.reverbnation.com\/*\/songs\/*" - ], - "url": "https:\/\/www.reverbnation.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "RiffReporter", - "provider_url": "https:\/\/www.riffreporter.de\/", - "endpoints": [ - { - "url": "https:\/\/www.riffreporter.de\/service\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Roomshare", - "provider_url": "http:\/\/roomshare.jp", - "endpoints": [ - { - "schemes": [ - "http:\/\/roomshare.jp\/post\/*", - "http:\/\/roomshare.jp\/en\/post\/*" - ], - "url": "http:\/\/roomshare.jp\/en\/oembed.{format}" - } - ] - }, - { - "provider_name": "RoosterTeeth", - "provider_url": "https:\/\/roosterteeth.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/roosterteeth.com\/*" - ], - "url": "https:\/\/roosterteeth.com\/oembed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Rumble", - "provider_url": "https:\/\/rumble.com\/", - "endpoints": [ - { - "url": "https:\/\/rumble.com\/api\/Media\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Sapo Videos", - "provider_url": "http:\/\/videos.sapo.pt", - "endpoints": [ - { - "schemes": [ - "http:\/\/videos.sapo.pt\/*" - ], - "url": "http:\/\/videos.sapo.pt\/oembed" - } - ] - }, - { - "provider_name": "Screen9", - "provider_url": "http:\/\/www.screen9.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/console.screen9.com\/*", - "https:\/\/*.screen9.tv\/*" - ], - "url": "https:\/\/api.screen9.com\/oembed" - } - ] - }, - { - "provider_name": "Screencast.com", - "provider_url": "http:\/\/www.screencast.com\/", - "endpoints": [ - { - "url": "https:\/\/api.screencast.com\/external\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Screenr", - "provider_url": "http:\/\/www.screenr.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.screenr.com\/*\/" - ], - "url": "http:\/\/www.screenr.com\/api\/oembed.{format}" - } - ] - }, - { - "provider_name": "ScribbleMaps", - "provider_url": "https:\/\/scribblemaps.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.scribblemaps.com\/maps\/view\/*", - "https:\/\/www.scribblemaps.com\/maps\/view\/*", - "http:\/\/scribblemaps.com\/maps\/view\/*", - "https:\/\/scribblemaps.com\/maps\/view\/*" - ], - "url": "https:\/\/scribblemaps.com\/api\/services\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Scribd", - "provider_url": "http:\/\/www.scribd.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.scribd.com\/doc\/*" - ], - "url": "http:\/\/www.scribd.com\/services\/oembed\/" - } - ] - }, - { - "provider_name": "SendtoNews", - "provider_url": "http:\/\/www.sendtonews.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/embed.sendtonews.com\/oembed\/*" - ], - "url": "https:\/\/embed.sendtonews.com\/services\/oembed", - "discovery": true, - "formats": [ - "json", - "xml" - ] - } - ] - }, - { - "provider_name": "ShortNote", - "provider_url": "https:\/\/www.shortnote.jp\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.shortnote.jp\/view\/notes\/*" - ], - "url": "https:\/\/www.shortnote.jp\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Shoudio", - "provider_url": "http:\/\/shoudio.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/shoudio.com\/*", - "http:\/\/shoud.io\/*" - ], - "url": "http:\/\/shoudio.com\/api\/oembed" - } - ] - }, - { - "provider_name": "Show the Way, actionable location info", - "provider_url": "https:\/\/showtheway.io", - "endpoints": [ - { - "schemes": [ - "https:\/\/showtheway.io\/to\/*" - ], - "url": "https:\/\/showtheway.io\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Simplecast", - "provider_url": "https:\/\/simplecast.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/simplecast.com\/s\/*" - ], - "url": "https:\/\/simplecast.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Sizzle", - "provider_url": "https:\/\/onsizzle.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/onsizzle.com\/i\/*" - ], - "url": "https:\/\/onsizzle.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Sketchfab", - "provider_url": "http:\/\/sketchfab.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/sketchfab.com\/models\/*", - "https:\/\/sketchfab.com\/models\/*", - "https:\/\/sketchfab.com\/*\/folders\/*" - ], - "url": "http:\/\/sketchfab.com\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "SlideShare", - "provider_url": "http:\/\/www.slideshare.net\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.slideshare.net\/*\/*", - "http:\/\/fr.slideshare.net\/*\/*", - "http:\/\/de.slideshare.net\/*\/*", - "http:\/\/es.slideshare.net\/*\/*", - "http:\/\/pt.slideshare.net\/*\/*" - ], - "url": "http:\/\/www.slideshare.net\/api\/oembed\/2", - "discovery": true - } - ] - }, - { - "provider_name": "SmashNotes", - "provider_url": "https:\/\/smashnotes.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/smashnotes.com\/p\/*", - "https:\/\/smashnotes.com\/p\/*\/e\/* - https:\/\/smashnotes.com\/p\/*\/e\/*\/s\/*" - ], - "url": "https:\/\/smashnotes.com\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "SmugMug", - "provider_url": "http:\/\/www.smugmug.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.smugmug.com\/*" - ], - "url": "http:\/\/api.smugmug.com\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "SocialExplorer", - "provider_url": "https:\/\/www.socialexplorer.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.socialexplorer.com\/*\/explore", - "https:\/\/www.socialexplorer.com\/*\/view", - "https:\/\/www.socialexplorer.com\/*\/edit", - "https:\/\/www.socialexplorer.com\/*\/embed" - ], - "url": "https:\/\/www.socialexplorer.com\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Songlink", - "provider_url": "https:\/\/song.link", - "endpoints": [ - { - "schemes": [ - "https:\/\/song.link\/*" - ], - "url": "https:\/\/song.link\/oembed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "SoundCloud", - "provider_url": "http:\/\/soundcloud.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/soundcloud.com\/*", - "https:\/\/soundcloud.com\/*" - ], - "url": "https:\/\/soundcloud.com\/oembed" - } - ] - }, - { - "provider_name": "Soundsgood", - "provider_url": "https:\/\/soundsgood.co", - "endpoints": [ - { - "schemes": [ - "https:\/\/play.soundsgood.co\/playlist\/*", - "https:\/\/soundsgood.co\/playlist\/*" - ], - "url": "https:\/\/play.soundsgood.co\/oembed", - "discovery": true, - "formats": [ - "json", - "xml" - ] - } - ] - }, - { - "provider_name": "SpeakerDeck", - "provider_url": "https:\/\/speakerdeck.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/speakerdeck.com\/*\/*", - "https:\/\/speakerdeck.com\/*\/*" - ], - "url": "https:\/\/speakerdeck.com\/oembed.json", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Spotful", - "provider_url": "https:\/\/bespotful.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/play.bespotful.com\/*" - ], - "url": "https:\/\/api.bespotful.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Spotify", - "provider_url": "https:\/\/spotify.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.spotify.com\/*", - "spotify:*" - ], - "url": "https:\/\/embed.spotify.com\/oembed\/" - } - ] - }, - { - "provider_name": "Spreaker", - "provider_url": "https:\/\/www.spreaker.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.spreaker.com\/*", - "https:\/\/*.spreaker.com\/*" - ], - "url": "https:\/\/api.spreaker.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Stanford Digital Repository", - "provider_url": "https:\/\/purl.stanford.edu\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/purl.stanford.edu\/*" - ], - "url": "https:\/\/purl.stanford.edu\/embed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Streamable", - "provider_url": "https:\/\/streamable.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/streamable.com\/*", - "https:\/\/streamable.com\/*" - ], - "url": "https:\/\/api.streamable.com\/oembed.json", - "discovery": true - } - ] - }, - { - "provider_name": "StreamOneCloud", - "provider_url": "https:\/\/www.streamone.nl", - "endpoints": [ - { - "schemes": [ - "https:\/\/content.streamonecloud.net\/embed\/*" - ], - "url": "https:\/\/content.streamonecloud.net\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Sutori", - "provider_url": "https:\/\/www.sutori.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.sutori.com\/story\/*" - ], - "url": "https:\/\/www.sutori.com\/api\/oembed", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Sway", - "provider_url": "https:\/\/www.sway.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/sway.com\/*", - "https:\/\/www.sway.com\/*" - ], - "url": "https:\/\/sway.com\/api\/v1.0\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Ted", - "provider_url": "https:\/\/ted.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/ted.com\/talks\/*", - "https:\/\/ted.com\/talks\/*", - "https:\/\/www.ted.com\/talks\/*" - ], - "url": "https:\/\/www.ted.com\/talks\/oembed.{format}" - } - ] - }, - { - "provider_name": "The New York Times", - "provider_url": "https:\/\/www.nytimes.com", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.nytimes.com\/svc\/oembed", - "https:\/\/nytimes.com\/*", - "https:\/\/*.nytimes.com\/*" - ], - "url": "https:\/\/www.nytimes.com\/svc\/oembed\/json\/", - "discovery": true - } - ] - }, - { - "provider_name": "They Said So", - "provider_url": "https:\/\/theysaidso.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/theysaidso.com\/image\/*" - ], - "url": "https:\/\/theysaidso.com\/extensions\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "TickCounter", - "provider_url": "https:\/\/www.tickcounter.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.tickcounter.com\/countdown\/*", - "http:\/\/www.tickcounter.com\/countup\/*", - "http:\/\/www.tickcounter.com\/ticker\/*", - "http:\/\/www.tickcounter.com\/worldclock\/*", - "https:\/\/www.tickcounter.com\/countdown\/*", - "https:\/\/www.tickcounter.com\/countup\/*", - "https:\/\/www.tickcounter.com\/ticker\/*", - "https:\/\/www.tickcounter.com\/worldclock\/*" - ], - "url": "https:\/\/www.tickcounter.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Toornament", - "provider_url": "https:\/\/www.toornament.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.toornament.com\/tournaments\/*\/information", - "https:\/\/www.toornament.com\/tournaments\/*\/registration\/", - "https:\/\/www.toornament.com\/tournaments\/*\/matches\/schedule", - "https:\/\/www.toornament.com\/tournaments\/*\/stages\/*\/" - ], - "url": "https:\/\/widget.toornament.com\/oembed", - "discovery": true, - "formats": [ - "json", - "xml" - ] - } - ] - }, - { - "provider_name": "Topy", - "provider_url": "http:\/\/www.topy.se\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.topy.se\/image\/*" - ], - "url": "http:\/\/www.topy.se\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Tuxx", - "provider_url": "https:\/\/www.tuxx.be\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.tuxx.be\/*" - ], - "url": "https:\/\/www.tuxx.be\/services\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "tvcf", - "provider_url": "http:\/\/tvcf.co.kr", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.tvcf.co.kr\/v\/*" - ], - "url": "http:\/\/www.tvcf.co.kr\/services\/oembed" - } - ] - }, - { - "provider_name": "Twitch", - "provider_url": "https:\/\/www.twitch.tv", - "endpoints": [ - { - "schemes": [ - "http:\/\/clips.twitch.tv\/*", - "https:\/\/clips.twitch.tv\/*", - "http:\/\/www.twitch.tv\/*", - "https:\/\/www.twitch.tv\/*", - "http:\/\/twitch.tv\/*", - "https:\/\/twitch.tv\/*" - ], - "url": "https:\/\/api.twitch.tv\/v4\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Twitter", - "provider_url": "http:\/\/www.twitter.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/twitter.com\/*\/status\/*", - "https:\/\/*.twitter.com\/*\/status\/*" - ], - "url": "https:\/\/publish.twitter.com\/oembed" - } - ] - }, - { - "provider_name": "TypeCast", - "provider_url": "https:\/\/typecast.ai", - "endpoints": [ - { - "schemes": [ - "https:\/\/play.typecast.ai\/s\/*", - "https:\/\/play.typecast.ai\/e\/*", - "https:\/\/play.typecast.ai\/*" - ], - "url": "https:\/\/play.typecast.ai\/oembed" - } - ] - }, - { - "provider_name": "Ubideo", - "provider_url": "https:\/\/player.ubideo.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/player.ubideo.com\/*" - ], - "url": "https:\/\/player.ubideo.com\/api\/oembed.json", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "University of Cambridge Map", - "provider_url": "https:\/\/map.cam.ac.uk", - "endpoints": [ - { - "schemes": [ - "https:\/\/map.cam.ac.uk\/*" - ], - "url": "https:\/\/map.cam.ac.uk\/oembed\/" - } - ] - }, - { - "provider_name": "UOL", - "provider_url": "https:\/\/mais.uol.com.br\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.uol.com.br\/view\/*", - "https:\/\/*.uol.com.br\/video\/*" - ], - "url": "https:\/\/mais.uol.com.br\/apiuol\/v3\/oembed\/view", - "discovery": true - } - ] - }, - { - "provider_name": "Ustream", - "provider_url": "http:\/\/www.ustream.tv", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.ustream.tv\/*", - "http:\/\/*.ustream.com\/*" - ], - "url": "http:\/\/www.ustream.tv\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Utposts", - "provider_url": "https:\/\/www.utposts.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/www.utposts.com\/products\/*", - "http:\/\/www.utposts.com\/products\/*", - "https:\/\/utposts.com\/products\/*", - "http:\/\/utposts.com\/products\/*" - ], - "url": "https:\/\/www.utposts.com\/api\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Uttles", - "provider_url": "http:\/\/uttles.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/uttles.com\/uttle\/*" - ], - "url": "http:\/\/uttles.com\/api\/reply\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "VeeR VR", - "provider_url": "http:\/\/veer.tv\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/veer.tv\/videos\/*" - ], - "url": "https:\/\/api.veer.tv\/oembed", - "discovery": true - }, - { - "schemes": [ - "http:\/\/veervr.tv\/videos\/*" - ], - "url": "https:\/\/api.veervr.tv\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Verse", - "provider_url": "http:\/\/verse.com\/", - "endpoints": [ - { - "url": "http:\/\/verse.com\/services\/oembed\/" - } - ] - }, - { - "provider_name": "VEVO", - "provider_url": "http:\/\/www.vevo.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.vevo.com\/*", - "https:\/\/www.vevo.com\/*" - ], - "url": "https:\/\/www.vevo.com\/oembed", - "discovery": false - } - ] - }, - { - "provider_name": "VideoJug", - "provider_url": "http:\/\/www.videojug.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/www.videojug.com\/film\/*", - "http:\/\/www.videojug.com\/interview\/*" - ], - "url": "http:\/\/www.videojug.com\/oembed.{format}" - } - ] - }, - { - "provider_name": "Vidlit", - "provider_url": "https:\/\/vidl.it\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/vidl.it\/*" - ], - "url": "https:\/\/api.vidl.it\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Vidmizer", - "provider_url": "https:\/\/www.vidmizer.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/players.vidmizer.com\/*" - ], - "url": "https:\/\/app-v2.vidmizer.com\/api\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Vidyard", - "provider_url": "http:\/\/www.vidyard.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/embed.vidyard.com\/*", - "http:\/\/play.vidyard.com\/*", - "http:\/\/share.vidyard.com\/*", - "http:\/\/*.hubs.vidyard.com\/*" - ], - "url": "https:\/\/api.vidyard.com\/dashboard\/v1.1\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Vimeo", - "provider_url": "https:\/\/vimeo.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/vimeo.com\/*", - "https:\/\/vimeo.com\/album\/*\/video\/*", - "https:\/\/vimeo.com\/channels\/*\/*", - "https:\/\/vimeo.com\/groups\/*\/videos\/*", - "https:\/\/vimeo.com\/ondemand\/*\/*", - "https:\/\/player.vimeo.com\/video\/*" - ], - "url": "https:\/\/vimeo.com\/api\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Viziosphere", - "provider_url": "http:\/\/www.viziosphere.com", - "endpoints": [ - { - "schemes": [ - "http:\/\/viziosphere.com\/3dphoto*" - ], - "url": "http:\/\/viziosphere.com\/services\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Vlipsy", - "provider_url": "https:\/\/vlipsy.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/vlipsy.com\/*" - ], - "url": "https:\/\/vlipsy.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "VLIVE", - "provider_url": "https:\/\/www.vlive.tv", - "endpoints": [ - { - "url": "https:\/\/www.vlive.tv\/oembed", - "schemes": [ - "https:\/\/www.vlive.tv\/video\/*" - ], - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "Vlurb", - "provider_url": "https:\/\/www.vlurb.co\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/vlurb.co\/video\/*", - "https:\/\/vlurb.co\/video\/*" - ], - "url": "https:\/\/vlurb.co\/oembed.json", - "discovery": true - } - ] - }, - { - "provider_name": "VoxSnap", - "provider_url": "https:\/\/voxsnap.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/article.voxsnap.com\/*\/*" - ], - "url": "https:\/\/data.voxsnap.com\/oembed", - "discovery": true, - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "wecandeo", - "provider_url": "http:\/\/www.wecandeo.com\/", - "endpoints": [ - { - "url": "http:\/\/play.wecandeo.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "Wiredrive", - "provider_url": "https:\/\/www.wiredrive.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.wiredrive.com\/*" - ], - "url": "http:\/\/*.wiredrive.com\/present-oembed\/", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "Wistia, Inc.", - "provider_url": "https:\/\/wistia.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/fast.wistia.com\/embed\/iframe\/*", - "https:\/\/fast.wistia.com\/embed\/playlists\/*", - "https:\/\/*.wistia.com\/medias\/*" - ], - "url": "https:\/\/fast.wistia.com\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "wizer.me", - "provider_url": "http:\/\/www.wizer.me\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.wizer.me\/learn\/*", - "https:\/\/*.wizer.me\/learn\/*", - "http:\/\/*.wizer.me\/preview\/*", - "https:\/\/*.wizer.me\/preview\/*" - ], - "url": "http:\/\/app.wizer.me\/api\/oembed.{format}", - "discovery": true - } - ] - }, - { - "provider_name": "Wootled", - "provider_url": "http:\/\/www.wootled.com\/", - "endpoints": [ - { - "url": "http:\/\/www.wootled.com\/oembed" - } - ] - }, - { - "provider_name": "WordPress.com", - "provider_url": "http:\/\/wordpress.com\/", - "endpoints": [ - { - "url": "http:\/\/public-api.wordpress.com\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "Yes, I Know IT!", - "provider_url": "http:\/\/yesik.it", - "endpoints": [ - { - "schemes": [ - "http:\/\/yesik.it\/*", - "http:\/\/www.yesik.it\/*" - ], - "url": "http:\/\/yesik.it\/s\/oembed", - "formats": [ - "json" - ], - "discovery": true - } - ] - }, - { - "provider_name": "YFrog", - "provider_url": "http:\/\/yfrog.com\/", - "endpoints": [ - { - "schemes": [ - "http:\/\/*.yfrog.com\/*", - "http:\/\/yfrog.us\/*" - ], - "url": "http:\/\/www.yfrog.com\/api\/oembed", - "formats": [ - "json" - ] - } - ] - }, - { - "provider_name": "YouTube", - "provider_url": "https:\/\/www.youtube.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.youtube.com\/watch*", - "https:\/\/*.youtube.com\/v\/*", - "https:\/\/youtu.be\/*" - ], - "url": "https:\/\/www.youtube.com\/oembed", - "discovery": true - } - ] - }, - { - "provider_name": "ZnipeTV", - "provider_url": "https:\/\/www.znipe.tv\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/*.znipe.tv\/*" - ], - "url": "https:\/\/api.znipe.tv\/v3\/oembed\/", - "discovery": true - } - ] - }, - { - "provider_name": "ZProvider", - "provider_url": "https:\/\/reports.zoho.com\/", - "endpoints": [ - { - "schemes": [ - "https:\/\/reports.zoho.com\/ZDBDataSheetView.cc?OBJID=1432535000000003002&STANDALONE=true&INTERVAL=120&DATATYPESYMBOL=false&REMTOOLBAR=false&SEARCHBOX=true&INCLUDETITLE=true&INCLUDEDESC=true&SHOWHIDEOPT=true" - ], - "url": "http:\/\/api.provider.com\/oembed.json", - "discovery": true - } - ] - } -] \ No newline at end of file diff --git a/backend/src/schema/resolvers/fileUpload/index.js b/backend/src/schema/resolvers/fileUpload/index.js index fa78238c3..960dde7f9 100644 --- a/backend/src/schema/resolvers/fileUpload/index.js +++ b/backend/src/schema/resolvers/fileUpload/index.js @@ -18,7 +18,6 @@ export default async function fileUpload(params, { file, url }, uploadCallback = const fileLocation = `/uploads/${Date.now()}-${slug(name)}` await uploadCallback({ createReadStream, fileLocation }) delete params[file] - params[url] = fileLocation } diff --git a/backend/src/schema/resolvers/follow.js b/backend/src/schema/resolvers/follow.js index 730a66cfd..80cce8400 100644 --- a/backend/src/schema/resolvers/follow.js +++ b/backend/src/schema/resolvers/follow.js @@ -1,51 +1,43 @@ +import { getNeode } from '../../db/neo4j' + +const neode = getNeode() + export default { Mutation: { - follow: async (_object, params, context, _resolveInfo) => { - const { id, type } = params + followUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params + const { user: currentUser } = context - const session = context.driver.session() - const transactionRes = await session.run( - `MATCH (node {id: $id}), (user:User {id: $userId}) - WHERE $type IN labels(node) AND NOT $id = $userId - MERGE (user)-[relation:FOLLOWS]->(node) - RETURN COUNT(relation) > 0 as isFollowed`, - { - id, - type, - userId: context.user.id, - }, - ) + if (currentUser.id === followedUserId) { + return null + } - const [isFollowed] = transactionRes.records.map(record => { - return record.get('isFollowed') - }) - - session.close() - - return isFollowed + const [user, followedUser] = await Promise.all([ + neode.find('User', currentUser.id), + neode.find('User', followedUserId), + ]) + await user.relateTo(followedUser, 'following') + return followedUser.toJson() }, - unfollow: async (_object, params, context, _resolveInfo) => { - const { id, type } = params - const session = context.driver.session() + unfollowUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params + const { user: currentUser } = context - const transactionRes = await session.run( - `MATCH (user:User {id: $userId})-[relation:FOLLOWS]->(node {id: $id}) - WHERE $type IN labels(node) + /* + * Note: Neode doesn't provide an easy method for retrieving or removing relationships. + * It's suggested to use query builder feature (https://github.com/adam-cowley/neode/issues/67) + * However, pure cypher query looks cleaner IMO + */ + await neode.cypher( + `MATCH (user:User {id: $currentUser.id})-[relation:FOLLOWS]->(followedUser:User {id: $followedUserId}) DELETE relation RETURN COUNT(relation) > 0 as isFollowed`, - { - id, - type, - userId: context.user.id, - }, + { followedUserId, currentUser }, ) - const [isFollowed] = transactionRes.records.map(record => { - return record.get('isFollowed') - }) - session.close() - return isFollowed + const followedUser = await neode.find('User', followedUserId) + return followedUser.toJson() }, }, } diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js index 66be20841..ad836a461 100644 --- a/backend/src/schema/resolvers/follow.spec.js +++ b/backend/src/schema/resolvers/follow.spec.js @@ -1,36 +1,96 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../../seed/factories' -import { host, login } from '../../jest/helpers' +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { getDriver, getNeode } from '../../db/neo4j' +import createServer from '../../server' +import { gql } from '../../helpers/jest' const factory = Factory() -let clientUser1 -let headersUser1 +const driver = getDriver() +const neode = getNeode() -const mutationFollowUser = id => ` - mutation { - follow(id: "${id}", type: User) +let query +let mutate +let authenticatedUser + +let user1 +let user2 +let variables + +const mutationFollowUser = gql` + mutation($id: ID!) { + followUser(id: $id) { + name + followedBy { + id + name + } + followedByCurrentUser + } } ` -const mutationUnfollowUser = id => ` - mutation { - unfollow(id: "${id}", type: User) + +const mutationUnfollowUser = gql` + mutation($id: ID!) { + unfollowUser(id: $id) { + name + followedBy { + id + name + } + followedByCurrentUser + } } ` +const userQuery = gql` + query($id: ID) { + User(id: $id) { + followedBy { + id + } + followedByCurrentUser + } + } +` + +beforeAll(async () => { + await factory.cleanDatabase() + const { server } = createServer({ + context: () => ({ + driver, + neode, + user: authenticatedUser, + cypherParams: { + currentUserId: authenticatedUser ? authenticatedUser.id : null, + }, + }), + }) + + const testClient = createTestClient(server) + query = testClient.query + mutate = testClient.mutate +}) + beforeEach(async () => { - await factory.create('User', { - id: 'u1', - email: 'test@example.org', - password: '1234', - }) - await factory.create('User', { - id: 'u2', - email: 'test2@example.org', - password: '1234', - }) + user1 = await factory + .create('User', { + id: 'u1', + name: 'user1', + email: 'test@example.org', + password: '1234', + }) + .then(user => user.toJson()) + user2 = await factory + .create('User', { + id: 'u2', + name: 'user2', + email: 'test2@example.org', + password: '1234', + }) + .then(user => user.toJson()) - headersUser1 = await login({ email: 'test@example.org', password: '1234' }) - clientUser1 = new GraphQLClient(host, { headers: headersUser1 }) + authenticatedUser = user1 + variables = { id: user2.id } }) afterEach(async () => { @@ -40,84 +100,103 @@ afterEach(async () => { describe('follow', () => { describe('follow user', () => { describe('unauthenticated follow', () => { - it('throws authorization error', async () => { - const client = new GraphQLClient(host) - await expect(client.request(mutationFollowUser('u2'))).rejects.toThrow('Not Authorised') + test('throws authorization error', async () => { + authenticatedUser = null + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { followUser: null }, + }) }) }) - it('I can follow another user', async () => { - const res = await clientUser1.request(mutationFollowUser('u2')) - const expected = { - follow: true, - } - expect(res).toMatchObject(expected) - - const { User } = await clientUser1.request(`{ - User(id: "u2") { - followedBy { id } - followedByCurrentUser - } - }`) - const expected2 = { - followedBy: [{ id: 'u1' }], + test('I can follow another user', async () => { + const expectedUser = { + name: user2.name, + followedBy: [{ id: user1.id, name: user1.name }], followedByCurrentUser: true, } - expect(User[0]).toMatchObject(expected2) - }) - - it('I can`t follow myself', async () => { - const res = await clientUser1.request(mutationFollowUser('u1')) - const expected = { - follow: false, - } - expect(res).toMatchObject(expected) - - const { User } = await clientUser1.request(`{ - User(id: "u1") { - followedBy { id } - followedByCurrentUser - } - }`) - const expected2 = { - followedBy: [], - followedByCurrentUser: false, - } - expect(User[0]).toMatchObject(expected2) - }) - }) - describe('unfollow user', () => { - describe('unauthenticated follow', () => { - it('throws authorization error', async () => { - // follow - await clientUser1.request(mutationFollowUser('u2')) - // unfollow - const client = new GraphQLClient(host) - await expect(client.request(mutationUnfollowUser('u2'))).rejects.toThrow('Not Authorised') + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + data: { followUser: expectedUser }, + errors: undefined, }) }) - it('I can unfollow a user', async () => { - // follow - await clientUser1.request(mutationFollowUser('u2')) - // unfollow - const expected = { - unfollow: true, - } - const res = await clientUser1.request(mutationUnfollowUser('u2')) - expect(res).toMatchObject(expected) + test('adds `createdAt` to `FOLLOW` relationship', async () => { + await mutate({ + mutation: mutationFollowUser, + variables, + }) + const relation = await neode.cypher( + 'MATCH (user:User {id: {id}})-[relationship:FOLLOWS]->(followed:User) WHERE relationship.createdAt IS NOT NULL RETURN relationship', + { id: 'u1' }, + ) + const relationshipProperties = relation.records.map( + record => record.get('relationship').properties.createdAt, + ) + expect(relationshipProperties[0]).toEqual(expect.any(String)) + }) - const { User } = await clientUser1.request(`{ - User(id: "u2") { - followedBy { id } - followedByCurrentUser - } - }`) - const expected2 = { + test('I can`t follow myself', async () => { + variables.id = user1.id + await expect(mutate({ mutation: mutationFollowUser, variables })).resolves.toMatchObject({ + data: { followUser: null }, + errors: undefined, + }) + + const expectedUser = { followedBy: [], followedByCurrentUser: false, } - expect(User[0]).toMatchObject(expected2) + await expect( + query({ + query: userQuery, + variables: { id: user1.id }, + }), + ).resolves.toMatchObject({ + data: { + User: [expectedUser], + }, + errors: undefined, + }) + }) + }) + + describe('unfollow user', () => { + beforeEach(async () => { + variables = { id: user2.id } + await mutate({ mutation: mutationFollowUser, variables }) + }) + + describe('unauthenticated follow', () => { + test('throws authorization error', async () => { + authenticatedUser = null + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + test('I can unfollow a user', async () => { + const expectedUser = { + name: user2.name, + followedBy: [], + followedByCurrentUser: false, + } + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: expectedUser }, + errors: undefined, + }) }) }) }) diff --git a/backend/src/schema/resolvers/helpers/Resolver.js b/backend/src/schema/resolvers/helpers/Resolver.js index 9a6f77513..64ba60f5e 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.js +++ b/backend/src/schema/resolvers/helpers/Resolver.js @@ -1,9 +1,9 @@ -import { neode } from '../../../bootstrap/neo4j' +import log from './databaseLogger' export const undefinedToNullResolver = list => { const resolvers = {} list.forEach(key => { - resolvers[key] = async (parent, params, context, resolveInfo) => { + resolvers[key] = async parent => { return typeof parent[key] === 'undefined' ? null : parent[key] } }) @@ -11,7 +11,6 @@ export const undefinedToNullResolver = list => { } export default function Resolver(type, options = {}) { - const instance = neode() const { idAttribute = 'id', undefinedToNull = [], @@ -22,32 +21,49 @@ export default function Resolver(type, options = {}) { } = options const _hasResolver = (resolvers, { key, connection }, { returnType }) => { - return async (parent, params, context, resolveInfo) => { + return async (parent, params, { driver, cypherParams }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] const id = parent[idAttribute] - const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related` - const result = await instance.cypher(statement, { id }) - let response = result.records.map(r => r.get('related').properties) - if (returnType === 'object') response = response[0] || null - return response + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const cypher = ` + MATCH(:${type} {${idAttribute}: $id})${connection} + RETURN related {.*} as related + ` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + return result.records.map(r => r.get('related')) + }) + try { + let response = await readTxResultPromise + if (returnType === 'object') response = response[0] || null + return response + } finally { + session.close() + } } } const booleanResolver = obj => { const resolvers = {} for (const [key, condition] of Object.entries(obj)) { - resolvers[key] = async (parent, params, { cypherParams }, resolveInfo) => { + resolvers[key] = async (parent, params, { cypherParams, driver }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] - const result = await instance.cypher( - ` - ${condition.replace('this', 'this {id: $parent.id}')} as ${key}`, - { - parent, - cypherParams, - }, - ) - const [record] = result.records - return record.get(key) + const id = parent[idAttribute] + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const nodeCondition = condition.replace('this', 'this {id: $id}') + const cypher = `${nodeCondition} as ${key}` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + const [response] = result.records.map(r => r.get(key)) + return response + }) + try { + return await readTxResultPromise + } finally { + session.close() + } } } return resolvers @@ -56,16 +72,25 @@ export default function Resolver(type, options = {}) { const countResolver = obj => { const resolvers = {} for (const [key, connection] of Object.entries(obj)) { - resolvers[key] = async (parent, params, context, resolveInfo) => { + resolvers[key] = async (parent, params, { driver, cypherParams }, resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] - const id = parent[idAttribute] - const statement = ` - MATCH(u:${type} {${idAttribute}: {id}})${connection} - RETURN COUNT(DISTINCT(related)) as count - ` - const result = await instance.cypher(statement, { id }) - const [response] = result.records.map(r => r.get('count').toNumber()) - return response + const session = driver.session() + const readTxResultPromise = session.readTransaction(async txc => { + const id = parent[idAttribute] + const cypher = ` + MATCH(u:${type} {${idAttribute}: $id})${connection} + RETURN COUNT(DISTINCT(related)) as count + ` + const result = await txc.run(cypher, { id, cypherParams }) + log(result) + const [response] = result.records.map(r => r.get('count').toNumber()) + return response + }) + try { + return await readTxResultPromise + } finally { + session.close() + } } } return resolvers @@ -86,6 +111,7 @@ export default function Resolver(type, options = {}) { } return resolvers } + const result = { ...undefinedToNullResolver(undefinedToNull), ...booleanResolver(boolean), diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.js b/backend/src/schema/resolvers/helpers/createPasswordReset.js new file mode 100644 index 000000000..dec55c893 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/createPasswordReset.js @@ -0,0 +1,34 @@ +import normalizeEmail from './normalizeEmail' + +export default async function createPasswordReset(options) { + const { driver, nonce, email, issuedAt = new Date() } = options + const normalizedEmail = normalizeEmail(email) + const session = driver.session() + try { + const createPasswordResetTxPromise = session.writeTransaction(async transaction => { + const createPasswordResetTransactionResponse = await transaction.run( + ` + MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email:$email}) + CREATE(passwordReset:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL}) + MERGE (user)-[:REQUESTED]->(passwordReset) + RETURN email, passwordReset, user + `, + { + issuedAt: issuedAt.toISOString(), + nonce, + email: normalizedEmail, + }, + ) + return createPasswordResetTransactionResponse.records.map(record => { + const { email } = record.get('email').properties + const { nonce } = record.get('passwordReset').properties + const { name } = record.get('user').properties + return { email, nonce, name } + }) + }) + const [records] = await createPasswordResetTxPromise + return records || {} + } finally { + session.close() + } +} diff --git a/backend/src/schema/resolvers/helpers/databaseLogger.js b/backend/src/schema/resolvers/helpers/databaseLogger.js new file mode 100644 index 000000000..fac1a5c4a --- /dev/null +++ b/backend/src/schema/resolvers/helpers/databaseLogger.js @@ -0,0 +1,15 @@ +import Debug from 'debug' +const debugCypher = Debug('human-connection:neo4j:cypher') +const debugStats = Debug('human-connection:neo4j:stats') + +export default function log(response) { + const { counters, resultConsumedAfter, resultAvailableAfter, query } = response.summary + const { text, parameters } = query + debugCypher('%s', text) + debugCypher('%o', parameters) + debugStats('%o', counters) + debugStats('%o', { + resultConsumedAfter: resultConsumedAfter.toNumber(), + resultAvailableAfter: resultAvailableAfter.toNumber(), + }) +} diff --git a/backend/src/schema/resolvers/helpers/existingEmailAddress.js b/backend/src/schema/resolvers/helpers/existingEmailAddress.js new file mode 100644 index 000000000..960b2066f --- /dev/null +++ b/backend/src/schema/resolvers/helpers/existingEmailAddress.js @@ -0,0 +1,29 @@ +import { UserInputError } from 'apollo-server' + +export default async function alreadyExistingMail({ args, context }) { + const session = context.driver.session() + try { + const existingEmailAddressTxPromise = session.writeTransaction(async transaction => { + const existingEmailAddressTransactionResponse = await transaction.run( + ` + MATCH (email:EmailAddress {email: $email}) + OPTIONAL MATCH (email)-[:BELONGS_TO]-(user) + RETURN email, user + `, + { email: args.email }, + ) + return existingEmailAddressTransactionResponse.records.map(record => { + return { + alreadyExistingEmail: record.get('email').properties, + user: record.get('user') && record.get('user').properties, + } + }) + }) + const [emailBelongsToUser] = await existingEmailAddressTxPromise + const { alreadyExistingEmail, user } = emailBelongsToUser || {} + if (user) throw new UserInputError('A user account with this email already exists.') + return alreadyExistingEmail + } finally { + session.close() + } +} diff --git a/backend/src/schema/resolvers/helpers/filterForMutedUsers.js b/backend/src/schema/resolvers/helpers/filterForMutedUsers.js new file mode 100644 index 000000000..78f461c28 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/filterForMutedUsers.js @@ -0,0 +1,22 @@ +import { getMutedUsers } from '../users.js' +import { mergeWith, isArray } from 'lodash' + +export const filterForMutedUsers = async (params, context) => { + if (!context.user) return params + const [mutedUsers] = await Promise.all([getMutedUsers(context)]) + const mutedUsersIds = [...mutedUsers.map(user => user.id)] + if (!mutedUsersIds.length) return params + + params.filter = mergeWith( + params.filter, + { + author_not: { id_in: mutedUsersIds }, + }, + (objValue, srcValue) => { + if (isArray(objValue)) { + return objValue.concat(srcValue) + } + }, + ) + return params +} diff --git a/backend/src/schema/resolvers/helpers/generateNonce.js b/backend/src/schema/resolvers/helpers/generateNonce.js new file mode 100644 index 000000000..4dde1df04 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/generateNonce.js @@ -0,0 +1,4 @@ +import uuid from 'uuid/v4' +export default function generateNonce() { + return uuid().substring(0, 6) +} diff --git a/backend/src/schema/resolvers/helpers/normalizeEmail.js b/backend/src/schema/resolvers/helpers/normalizeEmail.js new file mode 100644 index 000000000..bdd12e991 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/normalizeEmail.js @@ -0,0 +1,11 @@ +import { normalizeEmail } from 'validator' + +export default email => + normalizeEmail(email, { + // gmail_remove_dots: true, default + gmail_remove_subaddress: false, + // gmail_convert_googlemaildotcom: true, default + outlookdotcom_remove_subaddress: false, + yahoo_remove_subaddress: false, + icloud_remove_subaddress: false, + }) diff --git a/backend/src/schema/resolvers/locations.js b/backend/src/schema/resolvers/locations.js new file mode 100644 index 000000000..be72001f7 --- /dev/null +++ b/backend/src/schema/resolvers/locations.js @@ -0,0 +1,19 @@ +import Resolver from './helpers/Resolver' + +export default { + Location: { + ...Resolver('Location', { + undefinedToNull: [ + 'nameEN', + 'nameDE', + 'nameFR', + 'nameNL', + 'nameIT', + 'nameES', + 'namePT', + 'namePL', + 'nameRU', + ], + }), + }, +} diff --git a/backend/src/schema/resolvers/locations.spec.js b/backend/src/schema/resolvers/locations.spec.js new file mode 100644 index 000000000..aba11f9bc --- /dev/null +++ b/backend/src/schema/resolvers/locations.spec.js @@ -0,0 +1,85 @@ +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' +import { createTestClient } from 'apollo-server-testing' + +const factory = Factory() + +let mutate, authenticatedUser + +const driver = getDriver() +const neode = getNeode() + +beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('resolvers', () => { + describe('Location', () => { + describe('custom mutation, not handled by neo4j-graphql-js', () => { + let variables + const updateUserMutation = gql` + mutation($id: ID!, $name: String) { + UpdateUser(id: $id, name: $name) { + name + location { + name: nameRU + nameEN + } + } + } + ` + + beforeEach(async () => { + variables = { + id: 'u47', + name: 'John Doughnut', + } + const Paris = await factory.create('Location', { + id: 'region.9397217726497330', + name: 'Paris', + type: 'region', + lat: 2.35183, + lng: 48.85658, + nameEN: 'Paris', + }) + + const user = await factory.create('User', { + id: 'u47', + name: 'John Doe', + }) + await user.relateTo(Paris, 'isIn') + authenticatedUser = await user.toJson() + }) + + it('returns `null` if location translation is not available', async () => { + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ + data: { + UpdateUser: { + name: 'John Doughnut', + location: { + name: null, + nameEN: 'Paris', + }, + }, + }, + errors: undefined, + }) + }) + }) + }) +}) diff --git a/backend/src/schema/resolvers/moderation.js b/backend/src/schema/resolvers/moderation.js index d61df7545..4bdf82d50 100644 --- a/backend/src/schema/resolvers/moderation.js +++ b/backend/src/schema/resolvers/moderation.js @@ -1,41 +1,51 @@ +const transformReturnType = record => { + return { + ...record.get('review').properties, + report: record.get('report').properties, + resource: { + __typename: record.get('type'), + ...record.get('resource').properties, + }, + } +} + export default { Mutation: { - disable: async (object, params, { user, driver }) => { - const { id } = params - const { id: userId } = user - const cypher = ` - MATCH (u:User {id: $userId}) - MATCH (resource {id: $id}) - WHERE resource:User OR resource:Comment OR resource:Post - SET resource.disabled = true - MERGE (resource)<-[:DISABLED]-(u) - RETURN resource {.id} - ` + review: async (_object, params, context, _resolveInfo) => { + const { user: moderator, driver } = context + + let createdRelationshipWithNestedAttributes = null // return value const session = driver.session() - const res = await session.run(cypher, { id, userId }) - session.close() - const [resource] = res.records.map(record => { - return record.get('resource') - }) - if (!resource) return null - return resource.id - }, - enable: async (object, params, { user, driver }) => { - const { id } = params - const cypher = ` - MATCH (resource {id: $id})<-[d:DISABLED]-() - SET resource.disabled = false - DELETE d - RETURN resource {.id} - ` - const session = driver.session() - const res = await session.run(cypher, { id }) - session.close() - const [resource] = res.records.map(record => { - return record.get('resource') - }) - if (!resource) return null - return resource.id + try { + const cypher = ` + MATCH (moderator:User {id: $moderatorId}) + MATCH (resource {id: $params.resourceId})<-[:BELONGS_TO]-(report:Report {closed: false}) + WHERE resource:User OR resource:Post OR resource:Comment + MERGE (report)<-[review:REVIEWED]-(moderator) + ON CREATE SET review.createdAt = $dateTime, review.updatedAt = review.createdAt + ON MATCH SET review.updatedAt = $dateTime + SET review.disable = $params.disable + SET report.updatedAt = $dateTime, report.closed = $params.closed + SET resource.disabled = review.disable + + RETURN review, report, resource, labels(resource)[0] AS type + ` + const reviewWriteTxResultPromise = session.writeTransaction(async txc => { + const reviewTransactionResponse = await txc.run(cypher, { + params, + moderatorId: moderator.id, + dateTime: new Date().toISOString(), + }) + return reviewTransactionResponse.records.map(transformReturnType) + }) + const txResult = await reviewWriteTxResultPromise + if (!txResult[0]) return null + createdRelationshipWithNestedAttributes = txResult[0] + } finally { + session.close() + } + + return createdRelationshipWithNestedAttributes }, }, } diff --git a/backend/src/schema/resolvers/moderation.spec.js b/backend/src/schema/resolvers/moderation.spec.js index 3107a5799..cd502be75 100644 --- a/backend/src/schema/resolvers/moderation.spec.js +++ b/backend/src/schema/resolvers/moderation.spec.js @@ -1,420 +1,698 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../../seed/factories' -import { host, login, gql } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' -let client const factory = Factory() -const instance = neode() -const categoryIds = ['cat9'] +const neode = getNeode() +const driver = getDriver() -const setupAuthenticateClient = params => { - const authenticateClient = async () => { - await factory.create('User', params) - const headers = await login(params) - client = new GraphQLClient(host, { headers }) - } - return authenticateClient -} +let mutate, + authenticatedUser, + disableVariables, + enableVariables, + moderator, + nonModerator, + closeReportVariables -let createResource -let authenticateClient -let createPostVariables -let createCommentVariables - -beforeEach(async () => { - createResource = () => {} - authenticateClient = () => { - client = new GraphQLClient(host) - } - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) -}) - -const setup = async () => { - await createResource() - await authenticateClient() -} - -afterEach(async () => { - await factory.cleanDatabase() -}) - -describe('disable', () => { - const mutation = gql` - mutation($id: ID!) { - disable(id: $id) +const reviewMutation = gql` + mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) { + review(resourceId: $resourceId, disable: $disable, closed: $closed) { + createdAt + updatedAt + resource { + __typename + ... on User { + id + disabled + } + ... on Post { + id + disabled + } + ... on Comment { + id + disabled + } + } + report { + id + createdAt + updatedAt + closed + reviewed { + createdAt + moderator { + id + } + } + } } - ` - let variables - - beforeEach(() => { - // our defaul set of variables - variables = { - id: 'blabla', - } - }) - - const action = async () => { - return client.request(mutation, variables) } +` - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') +describe('moderate resources', () => { + beforeAll(async () => { + await factory.cleanDatabase() + authenticatedUser = undefined + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate }) - describe('authenticated', () => { - beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - email: 'user@example.org', - password: '1234', + beforeEach(async () => { + disableVariables = { + resourceId: 'undefined-resource', + disable: true, + closed: false, + } + enableVariables = { + resourceId: 'undefined-resource', + disable: false, + closed: false, + } + authenticatedUser = null + moderator = await factory.create('User', { + id: 'moderator-id', + name: 'Moderator', + email: 'moderator@example.org', + password: '1234', + role: 'moderator', + }) + nonModerator = await factory.create('User', { + id: 'non-moderator', + name: 'Non Moderator', + email: 'non.moderator@example.org', + password: '1234', + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('review to close report, leaving resource enabled', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) }) }) - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await nonModerator.toJson() + }) + + it('non-moderator receives an authorization error', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) }) - describe('as moderator', () => { - beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - id: 'u7', - email: 'moderator@example.org', - password: '1234', - role: 'moderator', + describe('moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + const questionablePost = await factory.create('Post', { + id: 'should-i-be-disabled', + }) + const reportAgainstQuestionablePost = await factory.create('Report') + await Promise.all([ + reportAgainstQuestionablePost.relateTo(nonModerator, 'filed', { + resourceId: 'should-i-be-disabled', + reasonCategory: 'doxing', + reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!", + }), + reportAgainstQuestionablePost.relateTo(questionablePost, 'belongsTo'), + ]) + closeReportVariables = { + resourceId: 'should-i-be-disabled', + disable: false, + closed: true, + } + }) + + it('report can be closed without disabling resource', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: closeReportVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Post', id: 'should-i-be-disabled', disabled: false }, + report: { id: expect.any(String), closed: true }, + }, + }, + errors: undefined, }) }) - describe('on something that is not a (Comment|Post|User) ', () => { - beforeEach(async () => { - variables = { - id: 't23', - } - createResource = () => { - return Promise.all([factory.create('Tag', { id: 't23' })]) - } - }) - - it('returns null', async () => { - const expected = { disable: null } - await setup() - await expect(action()).resolves.toEqual(expected) - }) + it('creates only one review for multiple reviews by the same moderator on same resource', async () => { + await Promise.all([ + mutate({ + mutation: reviewMutation, + variables: { ...disableVariables, resourceId: 'should-i-be-disabled' }, + }), + mutate({ + mutation: reviewMutation, + variables: { ...enableVariables, resourceId: 'should-i-be-disabled' }, + }), + ]) + const cypher = + 'MATCH (:Report)<-[review:REVIEWED]-(moderator:User {id: "moderator-id"}) RETURN review' + const reviews = await neode.cypher(cypher) + expect(reviews.records).toHaveLength(1) }) - describe('on a comment', () => { + it('updates the updatedAt attribute', async () => { + const [firstReview, secondReview] = await Promise.all([ + mutate({ + mutation: reviewMutation, + variables: { ...disableVariables, resourceId: 'should-i-be-disabled' }, + }), + mutate({ + mutation: reviewMutation, + variables: { ...enableVariables, resourceId: 'should-i-be-disabled' }, + }), + ]) + expect(firstReview.data.review.updatedAt).toBeTruthy() + expect(Date.parse(firstReview.data.review.updatedAt)).toEqual(expect.any(Number)) + expect(secondReview.data.review.updatedAt).toBeTruthy() + expect(Date.parse(secondReview.data.review.updatedAt)).toEqual(expect.any(Number)) + expect(firstReview.data.review.updatedAt).not.toEqual(secondReview.data.review.updatedAt) + }) + }) + }) + + describe('review to disable', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await nonModerator.toJson() + }) + + it('non-moderator receives an authorization error', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + }) + + describe('moderate a comment', () => { beforeEach(async () => { - variables = { - id: 'c47', - } - createPostVariables = { - id: 'p3', - title: 'post to comment on', - content: 'please comment on me', - categoryIds, - } - 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', - }) - const asAuthenticatedUser = await factory.authenticateAs({ - email: 'commenter@example.org', - password: '1234', - }) - await asAuthenticatedUser.create('Post', createPostVariables) - await asAuthenticatedUser.create('Comment', createCommentVariables) + const trollingComment = await factory.create('Comment', { + id: 'comment-id', + }) + const reportAgainstTrollingComment = await factory.create('Report') + await Promise.all([ + reportAgainstTrollingComment.relateTo(nonModerator, 'filed', { + resourceId: 'comment-id', + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + }), + reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'), + ]) + disableVariables = { + ...disableVariables, + resourceId: 'comment-id', } }) it('returns disabled resource id', async () => { - const expected = { disable: 'c47' } - await setup() - await expect(action()).resolves.toEqual(expected) + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } }, + errors: undefined, + }) }) - it('changes .disabledBy', async () => { - const before = { Comment: [{ id: 'c47', disabledBy: null }] } - const expected = { Comment: [{ id: 'c47', disabledBy: { id: 'u7' } }] } - - await setup() - await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( - before, - ) - await action() + it('returns .reviewed', async () => { await expect( - client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(expected) + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Comment', id: 'comment-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + errors: undefined, + }) }) it('updates .disabled on comment', async () => { - const before = { Comment: [{ id: 'c47', disabled: false }] } - const expected = { Comment: [{ id: 'c47', disabled: true }] } - - await setup() - await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(before) - await action() await expect( - client.request('{ Comment(disabled: true) { id disabled } }'), - ).resolves.toEqual(expected) + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { + review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: true } }, + }, + errors: undefined, + }) + }) + + it('can be closed with one review', async () => { + closeReportVariables = { + ...disableVariables, + closed: true, + } + await expect( + mutate({ mutation: reviewMutation, variables: closeReportVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Comment', id: 'comment-id' }, + report: { id: expect.any(String), closed: true }, + }, + }, + errors: undefined, + }) }) }) - describe('on a post', () => { + describe('moderate a post', () => { beforeEach(async () => { - variables = { - id: 'p9', - } - - createResource = async () => { - await factory.create('User', { email: 'author@example.org', password: '1234' }) - await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) - await factory.create('Post', { - id: 'p9', // that's the ID we will look for - categoryIds, - }) + const trollingPost = await factory.create('Post', { + id: 'post-id', + }) + const reportAgainstTrollingPost = await factory.create('Report') + await Promise.all([ + reportAgainstTrollingPost.relateTo(nonModerator, 'filed', { + resourceId: 'post-id', + reasonCategory: 'doxing', + reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!", + }), + reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'), + ]) + disableVariables = { + ...disableVariables, + resourceId: 'post-id', } }) it('returns disabled resource id', async () => { - const expected = { disable: 'p9' } - await setup() - await expect(action()).resolves.toEqual(expected) + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Post', id: 'post-id' }, + }, + }, + errors: undefined, + }) }) - it('changes .disabledBy', async () => { - const before = { Post: [{ id: 'p9', disabledBy: null }] } - const expected = { Post: [{ id: 'p9', disabledBy: { id: 'u7' } }] } - - await setup() - await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( - before, - ) - await action() + it('returns .reviewed', async () => { await expect( - client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(expected) + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Post', id: 'post-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + errors: undefined, + }) }) it('updates .disabled on post', async () => { - const before = { Post: [{ id: 'p9', disabled: false }] } - const expected = { Post: [{ id: 'p9', disabled: true }] } + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'Post', id: 'post-id', disabled: true } } }, + errors: undefined, + }) + }) - await setup() - await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(before) - await action() - await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( - expected, - ) - }) - }) - }) - }) -}) - -describe('enable', () => { - const mutation = gql` - mutation($id: ID!) { - enable(id: $id) - } - ` - let variables - - const action = async () => { - return client.request(mutation, variables) - } - - beforeEach(() => { - // our defaul set of variables - variables = { - id: 'blabla', - } - }) - - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') - }) - - describe('authenticated', () => { - beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - email: 'user@example.org', - password: '1234', - }) - }) - - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') - }) - - describe('as moderator', () => { - beforeEach(async () => { - authenticateClient = setupAuthenticateClient({ - role: 'moderator', - email: 'someuser@example.org', - password: '1234', - }) - }) - - describe('on something that is not a (Comment|Post|User) ', () => { - beforeEach(async () => { - variables = { - id: 't23', - } - createResource = () => { - // we cannot create a :DISABLED relationship here - return Promise.all([factory.create('Tag', { id: 't23' })]) - } - }) - - it('returns null', async () => { - const expected = { enable: null } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - }) - - describe('on a comment', () => { - beforeEach(async () => { - variables = { - id: 'c456', - } - createPostVariables = { - id: 'p9', - title: 'post to comment on', - content: 'please comment on me', - categoryIds, - } - 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', - }) - const asAuthenticatedUser = await factory.authenticateAs({ - email: 'author@example.org', - password: '1234', - }) - await asAuthenticatedUser.create('Post', createPostVariables) - await asAuthenticatedUser.create('Comment', createCommentVariables) - - const disableMutation = gql` - mutation { - disable(id: "c456") - } - ` - await factory.mutate(disableMutation) // that's we want to delete - } - }) - - it('returns disabled resource id', async () => { - const expected = { enable: 'c456' } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - - it('changes .disabledBy', async () => { - const before = { Comment: [{ id: 'c456', disabledBy: { id: 'u123' } }] } - const expected = { Comment: [{ id: 'c456', disabledBy: null }] } - - await setup() - await expect( - client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(before) - await action() - await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( - expected, - ) - }) - - it('updates .disabled on post', async () => { - const before = { Comment: [{ id: 'c456', disabled: true }] } - const expected = { Comment: [{ id: 'c456', disabled: false }] } - - await setup() - await expect( - client.request('{ Comment(disabled: true) { id disabled } }'), - ).resolves.toEqual(before) - await action() // this updates .disabled - await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(expected) - }) - }) - - describe('on a post', () => { - beforeEach(async () => { - variables = { - id: 'p9', - } - - createResource = async () => { - await factory.create('User', { - id: 'u123', - email: 'author@example.org', - password: '1234', - }) - await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) - await factory.create('Post', { - id: 'p9', // that's the ID we will look for - categoryIds, - }) - - const disableMutation = gql` - mutation { - disable(id: "p9") - } - ` - await factory.mutate(disableMutation) // that's we want to delete - } - }) - - it('returns disabled resource id', async () => { - const expected = { enable: 'p9' } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - - it('changes .disabledBy', async () => { - const before = { Post: [{ id: 'p9', disabledBy: { id: 'u123' } }] } - const expected = { Post: [{ id: 'p9', disabledBy: null }] } - - await setup() - await expect( - client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(before) - await action() - await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( - expected, - ) - }) - - it('updates .disabled on post', async () => { - const before = { Post: [{ id: 'p9', disabled: true }] } - const expected = { Post: [{ id: 'p9', disabled: false }] } - - await setup() - await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( - before, - ) - await action() // this updates .disabled - await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(expected) + it('can be closed with one review', async () => { + closeReportVariables = { + ...disableVariables, + closed: true, + } + await expect( + mutate({ mutation: reviewMutation, variables: closeReportVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Post', id: 'post-id' }, + report: { id: expect.any(String), closed: true }, + }, + }, + errors: undefined, + }) + }) + }) + + describe('moderate a user', () => { + beforeEach(async () => { + const troll = await factory.create('User', { + id: 'user-id', + }) + const reportAgainstTroll = await factory.create('Report') + await Promise.all([ + reportAgainstTroll.relateTo(nonModerator, 'filed', { + resourceId: 'user-id', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This user is harassing me with bigoted remarks!', + }), + reportAgainstTroll.relateTo(troll, 'belongsTo'), + ]) + disableVariables = { + ...disableVariables, + resourceId: 'user-id', + } + }) + + it('returns disabled resource id', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'User', id: 'user-id' } } }, + errors: undefined, + }) + }) + + it('returns .reviewed', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'User', id: 'user-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + errors: undefined, + }) + }) + + it('updates .disabled on user', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: disableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'User', id: 'user-id', disabled: true } } }, + errors: undefined, + }) + }) + + it('can be closed with one review', async () => { + closeReportVariables = { + ...disableVariables, + closed: true, + } + await expect( + mutate({ mutation: reviewMutation, variables: closeReportVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'User', id: 'user-id' }, + report: { id: expect.any(String), closed: true }, + }, + }, + errors: undefined, + }) + }) + }) + }) + }) + + describe('review to re-enable after disabled', () => { + describe('unautenticated user', () => { + it('throws authorization error', async () => { + enableVariables = { + ...enableVariables, + resourceId: 'post-id', + } + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated user', () => { + describe('non moderator', () => { + beforeEach(async () => { + authenticatedUser = await nonModerator.toJson() + }) + + it('throws authorization error', async () => { + enableVariables = { + ...enableVariables, + resourceId: 'post-id', + } + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + }) + + describe('moderate a comment', () => { + beforeEach(async () => { + const trollingComment = await factory.create('Comment', { + id: 'comment-id', + }) + const reportAgainstTrollingComment = await factory.create('Report') + await Promise.all([ + reportAgainstTrollingComment.relateTo(nonModerator, 'filed', { + resourceId: 'comment-id', + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + }), + reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'), + ]) + await Promise.all([ + reportAgainstTrollingComment.relateTo(moderator, 'reviewed', { + ...disableVariables, + resourceId: 'comment-id', + }), + trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }), + ]) + enableVariables = { + ...enableVariables, + resourceId: 'comment-id', + } + }) + + it('returns enabled resource id', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } }, + }) + }) + + it('returns .reviewed', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Comment', id: 'comment-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + }) + }) + + it('updates .disabled on comment', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: false } }, + }, + }) + }) + }) + + describe('moderate a post', () => { + beforeEach(async () => { + const trollingPost = await factory.create('Post', { + id: 'post-id', + }) + const reportAgainstTrollingPost = await factory.create('Report') + await Promise.all([ + reportAgainstTrollingPost.relateTo(nonModerator, 'filed', { + resourceId: 'post-id', + reasonCategory: 'doxing', + reasonDescription: + "This shouldn't be shown to anybody else! It's my private thing!", + }), + reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'), + ]) + await Promise.all([ + reportAgainstTrollingPost.relateTo(moderator, 'reviewed', { + ...disableVariables, + resourceId: 'comment-id', + }), + trollingPost.update({ disabled: true, updatedAt: new Date().toISOString() }), + ]) + enableVariables = { + ...enableVariables, + resourceId: 'post-id', + } + }) + + it('returns enabled resource id', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'Post', id: 'post-id' } } }, + }) + }) + + it('returns .reviewed', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'Post', id: 'post-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + }) + }) + + it('updates .disabled on post', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { resource: { __typename: 'Post', id: 'post-id', disabled: false } }, + }, + }) + }) + }) + + describe('moderate a user', () => { + beforeEach(async () => { + const troll = await factory.create('User', { + id: 'user-id', + }) + const reportAgainstTroll = await factory.create('Report') + await Promise.all([ + reportAgainstTroll.relateTo(nonModerator, 'filed', { + resourceId: 'user-id', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This user is harassing me with bigoted remarks!', + }), + reportAgainstTroll.relateTo(troll, 'belongsTo'), + ]) + await Promise.all([ + reportAgainstTroll.relateTo(moderator, 'reviewed', { + ...disableVariables, + resourceId: 'comment-id', + }), + troll.update({ disabled: true, updatedAt: new Date().toISOString() }), + ]) + enableVariables = { + ...enableVariables, + resourceId: 'user-id', + } + }) + + it('returns enabled resource id', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { review: { resource: { __typename: 'User', id: 'user-id' } } }, + }) + }) + + it('returns .reviewed', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { + resource: { __typename: 'User', id: 'user-id' }, + report: { + id: expect.any(String), + reviewed: expect.arrayContaining([ + { createdAt: expect.any(String), moderator: { id: 'moderator-id' } }, + ]), + }, + }, + }, + }) + }) + + it('updates .disabled on user', async () => { + await expect( + mutate({ mutation: reviewMutation, variables: enableVariables }), + ).resolves.toMatchObject({ + data: { + review: { resource: { __typename: 'User', id: 'user-id', disabled: false } }, + }, + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 4cab1ffc4..31369a8c7 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -1,3 +1,5 @@ +import log from './helpers/databaseLogger' + const resourceTypes = ['Post', 'Comment'] const transformReturnType = record => { @@ -18,9 +20,8 @@ export default { notifications: async (_parent, args, context, _resolveInfo) => { const { user: currentUser } = context const session = context.driver.session() - let notifications - let whereClause - let orderByClause + let whereClause, orderByClause + switch (args.read) { case true: whereClause = 'WHERE notification.read = TRUE' @@ -41,40 +42,64 @@ export default { default: orderByClause = '' } + const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : '' + const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : '' + const readTxResultPromise = session.readTransaction(async transaction => { + const notificationsTransactionResponse = await transaction.run( + ` + MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) + ${whereClause} + WITH user, notification, resource, + [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts + WITH resource, user, notification, authors, posts, + resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource + RETURN notification {.*, from: finalResource, to: properties(user)} + ${orderByClause} + ${offset} ${limit} + `, + { id: currentUser.id }, + ) + log(notificationsTransactionResponse) + return notificationsTransactionResponse.records.map(record => record.get('notification')) + }) try { - const cypher = ` - MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) - ${whereClause} - RETURN resource, notification, user - ${orderByClause} - ` - const result = await session.run(cypher, { id: currentUser.id }) - notifications = await result.records.map(transformReturnType) + const notifications = await readTxResultPromise + return notifications } finally { session.close() } - return notifications }, }, Mutation: { markAsRead: async (parent, args, context, resolveInfo) => { const { user: currentUser } = context const session = context.driver.session() - let notification + const writeTxResultPromise = session.writeTransaction(async transaction => { + const markNotificationAsReadTransactionResponse = await transaction.run( + ` + MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id}) + SET notification.read = TRUE + RETURN resource, notification, user + `, + { resourceId: args.id, id: currentUser.id }, + ) + log(markNotificationAsReadTransactionResponse) + return markNotificationAsReadTransactionResponse.records.map(transformReturnType) + }) try { - const cypher = ` - MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id}) - SET notification.read = TRUE - RETURN resource, notification, user - ` - const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id }) - const notifications = await result.records.map(transformReturnType) - notification = notifications[0] + const [notifications] = await writeTxResultPromise + return notifications } finally { session.close() } - return notification + }, + }, + NOTIFIED: { + id: async parent => { + // serialize an ID to help the client update the cache + return `${parent.reason}/${parent.from.id}/${parent.to.id}` }, }, } diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 0e4fc48f7..a5c46e930 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,6 +1,6 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { getDriver } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getDriver } from '../../db/neo4j' import { createTestClient } from 'apollo-server-testing' import createServer from '../.././server' @@ -184,6 +184,7 @@ describe('given some notifications', () => { data: { notifications: expect.arrayContaining(expected), }, + errors: undefined, }) }) }) @@ -192,7 +193,7 @@ describe('given some notifications', () => { it('returns only unread notifications of current user', async () => { const expected = expect.objectContaining({ data: { - notifications: [ + notifications: expect.arrayContaining([ { from: { __typename: 'Comment', @@ -209,12 +210,15 @@ describe('given some notifications', () => { read: false, createdAt: '2019-08-31T17:33:48.651Z', }, - ], + ]), }, }) - await expect( - query({ query: notificationQuery, variables: { ...variables, read: false } }), - ).resolves.toEqual(expected) + const response = await query({ + query: notificationQuery, + variables: { ...variables, read: false }, + }) + await expect(response).toMatchObject(expected) + await expect(response.data.notifications.length).toEqual(2) // double-check }) describe('if a resource gets deleted', () => { @@ -230,7 +234,10 @@ describe('given some notifications', () => { ` await expect( mutate({ mutation: deletePostMutation, variables: { id: 'p3' } }), - ).resolves.toMatchObject({ data: { DeletePost: { id: 'p3', deleted: true } } }) + ).resolves.toMatchObject({ + data: { DeletePost: { id: 'p3', deleted: true } }, + errors: undefined, + }) authenticatedUser = await user.toJson() } @@ -239,11 +246,12 @@ describe('given some notifications', () => { query({ query: notificationQuery, variables: { ...variables, read: false } }), ).resolves.toMatchObject({ data: { notifications: [expect.any(Object), expect.any(Object)] }, + errors: undefined, }) await deletePostAction() await expect( query({ query: notificationQuery, variables: { ...variables, read: false } }), - ).resolves.toMatchObject({ data: { notifications: [] } }) + ).resolves.toMatchObject({ data: { notifications: [] }, errors: undefined }) }) }) }) diff --git a/backend/src/schema/resolvers/passwordReset.js b/backend/src/schema/resolvers/passwordReset.js index 3c5f4636c..74c71e011 100644 --- a/backend/src/schema/resolvers/passwordReset.js +++ b/backend/src/schema/resolvers/passwordReset.js @@ -1,34 +1,6 @@ import uuid from 'uuid/v4' import bcrypt from 'bcryptjs' - -export async function createPasswordReset(options) { - const { driver, nonce, email, issuedAt = new Date() } = options - const session = driver.session() - let response = {} - try { - const cypher = ` - MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email}) - CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL}) - MERGE (u)-[:REQUESTED]->(pr) - RETURN e, pr, u - ` - const transactionRes = await session.run(cypher, { - issuedAt: issuedAt.toISOString(), - nonce, - email, - }) - const records = transactionRes.records.map(record => { - const { email } = record.get('e').properties - const { nonce } = record.get('pr').properties - const { name } = record.get('u').properties - return { email, nonce, name } - }) - response = records[0] || {} - } finally { - session.close() - } - return response -} +import createPasswordReset from './helpers/createPasswordReset' export default { Mutation: { @@ -37,28 +9,35 @@ export default { return createPasswordReset({ driver, nonce, email }) }, resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => { - const session = driver.session() const stillValid = new Date() stillValid.setDate(stillValid.getDate() - 1) const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10) - const cypher = ` - MATCH (pr:PasswordReset {nonce: $nonce}) - MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr) - WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL - SET pr.usedAt = datetime() - SET u.encryptedPassword = $encryptedNewPassword - RETURN pr - ` - const transactionRes = await session.run(cypher, { - stillValid, - email, - nonce, - encryptedNewPassword, - }) - const [reset] = transactionRes.records.map(record => record.get('pr')) - const response = !!(reset && reset.properties.usedAt) - session.close() - return response + const session = driver.session() + try { + const passwordResetTxPromise = session.writeTransaction(async transaction => { + const passwordResetTransactionResponse = await transaction.run( + ` + MATCH (passwordReset:PasswordReset {nonce: $nonce}) + MATCH (email:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(user:User)-[:REQUESTED]->(passwordReset) + WHERE duration.between(passwordReset.issuedAt, datetime()).days <= 0 AND passwordReset.usedAt IS NULL + SET passwordReset.usedAt = datetime() + SET user.encryptedPassword = $encryptedNewPassword + RETURN passwordReset + `, + { + stillValid, + email, + nonce, + encryptedNewPassword, + }, + ) + return passwordResetTransactionResponse.records.map(record => record.get('passwordReset')) + }) + const [reset] = await passwordResetTxPromise + return !!(reset && reset.properties.usedAt) + } finally { + session.close() + } }, }, } diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js index fabee1c7e..d7b3a0157 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.js +++ b/backend/src/schema/resolvers/passwordReset.spec.js @@ -1,7 +1,7 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' -import { createPasswordReset } from './passwordReset' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createPasswordReset from './helpers/createPasswordReset' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -14,10 +14,10 @@ let authenticatedUser let variables const getAllPasswordResets = async () => { - const session = driver.session() - const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r') - const resets = transactionRes.records.map(record => record.get('r')) - session.close() + const passwordResetQuery = await neode.cypher( + 'MATCH (passwordReset:PasswordReset) RETURN passwordReset', + ) + const resets = passwordResetQuery.records.map(record => record.get('passwordReset')) return resets } @@ -109,10 +109,7 @@ describe('passwordReset', () => { describe('resetPassword', () => { const setup = async (options = {}) => { const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcdef' } = options - - const session = driver.session() await createPasswordReset({ driver, email, issuedAt, nonce }) - session.close() } const mutation = gql` diff --git a/backend/src/schema/resolvers/passwordReset/emailTemplates.js b/backend/src/schema/resolvers/passwordReset/emailTemplates.js deleted file mode 100644 index 8508adccc..000000000 --- a/backend/src/schema/resolvers/passwordReset/emailTemplates.js +++ /dev/null @@ -1,85 +0,0 @@ -import CONFIG from '../../../config' - -export const from = '"Human Connection" ' - -export const resetPasswordMail = options => { - const { - name, - email, - code, - subject = 'Use this link to reset your password. The link is only valid for 24 hours.', - supportUrl = 'https://human-connection.org/en/contact/', - } = options - const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('code', code) - actionUrl.searchParams.set('email', email) - - return { - to: email, - subject, - text: ` -Hi ${name}! - -You recently requested to reset your password for your Human Connection account. -Use the link below to reset it. This password reset is only valid for the next -24 hours. - -${actionUrl} - -If you did not request a password reset, please ignore this email or contact -support if you have questions: - -${supportUrl} - -Thanks, -The Human Connection Team - -If you're having trouble with the link above, you can manually copy and -paste the following code into your browser window: - -${code} - -Human Connection gemeinnützige GmbH -Bahnhofstr. 11 -73235 Weilheim / Teck -Deutschland - `, - } -} - -export const wrongAccountMail = options => { - const { - email, - subject = `We received a request to reset your password with this email address (${email})`, - supportUrl = 'https://human-connection.org/en/contact/', - } = options - const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI) - return { - to: email, - subject, - text: ` -We received a request to reset the password to access Human Connection with your -email address, but we were unable to find an account associated with this -address. - -If you use Human Connection and were expecting this email, consider trying to -request a password reset using the email address associated with your account. -Try a different email: - -${actionUrl} - -If you do not use Human Connection or did not request a password reset, please -ignore this email. Feel free to contact support if you have further questions: - -${supportUrl} - -Thanks, -The Human Connection Team - -Human Connection gemeinnützige GmbH -Bahnhofstr. 11 -73235 Weilheim / Teck -Deutschland - `, - } -} diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index e65fa9b76..af8165997 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -1,76 +1,77 @@ import uuid from 'uuid/v4' import { neo4jgraphql } from 'neo4j-graphql-js' -import fileUpload from './fileUpload' -import { getBlockedUsers, getBlockedByUsers } from './users.js' -import { mergeWith, isArray } from 'lodash' +import { isEmpty } from 'lodash' import { UserInputError } from 'apollo-server' +import fileUpload from './fileUpload' import Resolver from './helpers/Resolver' +import { filterForMutedUsers } from './helpers/filterForMutedUsers' -const filterForBlockedUsers = async (params, context) => { - if (!context.user) return params - const [blockedUsers, blockedByUsers] = await Promise.all([ - getBlockedUsers(context), - getBlockedByUsers(context), - ]) - const badIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)] - if (!badIds.length) return params - - params.filter = mergeWith( - params.filter, - { - author_not: { id_in: badIds }, - }, - (objValue, srcValue) => { - if (isArray(objValue)) { - return objValue.concat(srcValue) - } - }, - ) +const maintainPinnedPosts = params => { + const pinnedPostFilter = { pinned: true } + if (isEmpty(params.filter)) { + params.filter = { OR: [pinnedPostFilter, {}] } + } else { + params.filter = { OR: [pinnedPostFilter, { ...params.filter }] } + } return params } export default { Query: { Post: async (object, params, context, resolveInfo) => { - params = await filterForBlockedUsers(params, context) - return neo4jgraphql(object, params, context, resolveInfo, false) + params = await filterForMutedUsers(params, context) + params = await maintainPinnedPosts(params) + return neo4jgraphql(object, params, context, resolveInfo) }, findPosts: async (object, params, context, resolveInfo) => { - params = await filterForBlockedUsers(params, context) - return neo4jgraphql(object, params, context, resolveInfo, false) + params = await filterForMutedUsers(params, context) + return neo4jgraphql(object, params, context, resolveInfo) + }, + profilePagePosts: async (object, params, context, resolveInfo) => { + params = await filterForMutedUsers(params, context) + return neo4jgraphql(object, params, context, resolveInfo) }, PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => { - const session = context.driver.session() const { postId, data } = params - const transactionRes = await session.run( - `MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-() - RETURN COUNT(DISTINCT emoted) as emotionsCount - `, - { postId, data }, - ) - session.close() - - const [emotionsCount] = transactionRes.records.map(record => { - return record.get('emotionsCount').low + const session = context.driver.session() + const readTxResultPromise = session.readTransaction(async transaction => { + const emotionsCountTransactionResponse = await transaction.run( + ` + MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-() + RETURN COUNT(DISTINCT emoted) as emotionsCount + `, + { postId, data }, + ) + return emotionsCountTransactionResponse.records.map( + record => record.get('emotionsCount').low, + ) }) - - return emotionsCount + try { + const [emotionsCount] = await readTxResultPromise + return emotionsCount + } finally { + session.close() + } }, PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => { - const session = context.driver.session() const { postId } = params - const transactionRes = await session.run( - `MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId}) - RETURN collect(emoted.emotion) as emotion`, - { userId: context.user.id, postId }, - ) - - session.close() - - const [emotions] = transactionRes.records.map(record => { - return record.get('emotion') + const session = context.driver.session() + const readTxResultPromise = session.readTransaction(async transaction => { + const emotionsTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId}) + RETURN collect(emoted.emotion) as emotion + `, + { userId: context.user.id, postId }, + ) + return emotionsTransactionResponse.records.map(record => record.get('emotion')) }) - return emotions + try { + const [emotions] = await readTxResultPromise + return emotions + } finally { + session.close() + } }, }, Mutation: { @@ -79,27 +80,29 @@ export default { delete params.categoryIds params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params.id = params.id || uuid() - let post - - const createPostCypher = `CREATE (post:Post {params}) - SET post.createdAt = toString(datetime()) - SET post.updatedAt = toString(datetime()) - WITH post - MATCH (author:User {id: $userId}) - MERGE (post)<-[:WROTE]-(author) - WITH post - UNWIND $categoryIds AS categoryId - MATCH (category:Category {id: categoryId}) - MERGE (post)-[:CATEGORIZED]->(category) - RETURN post` - - const createPostVariables = { userId: context.user.id, categoryIds, params } - const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async transaction => { + const createPostTransactionResponse = await transaction.run( + ` + CREATE (post:Post {params}) + SET post.createdAt = toString(datetime()) + SET post.updatedAt = toString(datetime()) + WITH post + MATCH (author:User {id: $userId}) + MERGE (post)<-[:WROTE]-(author) + WITH post + UNWIND $categoryIds AS categoryId + MATCH (category:Category {id: categoryId}) + MERGE (post)-[:CATEGORIZED]->(category) + RETURN post + `, + { userId: context.user.id, categoryIds, params }, + ) + return createPostTransactionResponse.records.map(record => record.get('post').properties) + }) try { - const transactionRes = await session.run(createPostCypher, createPostVariables) - const posts = transactionRes.records.map(record => record.get('post').properties) - post = posts[0] + const [post] = await writeTxResultPromise + return post } catch (e) { if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') throw new UserInputError('Post with this slug already exists!') @@ -107,114 +110,217 @@ export default { } finally { session.close() } - - return post }, UpdatePost: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params delete params.categoryIds params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) const session = context.driver.session() - - let updatePostCypher = `MATCH (post:Post {id: $params.id}) - SET post += $params - SET post.updatedAt = toString(datetime()) - ` + let updatePostCypher = ` + MATCH (post:Post {id: $params.id}) + SET post += $params + SET post.updatedAt = toString(datetime()) + WITH post + ` if (categoryIds && categoryIds.length) { const cypherDeletePreviousRelations = ` MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category) DELETE previousRelations RETURN post, category - ` + ` - await session.run(cypherDeletePreviousRelations, { params }) + await session.writeTransaction(transaction => { + return transaction.run(cypherDeletePreviousRelations, { params }) + }) updatePostCypher += ` - WITH post UNWIND $categoryIds AS categoryId MATCH (category:Category {id: categoryId}) MERGE (post)-[:CATEGORIZED]->(category) + WITH post ` } updatePostCypher += `RETURN post` const updatePostVariables = { categoryIds, params } - - const transactionRes = await session.run(updatePostCypher, updatePostVariables) - const [post] = transactionRes.records.map(record => { - return record.get('post').properties - }) - - session.close() - - return post + try { + const writeTxResultPromise = session.writeTransaction(async transaction => { + const updatePostTransactionResponse = await transaction.run( + updatePostCypher, + updatePostVariables, + ) + return updatePostTransactionResponse.records.map(record => record.get('post').properties) + }) + const [post] = await writeTxResultPromise + return post + } finally { + session.close() + } }, DeletePost: async (object, args, context, resolveInfo) => { const session = context.driver.session() - // we cannot set slug to 'UNAVAILABE' because of unique constraints - const transactionRes = await session.run( - ` - MATCH (post:Post {id: $postId}) - OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment) - SET post.deleted = TRUE - SET post.content = 'UNAVAILABLE' - SET post.contentExcerpt = 'UNAVAILABLE' - SET post.title = 'UNAVAILABLE' - SET comment.deleted = TRUE - REMOVE post.image - RETURN post - `, - { postId: args.id }, - ) - const [post] = transactionRes.records.map(record => record.get('post').properties) - return post + const writeTxResultPromise = session.writeTransaction(async transaction => { + const deletePostTransactionResponse = await transaction.run( + ` + MATCH (post:Post {id: $postId}) + OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment) + SET post.deleted = TRUE + SET post.content = 'UNAVAILABLE' + SET post.contentExcerpt = 'UNAVAILABLE' + SET post.title = 'UNAVAILABLE' + SET comment.deleted = TRUE + REMOVE post.image + RETURN post + `, + { postId: args.id }, + ) + return deletePostTransactionResponse.records.map(record => record.get('post').properties) + }) + try { + const [post] = await writeTxResultPromise + return post + } finally { + session.close() + } }, AddPostEmotions: async (object, params, context, resolveInfo) => { - const session = context.driver.session() const { to, data } = params const { user } = context - const transactionRes = await session.run( - `MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id}) - MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo) - RETURN userFrom, postTo, emotedRelation`, - { user, to, data }, - ) - session.close() - const [emoted] = transactionRes.records.map(record => { - return { - from: { ...record.get('userFrom').properties }, - to: { ...record.get('postTo').properties }, - ...record.get('emotedRelation').properties, - } + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async transaction => { + const addPostEmotionsTransactionResponse = await transaction.run( + ` + MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id}) + MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo) + RETURN userFrom, postTo, emotedRelation`, + { user, to, data }, + ) + return addPostEmotionsTransactionResponse.records.map(record => { + return { + from: { ...record.get('userFrom').properties }, + to: { ...record.get('postTo').properties }, + ...record.get('emotedRelation').properties, + } + }) }) - return emoted + try { + const [emoted] = await writeTxResultPromise + return emoted + } finally { + session.close() + } }, RemovePostEmotions: async (object, params, context, resolveInfo) => { - const session = context.driver.session() const { to, data } = params const { id: from } = context.user - const transactionRes = await session.run( - `MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id}) - DELETE emotedRelation - RETURN userFrom, postTo`, - { from, to, data }, - ) - session.close() - const [emoted] = transactionRes.records.map(record => { - return { - from: { ...record.get('userFrom').properties }, - to: { ...record.get('postTo').properties }, - emotion: data.emotion, - } + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async transaction => { + const removePostEmotionsTransactionResponse = await transaction.run( + ` + MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id}) + DELETE emotedRelation + RETURN userFrom, postTo + `, + { from, to, data }, + ) + return removePostEmotionsTransactionResponse.records.map(record => { + return { + from: { ...record.get('userFrom').properties }, + to: { ...record.get('postTo').properties }, + emotion: data.emotion, + } + }) }) - return emoted + try { + const [emoted] = await writeTxResultPromise + return emoted + } finally { + session.close() + } + }, + pinPost: async (_parent, params, context, _resolveInfo) => { + let pinnedPostWithNestedAttributes + const { driver, user } = context + const session = driver.session() + const { id: userId } = user + let writeTxResultPromise = session.writeTransaction(async transaction => { + const deletePreviousRelationsResponse = await transaction.run( + ` + MATCH (:User)-[previousRelations:PINNED]->(post:Post) + REMOVE post.pinned + DELETE previousRelations + RETURN post + `, + ) + return deletePreviousRelationsResponse.records.map(record => record.get('post').properties) + }) + try { + await writeTxResultPromise + + writeTxResultPromise = session.writeTransaction(async transaction => { + const pinPostTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $userId}) WHERE user.role = 'admin' + MATCH (post:Post {id: $params.id}) + MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post) + SET post.pinned = true + RETURN post, pinned.createdAt as pinnedAt + `, + { userId, params }, + ) + return pinPostTransactionResponse.records.map(record => ({ + pinnedPost: record.get('post').properties, + pinnedAt: record.get('pinnedAt'), + })) + }) + const [transactionResult] = await writeTxResultPromise + const { pinnedPost, pinnedAt } = transactionResult + pinnedPostWithNestedAttributes = { + ...pinnedPost, + pinnedAt, + } + } finally { + session.close() + } + return pinnedPostWithNestedAttributes + }, + unpinPost: async (_parent, params, context, _resolveInfo) => { + let unpinnedPost + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async transaction => { + const unpinPostTransactionResponse = await transaction.run( + ` + MATCH (:User)-[previousRelations:PINNED]->(post:Post {id: $params.id}) + REMOVE post.pinned + DELETE previousRelations + RETURN post + `, + { params }, + ) + return unpinPostTransactionResponse.records.map(record => record.get('post').properties) + }) + try { + ;[unpinnedPost] = await writeTxResultPromise + } finally { + session.close() + } + return unpinnedPost }, }, Post: { ...Resolver('Post', { - undefinedToNull: ['activityId', 'objectId', 'image', 'language'], + undefinedToNull: [ + 'activityId', + 'objectId', + 'image', + 'language', + 'pinnedAt', + 'pinned', + 'imageBlurred', + 'imageAspectRatio', + ], hasMany: { tags: '-[:TAGGED]->(related:Tag)', categories: '-[:CATEGORIZED]->(related:Category)', @@ -224,7 +330,7 @@ export default { }, hasOne: { author: '<-[:WROTE]-(related:User)', - disabledBy: '<-[:DISABLED]-(related:User)', + pinnedBy: '<-[:PINNED]-(related:User)', }, count: { commentsCount: @@ -241,21 +347,28 @@ export default { relatedContributions: async (parent, params, context, resolveInfo) => { if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions const { id } = parent - const statement = ` - MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post) - WHERE NOT post.deleted AND NOT post.disabled - RETURN DISTINCT post - LIMIT 10 - ` - let relatedContributions const session = context.driver.session() + + const writeTxResultPromise = session.writeTransaction(async transaction => { + const relatedContributionsTransactionResponse = await transaction.run( + ` + MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post) + WHERE NOT post.deleted AND NOT post.disabled + RETURN DISTINCT post + LIMIT 10 + `, + { id }, + ) + return relatedContributionsTransactionResponse.records.map( + record => record.get('post').properties, + ) + }) try { - const result = await session.run(statement, { id }) - relatedContributions = result.records.map(r => r.get('post').properties) + const relatedContributions = await writeTxResultPromise + return relatedContributions } finally { session.close() } - return relatedContributions }, }, } diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 0e7272e8e..56a47afa7 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -1,7 +1,7 @@ import { createTestClient } from 'apollo-server-testing' -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' const driver = getDriver() @@ -39,7 +39,8 @@ const createPostMutation = gql` } ` -beforeAll(() => { +beforeAll(async () => { + await factory.cleanDatabase() const { server } = createServer({ context: () => { return { @@ -209,6 +210,7 @@ describe('Post', () => { data: { Post: expect.arrayContaining(expected), }, + errors: undefined, }) }) }) @@ -228,7 +230,9 @@ describe('Post', () => { await user.relateTo(followedUser, 'following') variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } } - const expected = { + await expect( + query({ query: postQueryFilteredByUsersFollowed, variables }), + ).resolves.toMatchObject({ data: { Post: [ { @@ -237,10 +241,8 @@ describe('Post', () => { }, ], }, - } - await expect( - query({ query: postQueryFilteredByUsersFollowed, variables }), - ).resolves.toMatchObject(expected) + errors: undefined, + }) }) }) }) @@ -269,7 +271,10 @@ describe('CreatePost', () => { }) it('creates a post', async () => { - const expected = { data: { CreatePost: { title: 'I am a title', content: 'Some content' } } } + const expected = { + data: { CreatePost: { title: 'I am a title', content: 'Some content' } }, + errors: undefined, + } await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( expected, ) @@ -285,6 +290,7 @@ describe('CreatePost', () => { }, }, }, + errors: undefined, } await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( expected, @@ -310,53 +316,6 @@ describe('CreatePost', () => { ) }) }) - - describe('categories', () => { - describe('null', () => { - beforeEach(() => { - variables = { ...variables, categoryIds: null } - }) - it('throws UserInputError', async () => { - const { - errors: [error], - } = await mutate({ mutation: createPostMutation, variables }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', - ) - }) - }) - - describe('empty', () => { - beforeEach(() => { - variables = { ...variables, categoryIds: [] } - }) - it('throws UserInputError', async () => { - const { - errors: [error], - } = await mutate({ mutation: createPostMutation, variables }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', - ) - }) - }) - - describe('more than 3 items', () => { - beforeEach(() => { - variables = { ...variables, categoryIds: ['cat9', 'cat27', 'cat15', 'cat4'] } - }) - it('throws UserInputError', async () => { - const { - errors: [error], - } = await mutate({ mutation: createPostMutation, variables }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', - ) - }) - }) - }) }) }) @@ -366,7 +325,12 @@ describe('UpdatePost', () => { mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) { UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) { id + title content + author { + name + slug + } categories { id } @@ -386,7 +350,6 @@ describe('UpdatePost', () => { }) variables = { - ...variables, id: 'p9876', title: 'New title', content: 'New content', @@ -395,8 +358,11 @@ describe('UpdatePost', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { - const { errors } = await mutate({ mutation: updatePostMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + authenticatedUser = null + expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { UpdatePost: null }, + }) }) }) @@ -417,7 +383,10 @@ describe('UpdatePost', () => { }) it('updates a post', async () => { - const expected = { data: { UpdatePost: { id: 'p9876', content: 'New content' } } } + const expected = { + data: { UpdatePost: { id: 'p9876', content: 'New content' } }, + errors: undefined, + } await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( expected, ) @@ -428,6 +397,7 @@ describe('UpdatePost', () => { data: { UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) }, }, + errors: undefined, } await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( expected, @@ -455,6 +425,7 @@ describe('UpdatePost', () => { categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]), }, }, + errors: undefined, } await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( expected, @@ -475,78 +446,409 @@ describe('UpdatePost', () => { categories: expect.arrayContaining([{ id: 'cat27' }]), }, }, + errors: undefined, } await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( expected, ) }) + }) + }) - describe('more than 3 categories', () => { - beforeEach(() => { - variables = { ...variables, categoryIds: ['cat9', 'cat27', 'cat15', 'cat4'] } + describe('pin posts', () => { + const pinPostMutation = gql` + mutation($id: ID!) { + pinPost(id: $id) { + id + title + content + author { + name + slug + } + pinnedBy { + id + name + role + } + createdAt + updatedAt + pinnedAt + pinned + } + } + ` + beforeEach(async () => { + variables = { ...variables } + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { pinPost: null }, + }) + }) + }) + + describe('ordinary users', () => { + it('throws authorization error', async () => { + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { pinPost: null }, + }) + }) + }) + + describe('moderators', () => { + let moderator + beforeEach(async () => { + moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) + authenticatedUser = await moderator.toJson() + }) + + it('throws authorization error', async () => { + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { pinPost: null }, + }) + }) + }) + + describe('admins', () => { + let admin + beforeEach(async () => { + admin = await user.update({ + role: 'admin', + name: 'Admin', + updatedAt: new Date().toISOString(), + }) + authenticatedUser = await admin.toJson() + }) + + describe('are allowed to pin posts', () => { + beforeEach(async () => { + await factory.create('Post', { + id: 'created-and-pinned-by-same-admin', + author: admin, + }) + variables = { ...variables, id: 'created-and-pinned-by-same-admin' } }) - it('allows a maximum of three category for a successful update', async () => { - const { - errors: [error], - } = await mutate({ mutation: updatePostMutation, variables }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', + it('responds with the updated Post', async () => { + const expected = { + data: { + pinPost: { + id: 'created-and-pinned-by-same-admin', + author: { + name: 'Admin', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, + }, + }, + errors: undefined, + } + + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('sets createdAt date for PINNED', async () => { + const expected = { + data: { + pinPost: { + id: 'created-and-pinned-by-same-admin', + pinnedAt: expect.any(String), + }, + }, + errors: undefined, + } + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('sets redundant `pinned` property for performant ordering', async () => { + variables = { ...variables, id: 'created-and-pinned-by-same-admin' } + const expected = { + data: { pinPost: { pinned: true } }, + errors: undefined, + } + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject( + expected, ) }) }) - describe('post created without categories somehow', () => { - let owner - + describe('post created by another admin', () => { + let otherAdmin beforeEach(async () => { - const postSomehowCreated = await neode.create('Post', { - id: 'how-was-this-created', + otherAdmin = await factory.create('User', { + role: 'admin', + name: 'otherAdmin', }) - owner = await neode.create('User', { - id: 'author-of-post-without-category', - name: 'Hacker', - slug: 'hacker', - email: 'hacker@example.org', - password: '1234', + authenticatedUser = await otherAdmin.toJson() + await factory.create('Post', { + id: 'created-by-one-admin-pinned-by-different-one', + author: otherAdmin, }) - await postSomehowCreated.relateTo(owner, 'author') - authenticatedUser = await owner.toJson() - variables = { ...variables, id: 'how-was-this-created' } }) - it('throws an error if categoryIds is not an array', async () => { - const { - errors: [error], - } = await mutate({ - mutation: updatePostMutation, - variables: { - ...variables, - categoryIds: null, + it('responds with the updated Post', async () => { + authenticatedUser = await admin.toJson() + variables = { ...variables, id: 'created-by-one-admin-pinned-by-different-one' } + const expected = { + data: { + pinPost: { + id: 'created-by-one-admin-pinned-by-different-one', + author: { + name: 'otherAdmin', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, + }, }, - }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', + errors: undefined, + } + + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject( + expected, ) }) + }) + + describe('post created by another user', () => { + it('responds with the updated Post', async () => { + const expected = { + data: { + pinPost: { + id: 'p9876', + author: { + slug: 'the-author', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, + }, + }, + errors: undefined, + } + + await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + }) + + describe('pinned post already exists', () => { + let pinnedPost + beforeEach(async () => { + await factory.create('Post', { + id: 'only-pinned-post', + author: admin, + }) + await mutate({ mutation: pinPostMutation, variables }) + }) - it('requires at least one category for successful update', async () => { - const { - errors: [error], - } = await mutate({ - mutation: updatePostMutation, - variables: { - ...variables, - categoryIds: [], - }, - }) - expect(error).toHaveProperty( - 'message', - 'You cannot save a post without at least one category or more than three', - ) + it('removes previous `pinned` attribute', async () => { + const cypher = 'MATCH (post:Post) WHERE post.pinned IS NOT NULL RETURN post' + pinnedPost = await neode.cypher(cypher) + expect(pinnedPost.records).toHaveLength(1) + variables = { ...variables, id: 'only-pinned-post' } + await mutate({ mutation: pinPostMutation, variables }) + pinnedPost = await neode.cypher(cypher) + expect(pinnedPost.records).toHaveLength(1) }) + + it('removes previous PINNED relationship', async () => { + variables = { ...variables, id: 'only-pinned-post' } + await mutate({ mutation: pinPostMutation, variables }) + pinnedPost = await neode.cypher( + `MATCH (:User)-[pinned:PINNED]->(post:Post) RETURN post, pinned`, + ) + expect(pinnedPost.records).toHaveLength(1) + }) + }) + + describe('PostOrdering', () => { + beforeEach(async () => { + await factory.create('Post', { + id: 'im-a-pinned-post', + createdAt: '2019-11-22T17:26:29.070Z', + pinned: true, + }) + await factory.create('Post', { + id: 'i-was-created-before-pinned-post', + // fairly old, so this should be 3rd + createdAt: '2019-10-22T17:26:29.070Z', + }) + }) + + describe('order by `pinned_asc` and `createdAt_desc`', () => { + beforeEach(() => { + // this is the ordering in the frontend + variables = { orderBy: ['pinned_asc', 'createdAt_desc'] } + }) + + it('pinned post appear first even when created before other posts', async () => { + const postOrderingQuery = gql` + query($orderBy: [_PostOrdering]) { + Post(orderBy: $orderBy) { + id + pinned + createdAt + pinnedAt + } + } + ` + await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject({ + data: { + Post: [ + { + id: 'im-a-pinned-post', + pinned: true, + createdAt: '2019-11-22T17:26:29.070Z', + pinnedAt: expect.any(String), + }, + { + id: 'p9876', + pinned: null, + createdAt: expect.any(String), + pinnedAt: null, + }, + { + id: 'i-was-created-before-pinned-post', + pinned: null, + createdAt: '2019-10-22T17:26:29.070Z', + pinnedAt: null, + }, + ], + }, + errors: undefined, + }) + }) + }) + }) + }) + }) + + describe('unpin posts', () => { + const unpinPostMutation = gql` + mutation($id: ID!) { + unpinPost(id: $id) { + id + title + content + author { + name + slug + } + pinnedBy { + id + name + role + } + createdAt + updatedAt + pinned + pinnedAt + } + } + ` + beforeEach(async () => { + variables = { ...variables } + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { unpinPost: null }, + }) + }) + }) + + describe('users cannot unpin posts', () => { + it('throws authorization error', async () => { + await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { unpinPost: null }, + }) + }) + }) + + describe('moderators cannot unpin posts', () => { + let moderator + beforeEach(async () => { + moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) + authenticatedUser = await moderator.toJson() + }) + + it('throws authorization error', async () => { + await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { unpinPost: null }, + }) + }) + }) + + describe('admin can unpin posts', () => { + let admin, pinnedPost + beforeEach(async () => { + pinnedPost = await factory.create('Post', { id: 'post-to-be-unpinned' }) + admin = await user.update({ + role: 'admin', + name: 'Admin', + updatedAt: new Date().toISOString(), + }) + authenticatedUser = await admin.toJson() + await admin.relateTo(pinnedPost, 'pinned', { createdAt: new Date().toISOString() }) + variables = { ...variables, id: 'post-to-be-unpinned' } + }) + + it('responds with the unpinned Post', async () => { + authenticatedUser = await admin.toJson() + const expected = { + data: { + unpinPost: { + id: 'post-to-be-unpinned', + pinnedBy: null, + pinnedAt: null, + }, + }, + errors: undefined, + } + + await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('unsets `pinned` property', async () => { + const expected = { + data: { + unpinPost: { + id: 'post-to-be-unpinned', + pinned: null, + }, + }, + errors: undefined, + } + await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject( + expected, + ) }) }) }) diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index fa7262f43..1e7708395 100644 --- a/backend/src/schema/resolvers/registration.js +++ b/backend/src/schema/resolvers/registration.js @@ -1,82 +1,31 @@ import { UserInputError } from 'apollo-server' -import uuid from 'uuid/v4' -import { neode } from '../../bootstrap/neo4j' +import { getNeode } from '../../db/neo4j' import fileUpload from './fileUpload' import encryptPassword from '../../helpers/encryptPassword' +import generateNonce from './helpers/generateNonce' +import existingEmailAddress from './helpers/existingEmailAddress' +import normalizeEmail from './helpers/normalizeEmail' +import createOrUpdateLocations from './users/location' -const instance = neode() - -/* - * TODO: remove this function as soon type `User` has no `email` property - * anymore - */ -const checkEmailDoesNotExist = async ({ email }) => { - email = email.toLowerCase() - const emails = await instance.all('EmailAddress', { email }) - if (emails.length > 0) throw new UserInputError('User account with this email already exists.') -} +const neode = getNeode() export default { Mutation: { - CreateInvitationCode: async (_parent, args, context, _resolveInfo) => { - args.token = uuid().substring(0, 6) - const { - user: { id: userId }, - } = context - let response + Signup: async (_parent, args, context) => { + args.nonce = generateNonce() + args.email = normalizeEmail(args.email) + let emailAddress = await existingEmailAddress({ args, context }) + if (emailAddress) return emailAddress try { - const [user, invitationCode] = await Promise.all([ - instance.find('User', userId), - instance.create('InvitationCode', args), - ]) - await invitationCode.relateTo(user, 'generatedBy') - response = invitationCode.toJson() - response.generatedBy = user.toJson() - } catch (e) { - throw new UserInputError(e) - } - return response - }, - Signup: async (_parent, args, _context, _resolveInfo) => { - const nonce = uuid().substring(0, 6) - args.nonce = nonce - await checkEmailDoesNotExist({ email: args.email }) - try { - const emailAddress = await instance.create('EmailAddress', args) + emailAddress = await neode.create('EmailAddress', args) return emailAddress.toJson() } catch (e) { throw new UserInputError(e.message) } }, - SignupByInvitation: async (_parent, args, _context, _resolveInfo) => { - const { token } = args - const nonce = uuid().substring(0, 6) - args.nonce = nonce - await checkEmailDoesNotExist({ email: args.email }) - try { - const result = await instance.cypher( - ` - MATCH (invitationCode:InvitationCode {token:{token}}) - WHERE NOT (invitationCode)-[:ACTIVATED]->() - RETURN invitationCode - `, - { token }, - ) - const validInvitationCode = instance.hydrateFirst( - result, - 'invitationCode', - instance.model('InvitationCode'), - ) - if (!validInvitationCode) - throw new UserInputError('Invitation code already used or does not exist.') - const emailAddress = await instance.create('EmailAddress', args) - await validInvitationCode.relateTo(emailAddress, 'activated') - return emailAddress.toJson() - } catch (e) { - throw new UserInputError(e) - } - }, - SignupVerification: async (_parent, args, _context, _resolveInfo) => { + SignupVerification: async (_parent, args, context) => { + const { driver } = context + const session = driver.session() const { termsAndConditionsAgreedVersion } = args const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) if (!regEx.test(termsAndConditionsAgreedVersion)) { @@ -85,8 +34,8 @@ export default { args.termsAndConditionsAgreedAt = new Date().toISOString() let { nonce, email } = args - email = email.toLowerCase() - const result = await instance.cypher( + email = normalizeEmail(email) + const result = await neode.cypher( ` MATCH(email:EmailAddress {nonce: {nonce}, email: {email}}) WHERE NOT (email)-[:BELONGS_TO]->() @@ -94,22 +43,25 @@ export default { `, { nonce, email }, ) - const emailAddress = await instance.hydrateFirst(result, 'email', instance.model('Email')) + const emailAddress = await neode.hydrateFirst(result, 'email', neode.model('EmailAddress')) if (!emailAddress) throw new UserInputError('Invalid email or nonce') args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' }) args = await encryptPassword(args) try { - const user = await instance.create('User', args) + const user = await neode.create('User', args) await Promise.all([ user.relateTo(emailAddress, 'primaryEmail'), emailAddress.relateTo(user, 'belongsTo'), emailAddress.update({ verifiedAt: new Date().toISOString() }), ]) + await createOrUpdateLocations(args.id, args.locationName, session) return user.toJson() } catch (e) { if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') throw new UserInputError('User with this slug already exists!') throw new UserInputError(e.message) + } finally { + session.close() } }, }, diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js index ca19f03c4..23b1f9d2a 100644 --- a/backend/src/schema/resolvers/registration.spec.js +++ b/backend/src/schema/resolvers/registration.spec.js @@ -1,6 +1,6 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { getDriver, neode as getNeode } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getDriver, getNeode } from '../../db/neo4j' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -9,7 +9,6 @@ const neode = getNeode() let mutate let authenticatedUser -let user let variables const driver = getDriver() @@ -34,243 +33,6 @@ afterEach(async () => { await factory.cleanDatabase() }) -describe('CreateInvitationCode', () => { - const mutation = gql` - mutation { - CreateInvitationCode { - token - } - } - ` - - describe('unauthenticated', () => { - beforeEach(() => { - authenticatedUser = null - }) - - it('throws Authorization error', async () => { - await expect(mutate({ mutation })).resolves.toMatchObject({ - errors: [{ message: 'Not Authorised!' }], - }) - }) - }) - - describe('authenticated', () => { - beforeEach(async () => { - user = await factory.create('User', { - id: 'i123', - name: 'Inviter', - email: 'inviter@example.org', - password: '1234', - termsAndConditionsAgreedVersion: null, - }) - authenticatedUser = await user.toJson() - }) - - it('resolves', async () => { - await expect(mutate({ mutation })).resolves.toMatchObject({ - data: { CreateInvitationCode: { token: expect.any(String) } }, - }) - }) - - it('creates an InvitationCode with a `createdAt` attribute', async () => { - await mutate({ mutation }) - const codes = await neode.all('InvitationCode') - const invitation = await codes.first().toJson() - expect(invitation.createdAt).toBeTruthy() - expect(Date.parse(invitation.createdAt)).toEqual(expect.any(Number)) - }) - - it('relates inviting User to InvitationCode', async () => { - await mutate({ mutation }) - const result = await neode.cypher( - 'MATCH(code:InvitationCode)<-[:GENERATED]-(user:User) RETURN user', - ) - const inviter = neode.hydrateFirst(result, 'user', neode.model('User')) - await expect(inviter.toJson()).resolves.toEqual(expect.objectContaining({ name: 'Inviter' })) - }) - - describe('who has invited a lot of users already', () => { - beforeEach(async () => { - await Promise.all([mutate({ mutation }), mutate({ mutation }), mutate({ mutation })]) - }) - - describe('as ordinary `user`', () => { - it('throws `Not Authorised` because of maximum number of invitations', async () => { - await expect(mutate({ mutation })).resolves.toMatchObject({ - errors: [{ message: 'Not Authorised!' }], - }) - }) - - it('creates no additional invitation codes', async () => { - await mutate({ mutation }) - const invitationCodes = await neode.all('InvitationCode') - await expect(invitationCodes.toJson()).resolves.toHaveLength(3) - }) - }) - - describe('as a strong donator', () => { - beforeEach(() => { - // What is the setup? - }) - - it.todo('can invite more people') - // it('can invite more people', async () => { - // await action() - // const invitationQuery = `{ User { createdAt } }` - // const { User: users } = await client.request(invitationQuery ) - // expect(users).toHaveLength(3 + 1 + 1) - // }) - }) - }) - }) -}) - -describe('SignupByInvitation', () => { - const mutation = gql` - mutation($email: String!, $token: String!) { - SignupByInvitation(email: $email, token: $token) { - email - } - } - ` - - describe('with valid email but invalid InvitationCode', () => { - beforeEach(() => { - variables = { - ...variables, - email: 'any-email@example.org', - token: 'wut?', - } - }) - - it('throws UserInputError', async () => { - await expect(mutate({ mutation, variables })).resolves.toMatchObject({ - errors: [{ message: 'UserInputError: Invitation code already used or does not exist.' }], - }) - }) - - describe('with valid InvitationCode', () => { - beforeEach(async () => { - const inviter = await factory.create('User', { - name: 'Inviter', - email: 'inviter@example.org', - password: '1234', - }) - authenticatedUser = await inviter.toJson() - const invitationMutation = gql` - mutation { - CreateInvitationCode { - token - } - } - ` - const { - data: { - CreateInvitationCode: { token }, - }, - } = await mutate({ mutation: invitationMutation }) - authenticatedUser = null - variables = { - ...variables, - token, - } - }) - - describe('given an invalid email', () => { - beforeEach(() => { - variables = { ...variables, email: 'someuser' } - }) - - it('throws `email is not a valid email`', async () => { - await expect(mutate({ mutation, variables })).resolves.toMatchObject({ - errors: [{ message: expect.stringContaining('"email" must be a valid email') }], - }) - }) - - it('creates no additional EmailAddress node', async () => { - let emailAddresses = await neode.all('EmailAddress') - emailAddresses = await emailAddresses.toJson() - expect(emailAddresses).toHaveLength(1) - await mutate({ mutation, variables }) - emailAddresses = await neode.all('EmailAddress') - emailAddresses = await emailAddresses.toJson() - expect(emailAddresses).toHaveLength(1) - }) - }) - - describe('given a valid email', () => { - beforeEach(() => { - variables = { ...variables, email: 'someUser@example.org' } - }) - - it('resolves', async () => { - await expect(mutate({ mutation, variables })).resolves.toMatchObject({ - data: { SignupByInvitation: { email: 'someuser@example.org' } }, - }) - }) - - describe('creates a EmailAddress node', () => { - it('with a `createdAt` attribute', async () => { - await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.createdAt).toBeTruthy() - expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number)) - }) - - it('with a cryptographic `nonce`', async () => { - await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.nonce).toEqual(expect.any(String)) - }) - - it('connects inviter through invitation code', async () => { - await mutate({ mutation, variables }) - const result = await neode.cypher( - 'MATCH(inviter:User)-[:GENERATED]->(:InvitationCode)-[:ACTIVATED]->(email:EmailAddress {email: {email}}) RETURN inviter', - { email: 'someuser@example.org' }, - ) - const inviter = neode.hydrateFirst(result, 'inviter', neode.model('User')) - await expect(inviter.toJson()).resolves.toEqual( - expect.objectContaining({ name: 'Inviter' }), - ) - }) - - describe('using the same InvitationCode twice', () => { - it('rejects because codes can be used only once', async () => { - await mutate({ mutation, variables }) - variables = { ...variables, email: 'yetanotheremail@example.org' } - await expect(mutate({ mutation, variables })).resolves.toMatchObject({ - errors: [ - { message: 'UserInputError: Invitation code already used or does not exist.' }, - ], - }) - }) - }) - - describe('if a user account with the given email already exists', () => { - beforeEach(async () => { - await factory.create('User', { email: 'someuser@example.org' }) - }) - - it('throws unique violation error', async () => { - await expect(mutate({ mutation, variables })).resolves.toMatchObject({ - errors: [{ message: 'User account with this email already exists.' }], - }) - }) - }) - - describe('if the EmailAddress already exists but without user account', () => { - it.todo('shall we re-send the registration email?') - }) - }) - }) - }) - }) -}) - describe('Signup', () => { const mutation = gql` mutation($email: String!) { @@ -307,14 +69,66 @@ describe('Signup', () => { it('is allowed to signup users by email', async () => { await expect(mutate({ mutation, variables })).resolves.toMatchObject({ data: { Signup: { email: 'someuser@example.org' } }, + errors: undefined, }) }) - it('creates a Signup with a cryptographic `nonce`', async () => { - await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.nonce).toEqual(expect.any(String)) + describe('creates a EmailAddress node', () => { + it('with `createdAt` attribute', async () => { + await mutate({ mutation, variables }) + let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) + emailAddress = await emailAddress.toJson() + expect(emailAddress.createdAt).toBeTruthy() + expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number)) + }) + + it('with a cryptographic `nonce`', async () => { + await mutate({ mutation, variables }) + let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) + emailAddress = await emailAddress.toJson() + expect(emailAddress.nonce).toEqual(expect.any(String)) + }) + + describe('if the email already exists', () => { + let email + beforeEach(async () => { + email = await factory.create('EmailAddress', { + email: 'someuser@example.org', + verifiedAt: null, + }) + }) + + describe('and the user has registered already', () => { + beforeEach(async () => { + await factory.create('User', { email }) + }) + + it('throws UserInputError error because of unique constraint violation', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'A user account with this email already exists.' }], + }) + }) + }) + + describe('but the user has not yet registered', () => { + it('resolves with the already existing email', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { Signup: { email: 'someuser@example.org' } }, + errors: undefined, + }) + }) + + it('creates no additional `EmailAddress` node', async () => { + // admin account and the already existing user + await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { Signup: { email: 'someuser@example.org' } }, + errors: undefined, + }) + await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) + }) + }) + }) }) }) }) @@ -329,6 +143,7 @@ describe('SignupVerification', () => { $nonce: String! $about: String $termsAndConditionsAgreedVersion: String! + $locale: String ) { SignupVerification( name: $name @@ -337,6 +152,7 @@ describe('SignupVerification', () => { nonce: $nonce about: $about termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion + locale: $locale ) { id termsAndConditionsAgreedVersion @@ -353,6 +169,7 @@ describe('SignupVerification', () => { password: '123', email: 'john@example.org', termsAndConditionsAgreedVersion: '0.1.0', + locale: 'en', } }) @@ -435,6 +252,17 @@ describe('SignupVerification', () => { }) }) + it('allowing the about field to be an empty string', async () => { + variables = { ...variables, about: '' } + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { + SignupVerification: expect.objectContaining({ + id: expect.any(String), + }), + }, + }) + }) + it('marks the EmailAddress as primary', async () => { const cypher = ` MATCH(email:EmailAddress)<-[:PRIMARY_EMAIL]-(u:User {name: {name}}) diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index 79cae032b..0565c4d8a 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -1,86 +1,186 @@ -import uuid from 'uuid/v4' +import log from './helpers/databaseLogger' + +const transformReturnType = record => { + return { + ...record.get('report').properties, + resource: { + __typename: record.get('type'), + ...record.get('resource').properties, + }, + } +} export default { Mutation: { - report: async (parent, { id, description }, { driver, req, user }, resolveInfo) => { - const reportId = uuid() + fileReport: async (_parent, params, context, _resolveInfo) => { + const { resourceId, reasonCategory, reasonDescription } = params + const { driver, user } = context const session = driver.session() - const reportData = { - id: reportId, - createdAt: new Date().toISOString(), - description: description, - } + const reportWriteTxResultPromise = session.writeTransaction(async transaction => { + const reportTransactionResponse = await transaction.run( + ` + MATCH (submitter:User {id: $submitterId}) + MATCH (resource {id: $resourceId}) + WHERE resource:User OR resource:Post OR resource:Comment + MERGE (resource)<-[:BELONGS_TO]-(report:Report {closed: false}) + ON CREATE SET report.id = randomUUID(), report.createdAt = $createdAt, report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.disable = resource.disabled, report.closed = false + WITH submitter, resource, report + CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter) - const reportQueryRes = await session.run( - ` - match (u:User {id:$submitterId}) -[:REPORTED]->(report)-[:REPORTED]-> (resource {id: $resourceId}) - return labels(resource)[0] as label - `, - { - resourceId: id, - submitterId: user.id, - }, - ) - const [rep] = reportQueryRes.records.map(record => { - return { - label: record.get('label'), - } + RETURN report, resource, labels(resource)[0] AS type + `, + { + resourceId, + submitterId: user.id, + createdAt: new Date().toISOString(), + reasonCategory, + reasonDescription, + }, + ) + log(reportTransactionResponse) + return reportTransactionResponse.records.map(transformReturnType) }) - - if (rep) { - throw new Error(rep.label) + try { + const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise + if (!createdRelationshipWithNestedAttributes) return null + return createdRelationshipWithNestedAttributes + } finally { + session.close() + } + }, + }, + Query: { + reports: async (_parent, params, context, _resolveInfo) => { + const { driver } = context + const session = driver.session() + let orderByClause, filterClause + switch (params.orderBy) { + case 'createdAt_asc': + orderByClause = 'ORDER BY report.createdAt ASC' + break + case 'createdAt_desc': + orderByClause = 'ORDER BY report.createdAt DESC' + break + default: + orderByClause = '' } - const res = await session.run( - ` - MATCH (submitter:User {id: $userId}) - MATCH (resource {id: $resourceId}) - WHERE resource:User OR resource:Comment OR resource:Post - MERGE (report:Report {id: {reportData}.id }) - MERGE (resource)<-[:REPORTED]-(report) - MERGE (report)<-[:REPORTED]-(submitter) - RETURN report, submitter, resource, labels(resource)[0] as type - `, - { - resourceId: id, - userId: user.id, - reportData, - }, - ) - session.close() + switch (params.reviewed) { + case true: + filterClause = 'AND ((report)<-[:REVIEWED]-(:User))' + break + case false: + filterClause = 'AND NOT ((report)<-[:REVIEWED]-(:User))' + break + default: + filterClause = '' + } - const [dbResponse] = res.records.map(r => { - return { - report: r.get('report'), - submitter: r.get('submitter'), - resource: r.get('resource'), - type: r.get('type'), - } + if (params.closed) filterClause = 'AND report.closed = true' + + const offset = + params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : '' + const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : '' + + const reportReadTxPromise = session.readTransaction(async transaction => { + const allReportsTransactionResponse = await transaction.run( + ` + MATCH (report:Report)-[:BELONGS_TO]->(resource) + WHERE (resource:User OR resource:Post OR resource:Comment) + ${filterClause} + WITH report, resource, + [(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed, + [(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed, + [(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors, + [(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts, + resource {.*, __typename: labels(resource)[0] } as resourceWithType + WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed, + resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource + RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed } + ${orderByClause} + ${offset} ${limit} + `, + ) + log(allReportsTransactionResponse) + return allReportsTransactionResponse.records.map(record => record.get('report')) }) - if (!dbResponse) return null - const { report, submitter, resource, type } = dbResponse - - const response = { - ...report.properties, - post: null, - comment: null, - user: null, - submitter: submitter.properties, - type, + try { + const reports = await reportReadTxPromise + return reports + } finally { + session.close() } - switch (type) { - case 'Post': - response.post = resource.properties - break - case 'Comment': - response.comment = resource.properties - break - case 'User': - response.user = resource.properties - break + }, + }, + Report: { + filed: async (parent, _params, context, _resolveInfo) => { + if (typeof parent.filed !== 'undefined') return parent.filed + const session = context.driver.session() + const { id } = parent + let filed + const readTxPromise = session.readTransaction(async transaction => { + const filedReportsTransactionResponse = await transaction.run( + ` + MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id}) + RETURN filed, submitter + `, + { id }, + ) + log(filedReportsTransactionResponse) + return filedReportsTransactionResponse.records.map(record => ({ + submitter: record.get('submitter').properties, + filed: record.get('filed').properties, + })) + }) + try { + const filedReports = await readTxPromise + filed = filedReports.map(reportedRecord => { + const { submitter, filed } = reportedRecord + const relationshipWithNestedAttributes = { + ...filed, + submitter, + } + return relationshipWithNestedAttributes + }) + } finally { + session.close() } - - return response + return filed + }, + reviewed: async (parent, _params, context, _resolveInfo) => { + if (typeof parent.reviewed !== 'undefined') return parent.reviewed + const session = context.driver.session() + const { id } = parent + let reviewed + const readTxPromise = session.readTransaction(async transaction => { + const reviewedReportsTransactionResponse = await transaction.run( + ` + MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User) + RETURN moderator, review + ORDER BY report.updatedAt DESC, review.updatedAt DESC + `, + { id }, + ) + log(reviewedReportsTransactionResponse) + return reviewedReportsTransactionResponse.records.map(record => ({ + review: record.get('review').properties, + moderator: record.get('moderator').properties, + })) + }) + try { + const reviewedReports = await readTxPromise + reviewed = reviewedReports.map(reportedRecord => { + const { review, moderator } = reportedRecord + const relationshipWithNestedAttributes = { + ...review, + moderator, + } + return relationshipWithNestedAttributes + }) + } finally { + session.close() + } + return reviewed }, }, } diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js index 512d8d956..7f827b111 100644 --- a/backend/src/schema/resolvers/reports.spec.js +++ b/backend/src/schema/resolvers/reports.spec.js @@ -1,127 +1,340 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../../seed/factories' -import { host, login } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../.././server' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getDriver, getNeode } from '../../db/neo4j' const factory = Factory() -const instance = neode() +const instance = getNeode() +const driver = getDriver() -describe('report', () => { - let mutation - let headers - let returnedObject - let variables - let createPostVariables - let user +describe('file a report on a resource', () => { + let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser, otherReportingUser const categoryIds = ['cat9'] - - beforeEach(async () => { - returnedObject = '{ description }' - variables = { - id: 'whatever', + const reportMutation = gql` + mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { + fileReport( + resourceId: $resourceId + reasonCategory: $reasonCategory + reasonDescription: $reasonDescription + ) { + id + createdAt + updatedAt + closed + rule + resource { + __typename + ... on User { + name + } + ... on Post { + title + } + ... on Comment { + content + } + } + filed { + submitter { + id + } + createdAt + reasonCategory + reasonDescription + } + } } - headers = {} - user = await factory.create('User', { - email: 'test@example.org', - password: '1234', - id: 'u1', - }) - await factory.create('User', { - id: 'u2', - name: 'abusive-user', - role: 'user', - email: 'abusive-user@example.org', - }) - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', + ` + const variables = { + resourceId: 'whatever', + reasonCategory: 'other', + reasonDescription: 'Violates code of conduct !!!', + } + + beforeAll(async () => { + await factory.cleanDatabase() + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, }) + mutate = createTestClient(server).mutate + query = createTestClient(server).query }) afterEach(async () => { await factory.cleanDatabase() }) - let client - const action = () => { - mutation = ` - mutation($id: ID!) { - report( - id: $id, - description: "Violates code of conduct" - ) ${returnedObject} - } - ` - client = new GraphQLClient(host, { - headers, - }) - return client.request(mutation, variables) - } - - describe('unauthenticated', () => { - it('throws authorization error', async () => { - await expect(action()).rejects.toThrow('Not Authorised') + describe('report a resource', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({ + data: { fileReport: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) }) describe('authenticated', () => { beforeEach(async () => { - headers = await login({ + currentUser = await factory.create('User', { + id: 'current-user-id', + role: 'user', email: 'test@example.org', password: '1234', }) + otherReportingUser = await factory.create('User', { + id: 'other-reporting-user-id', + role: 'user', + email: 'reporting@example.org', + password: '1234', + }) + await factory.create('User', { + id: 'abusive-user-id', + role: 'user', + name: 'abusive-user', + email: 'abusive-user@example.org', + }) + await instance.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }) + + authenticatedUser = await currentUser.toJson() }) describe('invalid resource id', () => { it('returns null', async () => { - await expect(action()).resolves.toEqual({ - report: null, + await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({ + data: { fileReport: null }, + errors: undefined, }) }) }) - describe('valid resource id', () => { - beforeEach(async () => { - variables = { - id: 'u2', - } - }) - /* - it('creates a report', async () => { - await expect(action()).resolves.toEqual({ - type: null, - }) - }) - */ - it('returns the submitter', async () => { - returnedObject = '{ submitter { email } }' - await expect(action()).resolves.toEqual({ - report: { - submitter: { - email: 'test@example.org', - }, - }, - }) - }) - - describe('reported resource is a user', () => { - it('returns type "User"', async () => { - returnedObject = '{ type }' - await expect(action()).resolves.toEqual({ - report: { - type: 'User', + describe('valid resource', () => { + describe('creates report', () => { + it('which belongs to resource', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + id: expect.any(String), + }, }, + errors: undefined, }) }) - it('returns resource in user attribute', async () => { - returnedObject = '{ user { name } }' - await expect(action()).resolves.toEqual({ - report: { - user: { - name: 'abusive-user', + it('creates only one report for multiple reports on the same resource', async () => { + const firstReport = await mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }) + authenticatedUser = await otherReportingUser.toJson() + const secondReport = await mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }) + expect(firstReport.data.fileReport.id).toEqual(secondReport.data.fileReport.id) + }) + + it('returns the rule for how the report was decided', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + rule: 'latestReviewUpdatedAtRules', }, }, + errors: undefined, + }) + }) + it.todo('creates multiple filed reports') + }) + + describe('reported resource is a user', () => { + it('returns __typename "User"', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'User', + }, + }, + }, + errors: undefined, + }) + }) + + it('returns user attribute info', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'User', + name: 'abusive-user', + }, + }, + }, + errors: undefined, + }) + }) + + it('returns the submitter', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + filed: [ + { + submitter: { + id: 'current-user-id', + }, + }, + ], + }, + }, + errors: undefined, + }) + }) + + it('returns a date', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { ...variables, resourceId: 'abusive-user-id' }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + createdAt: expect.any(String), + }, + }, + errors: undefined, + }) + }) + + it('returns the reason category', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'abusive-user-id', + reasonCategory: 'criminal_behavior_violation_german_law', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + filed: [ + { + reasonCategory: 'criminal_behavior_violation_german_law', + }, + ], + }, + }, + errors: undefined, + }) + }) + + it('gives an error if the reason category is not in enum "ReasonCategory"', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'abusive-user-id', + reasonCategory: 'category_missing_from_enum_reason_category', + }, + }), + ).resolves.toMatchObject({ + data: undefined, + errors: [ + { + message: + 'Variable "$reasonCategory" got invalid value "category_missing_from_enum_reason_category"; Expected type ReasonCategory.', + }, + ], + }) + }) + + it('returns the reason description', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'abusive-user-id', + reasonDescription: 'My reason!', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + filed: [ + { + reasonDescription: 'My reason!', + }, + ], + }, + }, + errors: undefined, + }) + }) + + it('sanitizes the reason description', async () => { + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'abusive-user-id', + reasonDescription: 'My reason !', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + filed: [ + { + reasonDescription: 'My reason !', + }, + ], + }, + }, + errors: undefined, }) }) }) @@ -129,50 +342,59 @@ describe('report', () => { describe('reported resource is a post', () => { beforeEach(async () => { await factory.create('Post', { - author: user, - id: 'p23', - title: 'Matt and Robert having a pair-programming', + author: currentUser, + id: 'post-to-report-id', + title: 'This is a post that is going to be reported', categoryIds, }) - variables = { - id: 'p23', - } }) it('returns type "Post"', async () => { - returnedObject = '{ type }' - await expect(action()).resolves.toEqual({ - report: { - type: 'Post', + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'post-to-report-id', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'Post', + }, + }, }, + errors: undefined, }) }) it('returns resource in post attribute', async () => { - returnedObject = '{ post { title } }' - await expect(action()).resolves.toEqual({ - report: { - post: { - title: 'Matt and Robert having a pair-programming', + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'post-to-report-id', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'Post', + title: 'This is a post that is going to be reported', + }, }, }, - }) - }) - - it('returns null in user attribute', async () => { - returnedObject = '{ user { name } }' - await expect(action()).resolves.toEqual({ - report: { - user: null, - }, + errors: undefined, }) }) }) - /* An der Stelle würde ich den p23 noch mal prüfen, diesmal muss aber eine error meldung kommen. - At this point I would check the p23 again, but this time there must be an error message. */ - describe('reported resource is a comment', () => { + let createPostVariables beforeEach(async () => { createPostVariables = { id: 'p1', @@ -180,61 +402,287 @@ describe('report', () => { content: 'please comment on me', categoryIds, } - await factory.create('Post', { ...createPostVariables, author: user }) + await factory.create('Post', { ...createPostVariables, author: currentUser }) await factory.create('Comment', { - author: user, + author: currentUser, postId: 'p1', - id: 'c34', - content: 'Robert getting tired.', + id: 'comment-to-report-id', + content: 'Post comment to be reported.', }) - variables = { - id: 'c34', - } }) it('returns type "Comment"', async () => { - returnedObject = '{ type }' - await expect(action()).resolves.toEqual({ - report: { - type: 'Comment', + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'comment-to-report-id', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'Comment', + }, + }, }, + errors: undefined, }) }) it('returns resource in comment attribute', async () => { - returnedObject = '{ comment { content } }' - await expect(action()).resolves.toEqual({ - report: { - comment: { - content: 'Robert getting tired.', + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'comment-to-report-id', + }, + }), + ).resolves.toMatchObject({ + data: { + fileReport: { + resource: { + __typename: 'Comment', + content: 'Post comment to be reported.', + }, }, }, + errors: undefined, }) }) }) - /* An der Stelle würde ich den c34 noch mal prüfen, diesmal muss aber eine error meldung kommen. - At this point I would check the c34 again, but this time there must be an error message. */ - describe('reported resource is a tag', () => { beforeEach(async () => { await factory.create('Tag', { - id: 't23', + id: 'tag-to-report-id', }) - variables = { - id: 't23', - } }) it('returns null', async () => { - await expect(action()).resolves.toEqual({ - report: null, + await expect( + mutate({ + mutation: reportMutation, + variables: { + ...variables, + resourceId: 'tag-to-report-id', + }, + }), + ).resolves.toMatchObject({ + data: { fileReport: null }, + errors: undefined, }) }) }) + }) + }) + }) - /* An der Stelle würde ich den t23 noch mal prüfen, diesmal muss aber eine error meldung kommen. - At this point I would check the t23 again, but this time there must be an error message. */ + describe('query for reported resource', () => { + const reportsQuery = gql` + query { + reports(orderBy: createdAt_desc) { + id + createdAt + updatedAt + closed + resource { + __typename + ... on User { + id + } + ... on Post { + id + } + ... on Comment { + id + } + } + filed { + submitter { + id + } + createdAt + reasonCategory + reasonDescription + } + } + } + ` + + beforeEach(async () => { + authenticatedUser = null + moderator = await factory.create('User', { + id: 'moderator-1', + role: 'moderator', + email: 'moderator@example.org', + password: '1234', + }) + currentUser = await factory.create('User', { + id: 'current-user-id', + role: 'user', + email: 'current.user@example.org', + password: '1234', + }) + abusiveUser = await factory.create('User', { + id: 'abusive-user-1', + role: 'user', + name: 'abusive-user', + email: 'abusive-user@example.org', + }) + await instance.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }) + + await Promise.all([ + factory.create('Post', { + author: abusiveUser, + id: 'abusive-post-1', + categoryIds, + content: 'Interesting Knowledge', + }), + factory.create('Post', { + author: moderator, + id: 'post-2', + categoryIds, + content: 'More things to do …', + }), + factory.create('Post', { + author: currentUser, + id: 'post-3', + categoryIds, + content: 'I am at school …', + }), + ]) + await Promise.all([ + factory.create('Comment', { + author: currentUser, + id: 'abusive-comment-1', + postId: 'post-1', + }), + ]) + authenticatedUser = await currentUser.toJson() + await Promise.all([ + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'abusive-post-1', + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + }, + }), + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'abusive-comment-1', + reasonCategory: 'discrimination_etc', + reasonDescription: 'This post is bigoted', + }, + }), + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'abusive-user-1', + reasonCategory: 'doxing', + reasonDescription: 'This user is harassing me with bigoted remarks', + }, + }), + ]) + authenticatedUser = null + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + expect(query({ query: reportsQuery })).resolves.toMatchObject({ + data: { reports: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated', () => { + it('role "user" gets no reports', async () => { + authenticatedUser = await currentUser.toJson() + expect(query({ query: reportsQuery })).resolves.toMatchObject({ + data: { reports: null }, + errors: [{ message: 'Not Authorised!' }], + }) + }) + + it('role "moderator" gets reports', async () => { + const expected = { + reports: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + closed: false, + resource: { + __typename: 'User', + id: 'abusive-user-1', + }, + filed: expect.arrayContaining([ + expect.objectContaining({ + submitter: expect.objectContaining({ + id: 'current-user-id', + }), + createdAt: expect.any(String), + reasonCategory: 'doxing', + reasonDescription: 'This user is harassing me with bigoted remarks', + }), + ]), + }), + expect.objectContaining({ + id: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + closed: false, + resource: { + __typename: 'Post', + id: 'abusive-post-1', + }, + filed: expect.arrayContaining([ + expect.objectContaining({ + submitter: expect.objectContaining({ + id: 'current-user-id', + }), + createdAt: expect.any(String), + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + }), + ]), + }), + expect.objectContaining({ + id: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + closed: false, + resource: { + __typename: 'Comment', + id: 'abusive-comment-1', + }, + filed: expect.arrayContaining([ + expect.objectContaining({ + submitter: expect.objectContaining({ + id: 'current-user-id', + }), + createdAt: expect.any(String), + reasonCategory: 'discrimination_etc', + reasonDescription: 'This post is bigoted', + }), + ]), + }), + ]), + } + authenticatedUser = await moderator.toJson() + const { data } = await query({ query: reportsQuery }) + expect(data).toEqual(expected) }) }) }) diff --git a/backend/src/schema/resolvers/rewards.js b/backend/src/schema/resolvers/rewards.js index 74c7860e4..311cfd2e6 100644 --- a/backend/src/schema/resolvers/rewards.js +++ b/backend/src/schema/resolvers/rewards.js @@ -1,11 +1,11 @@ -import { neode } from '../../bootstrap/neo4j' +import { getNeode } from '../../db/neo4j' import { UserInputError } from 'apollo-server' -const instance = neode() +const neode = getNeode() const getUserAndBadge = async ({ badgeKey, userId }) => { - const user = await instance.first('User', 'id', userId) - const badge = await instance.first('Badge', 'id', badgeKey) + const user = await neode.first('User', 'id', userId) + const badge = await neode.first('Badge', 'id', badgeKey) if (!user) throw new UserInputError("Couldn't find a user with that id") if (!badge) throw new UserInputError("Couldn't find a badge with that id") return { user, badge } @@ -24,18 +24,19 @@ export default { const { user } = await getUserAndBadge(params) const session = context.driver.session() try { - // silly neode cannot remove relationships - await session.run( - ` - MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) - DELETE reward - RETURN rewardedUser - `, - { - badgeKey, - userId, - }, - ) + await session.writeTransaction(transaction => { + return transaction.run( + ` + MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) + DELETE reward + RETURN rewardedUser + `, + { + badgeKey, + userId, + }, + ) + }) } finally { session.close() } diff --git a/backend/src/schema/resolvers/rewards.spec.js b/backend/src/schema/resolvers/rewards.spec.js index 3b94e93aa..fe2807f25 100644 --- a/backend/src/schema/resolvers/rewards.spec.js +++ b/backend/src/schema/resolvers/rewards.spec.js @@ -1,31 +1,49 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../../seed/factories' -import { host, login, gql } from '../../jest/helpers' +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' const factory = Factory() -let user -let badge +const driver = getDriver() +const instance = getNeode() + +let authenticatedUser, regularUser, administrator, moderator, badge, query, mutate describe('rewards', () => { const variables = { from: 'indiegogo_en_rhino', - to: 'u1', + to: 'regular-user-id', } + beforeAll(async () => { + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate + }) + beforeEach(async () => { - user = await factory.create('User', { - id: 'u1', + regularUser = await factory.create('User', { + id: 'regular-user-id', role: 'user', email: 'user@example.org', password: '1234', }) - await factory.create('User', { - id: 'u2', + moderator = await factory.create('User', { + id: 'moderator-id', role: 'moderator', email: 'moderator@example.org', }) - await factory.create('User', { - id: 'u3', + administrator = await factory.create('User', { + id: 'admin-id', role: 'admin', email: 'admin@example.org', }) @@ -42,7 +60,7 @@ describe('rewards', () => { }) describe('reward', () => { - const mutation = gql` + const rewardMutation = gql` mutation($from: ID!, $to: ID!) { reward(badgeKey: $from, userId: $to) { id @@ -54,51 +72,61 @@ describe('rewards', () => { ` describe('unauthenticated', () => { - let client - it('throws authorization error', async () => { - client = new GraphQLClient(host) - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + authenticatedUser = null + await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ + data: { reward: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) describe('authenticated admin', () => { - let client beforeEach(async () => { - const headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + authenticatedUser = await administrator.toJson() }) describe('badge for id does not exist', () => { - it('rejects with a telling error message', async () => { + it('rejects with an informative error message', async () => { await expect( - client.request(mutation, { - ...variables, - from: 'bullshit', + mutate({ + mutation: rewardMutation, + variables: { to: 'regular-user-id', from: 'non-existent-badge-id' }, }), - ).rejects.toThrow("Couldn't find a badge with that id") + ).resolves.toMatchObject({ + data: { reward: null }, + errors: [{ message: "Couldn't find a badge with that id" }], + }) }) }) - describe('user for id does not exist', () => { + describe('non-existent user', () => { it('rejects with a telling error message', async () => { await expect( - client.request(mutation, { - ...variables, - to: 'bullshit', + mutate({ + mutation: rewardMutation, + variables: { to: 'non-existent-user-id', from: 'indiegogo_en_rhino' }, }), - ).rejects.toThrow("Couldn't find a user with that id") + ).resolves.toMatchObject({ + data: { reward: null }, + errors: [{ message: "Couldn't find a user with that id" }], + }) }) }) it('rewards a badge to user', async () => { const expected = { - reward: { - id: 'u1', - badges: [{ id: 'indiegogo_en_rhino' }], + data: { + reward: { + id: 'regular-user-id', + badges: [{ id: 'indiegogo_en_rhino' }], + }, }, + errors: undefined, } - await expect(client.request(mutation, variables)).resolves.toEqual(expected) + await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject( + expected, + ) }) it('rewards a second different badge to same user', async () => { @@ -108,42 +136,74 @@ describe('rewards', () => { }) const badges = [{ id: 'indiegogo_en_racoon' }, { id: 'indiegogo_en_rhino' }] const expected = { - reward: { - id: 'u1', - badges: expect.arrayContaining(badges), + data: { + reward: { + id: 'regular-user-id', + badges: expect.arrayContaining(badges), + }, }, + errors: undefined, } - await client.request(mutation, variables) + await mutate({ + mutation: rewardMutation, + variables: { + to: 'regular-user-id', + from: 'indiegogo_en_rhino', + }, + }) await expect( - client.request(mutation, { - ...variables, - from: 'indiegogo_en_racoon', + mutate({ + mutation: rewardMutation, + variables: { + to: 'regular-user-id', + from: 'indiegogo_en_racoon', + }, }), - ).resolves.toEqual(expected) + ).resolves.toMatchObject(expected) }) it('rewards the same badge as well to another user', async () => { const expected = { - reward: { - id: 'u2', - badges: [{ id: 'indiegogo_en_rhino' }], + data: { + reward: { + id: 'regular-user-2-id', + badges: [{ id: 'indiegogo_en_rhino' }], + }, }, + errors: undefined, } + await factory.create('User', { + id: 'regular-user-2-id', + email: 'regular2@email.com', + }) + await mutate({ + mutation: rewardMutation, + variables, + }) await expect( - client.request(mutation, { - ...variables, - to: 'u2', + mutate({ + mutation: rewardMutation, + variables: { + to: 'regular-user-2-id', + from: 'indiegogo_en_rhino', + }, }), - ).resolves.toEqual(expected) + ).resolves.toMatchObject(expected) }) it('creates no duplicate reward relationships', async () => { - await client.request(mutation, variables) - await client.request(mutation, variables) + await mutate({ + mutation: rewardMutation, + variables, + }) + await mutate({ + mutation: rewardMutation, + variables, + }) - const query = gql` + const userQuery = gql` { - User(id: "u1") { + User(id: "regular-user-id") { badgesCount badges { id @@ -151,22 +211,26 @@ describe('rewards', () => { } } ` - const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] } + const expected = { + data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }, + errors: undefined, + } - await expect(client.request(query)).resolves.toEqual(expected) + await expect(query({ query: userQuery })).resolves.toMatchObject(expected) }) }) describe('authenticated moderator', () => { - let client beforeEach(async () => { - const headers = await login({ email: 'moderator@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + authenticatedUser = moderator.toJson() }) - describe('rewards bage to user', () => { + describe('rewards badge to user', () => { it('throws authorization error', async () => { - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ + data: { reward: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) }) @@ -174,11 +238,14 @@ describe('rewards', () => { describe('unreward', () => { beforeEach(async () => { - await user.relateTo(badge, 'rewarded') + await regularUser.relateTo(badge, 'rewarded') }) - const expected = { unreward: { id: 'u1', badges: [] } } + const expected = { + data: { unreward: { id: 'regular-user-id', badges: [] } }, + errors: undefined, + } - const mutation = gql` + const unrewardMutation = gql` mutation($from: ID!, $to: ID!) { unreward(badgeKey: $from, userId: $to) { id @@ -191,9 +258,10 @@ describe('rewards', () => { describe('check test setup', () => { it('user has one badge', async () => { - const query = gql` + authenticatedUser = regularUser.toJson() + const userQuery = gql` { - User(id: "u1") { + User(id: "regular-user-id") { badgesCount badges { id @@ -201,48 +269,54 @@ describe('rewards', () => { } } ` - const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] } - const client = new GraphQLClient(host) - await expect(client.request(query)).resolves.toEqual(expected) + const expected = { + data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }, + errors: undefined, + } + await expect(query({ query: userQuery })).resolves.toMatchObject(expected) }) }) describe('unauthenticated', () => { - let client - it('throws authorization error', async () => { - client = new GraphQLClient(host) - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + authenticatedUser = null + await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ + data: { unreward: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) describe('authenticated admin', () => { - let client beforeEach(async () => { - const headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + authenticatedUser = await administrator.toJson() }) it('removes a badge from user', async () => { - await expect(client.request(mutation, variables)).resolves.toEqual(expected) + await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject( + expected, + ) }) it('does not crash when unrewarding multiple times', async () => { - await client.request(mutation, variables) - await expect(client.request(mutation, variables)).resolves.toEqual(expected) + await mutate({ mutation: unrewardMutation, variables }) + await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject( + expected, + ) }) }) describe('authenticated moderator', () => { - let client beforeEach(async () => { - const headers = await login({ email: 'moderator@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + authenticatedUser = await moderator.toJson() }) describe('removes bage from user', () => { it('throws authorization error', async () => { - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ + data: { unreward: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) }) diff --git a/backend/src/schema/resolvers/searches.js b/backend/src/schema/resolvers/searches.js new file mode 100644 index 000000000..5316ccd9a --- /dev/null +++ b/backend/src/schema/resolvers/searches.js @@ -0,0 +1,74 @@ +import log from './helpers/databaseLogger' + +export default { + Query: { + findResources: async (_parent, args, context, _resolveInfo) => { + const { query, limit } = args + const { id: thisUserId } = context.user + // see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description + const myQuery = query + .replace(/\s+/g, ' ') + .replace(/[[@#:*~\\$|^\]?/"'(){}+?!,.-;]/g, '') + .split(' ') + .map(s => (s.toLowerCase().match(/^(not|and|or)$/) ? '"' + s + '"' : s + '*')) + .join(' ') + const postCypher = ` + CALL db.index.fulltext.queryNodes('post_fulltext_search', $query) + YIELD node as resource, score + MATCH (resource)<-[:WROTE]-(author:User) + WHERE score >= 0.5 + AND NOT ( + author.deleted = true OR author.disabled = true + OR resource.deleted = true OR resource.disabled = true + OR (:User { id: $thisUserId })-[:BLOCKED]-(author) + ) + WITH resource, author, + [(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments, + [(resource)<-[:SHOUTED]-(user:User) | user] as shouter + RETURN resource { + .*, + __typename: labels(resource)[0], + author: properties(author), + commentsCount: toString(size(comments)), + shoutedCount: toString(size(shouter)) + } + LIMIT $limit + ` + + const userCypher = ` + CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) + YIELD node as resource, score + MATCH (resource) + WHERE score >= 0.5 + AND NOT (resource.deleted = true OR resource.disabled = true + OR (:User { id: $thisUserId })-[:BLOCKED]-(resource)) + RETURN resource {.*, __typename: labels(resource)[0]} + LIMIT $limit + ` + + const session = context.driver.session() + const searchResultPromise = session.readTransaction(async transaction => { + const postTransactionResponse = transaction.run(postCypher, { + query: myQuery, + limit, + thisUserId, + }) + const userTransactionResponse = transaction.run(userCypher, { + query: myQuery, + limit, + thisUserId, + }) + return Promise.all([postTransactionResponse, userTransactionResponse]) + }) + + try { + const [postResults, userResults] = await searchResultPromise + log(postResults) + log(userResults) + return [...postResults.records, ...userResults.records].map(r => r.get('resource')) + } finally { + session.close() + } + }, + }, +} diff --git a/backend/src/schema/resolvers/shout.js b/backend/src/schema/resolvers/shout.js index 398e43d77..70ebdf7ae 100644 --- a/backend/src/schema/resolvers/shout.js +++ b/backend/src/schema/resolvers/shout.js @@ -1,51 +1,62 @@ +import log from './helpers/databaseLogger' + export default { Mutation: { shout: async (_object, params, context, _resolveInfo) => { const { id, type } = params const session = context.driver.session() - const transactionRes = await session.run( - `MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId}) - WHERE $type IN labels(node) AND NOT userWritten.id = $userId - MERGE (user)-[relation:SHOUTED]->(node) - RETURN COUNT(relation) > 0 as isShouted`, - { - id, - type, - userId: context.user.id, - }, - ) - - const [isShouted] = transactionRes.records.map(record => { - return record.get('isShouted') - }) - - session.close() - - return isShouted + try { + const shoutWriteTxResultPromise = session.writeTransaction(async transaction => { + const shoutTransactionResponse = await transaction.run( + ` + MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId}) + WHERE $type IN labels(node) AND NOT userWritten.id = $userId + MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node) + RETURN COUNT(relation) > 0 as isShouted + `, + { + id, + type, + userId: context.user.id, + }, + ) + log(shoutTransactionResponse) + return shoutTransactionResponse.records.map(record => record.get('isShouted')) + }) + const [isShouted] = await shoutWriteTxResultPromise + return isShouted + } finally { + session.close() + } }, unshout: async (_object, params, context, _resolveInfo) => { const { id, type } = params const session = context.driver.session() - - const transactionRes = await session.run( - `MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id}) - WHERE $type IN labels(node) - DELETE relation - RETURN COUNT(relation) > 0 as isShouted`, - { - id, - type, - userId: context.user.id, - }, - ) - const [isShouted] = transactionRes.records.map(record => { - return record.get('isShouted') - }) - session.close() - - return isShouted + try { + const unshoutWriteTxResultPromise = session.writeTransaction(async transaction => { + const unshoutTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id}) + WHERE $type IN labels(node) + DELETE relation + RETURN COUNT(relation) > 0 as isShouted + `, + { + id, + type, + userId: context.user.id, + }, + ) + log(unshoutTransactionResponse) + return unshoutTransactionResponse.records.map(record => record.get('isShouted')) + }) + const [isShouted] = await unshoutWriteTxResultPromise + return isShouted + } finally { + session.close() + } }, }, } diff --git a/backend/src/schema/resolvers/shout.spec.js b/backend/src/schema/resolvers/shout.spec.js index 718a1e169..104a28399 100644 --- a/backend/src/schema/resolvers/shout.spec.js +++ b/backend/src/schema/resolvers/shout.spec.js @@ -1,13 +1,13 @@ -import { GraphQLClient } from 'graphql-request' -import Factory from '../../seed/factories' -import { host, login, gql } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' -let clientUser1, clientUser2 -let headersUser1, headersUser2 +let mutate, query, authenticatedUser, variables const factory = Factory() -const instance = neode() -const categoryIds = ['cat9'] +const instance = getNeode() +const driver = getDriver() const mutationShoutPost = gql` mutation($id: ID!) { @@ -19,141 +19,152 @@ const mutationUnshoutPost = gql` unshout(id: $id, type: Post) } ` -const createPostMutation = gql` - mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]!) { - CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) { +const queryPost = gql` + query($id: ID!) { + Post(id: $id) { id - title - content + shoutedBy { + id + } } } ` -const createPostVariables = { - id: 'p1234', - title: 'Post Title 1234', - content: 'Some Post Content 1234', - categoryIds, -} -beforeEach(async () => { - await factory.create('User', { - id: 'u1', - email: 'test@example.org', - password: '1234', - }) - await factory.create('User', { - id: 'u2', - email: 'test2@example.org', - password: '1234', - }) - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) - headersUser1 = await login({ email: 'test@example.org', password: '1234' }) - headersUser2 = await login({ email: 'test2@example.org', password: '1234' }) - clientUser1 = new GraphQLClient(host, { headers: headersUser1 }) - clientUser2 = new GraphQLClient(host, { headers: headersUser2 }) - await clientUser1.request(createPostMutation, createPostVariables) - await clientUser2.request(createPostMutation, { - id: 'p12345', - title: 'Post Title 12345', - content: 'Some Post Content 12345', - categoryIds, +describe('shout and unshout posts', () => { + let currentUser, postAuthor + beforeAll(() => { + authenticatedUser = undefined + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate + query = createTestClient(server).query }) -}) + beforeEach(async () => { + currentUser = await factory.create('User', { + id: 'current-user-id', + name: 'Current User', + email: 'current.user@example.org', + password: '1234', + }) -afterEach(async () => { - await factory.cleanDatabase() -}) + postAuthor = await factory.create('User', { + id: 'id-of-another-user', + name: 'Another User', + email: 'another.user@example.org', + password: '1234', + }) + }) + afterEach(async () => { + await factory.cleanDatabase() + }) -describe('shout', () => { - describe('shout foreign post', () => { - describe('unauthenticated shout', () => { + describe('shout', () => { + describe('unauthenticated', () => { it('throws authorization error', async () => { - const client = new GraphQLClient(host) - await expect(client.request(mutationShoutPost, { id: 'p1234' })).rejects.toThrow( - 'Not Authorised', + variables = { id: 'post-to-shout-id' } + authenticatedUser = undefined + await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await currentUser.toJson() + await factory.create('Post', { + name: 'Other user post', + id: 'another-user-post-id', + author: postAuthor, + }) + await factory.create('Post', { + name: 'current user post', + id: 'current-user-post-id', + author: currentUser, + }) + variables = {} + }) + + it("can shout another user's post", async () => { + variables = { id: 'another-user-post-id' } + await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({ + data: { shout: true }, + }) + await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ + data: { Post: [{ id: 'another-user-post-id', shoutedBy: [{ id: 'current-user-id' }] }] }, + errors: undefined, + }) + }) + + it('adds `createdAt` to `SHOUT` relationship', async () => { + variables = { id: 'another-user-post-id' } + await mutate({ mutation: mutationShoutPost, variables }) + const relation = await instance.cypher( + 'MATCH (user:User {id: $userId1})-[relationship:SHOUTED]->(node {id: $userId2}) WHERE relationship.createdAt IS NOT NULL RETURN relationship', + { + userId1: 'current-user-id', + userId2: 'another-user-post-id', + }, ) + const relationshipProperties = relation.records.map( + record => record.get('relationship').properties.createdAt, + ) + expect(relationshipProperties[0]).toEqual(expect.any(String)) + }) + + it('can not shout my own post', async () => { + variables = { id: 'current-user-post-id' } + await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({ + data: { shout: false }, + }) + await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ + data: { Post: [{ id: 'current-user-post-id', shoutedBy: [] }] }, + errors: undefined, + }) + }) + }) + }) + describe('unshout', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = undefined + variables = { id: 'post-to-shout-id' } + await expect(mutate({ mutation: mutationUnshoutPost, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) }) }) - it('I shout a post of another user', async () => { - const res = await clientUser1.request(mutationShoutPost, { id: 'p12345' }) - const expected = { - shout: true, - } - expect(res).toMatchObject(expected) - - const { Post } = await clientUser1.request(gql` - query { - Post(id: "p12345") { - shoutedByCurrentUser - } - } - `) - const expected2 = { - shoutedByCurrentUser: true, - } - expect(Post[0]).toMatchObject(expected2) - }) - - it('I can`t shout my own post', async () => { - const res = await clientUser1.request(mutationShoutPost, { id: 'p1234' }) - const expected = { - shout: false, - } - expect(res).toMatchObject(expected) - - const { Post } = await clientUser1.request(gql` - query { - Post(id: "p1234") { - shoutedByCurrentUser - } - } - `) - const expected2 = { - shoutedByCurrentUser: false, - } - expect(Post[0]).toMatchObject(expected2) - }) - }) - - describe('unshout foreign post', () => { - describe('unauthenticated shout', () => { - it('throws authorization error', async () => { - // shout - await clientUser1.request(mutationShoutPost, { id: 'p12345' }) - // unshout - const client = new GraphQLClient(host) - await expect(client.request(mutationUnshoutPost, { id: 'p12345' })).rejects.toThrow( - 'Not Authorised', - ) + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await currentUser.toJson() + await factory.create('Post', { + name: 'Posted By Another User', + id: 'posted-by-another-user', + author: postAuthor, + }) + await mutate({ + mutation: mutationShoutPost, + variables: { id: 'posted-by-another-user' }, + }) }) - }) - it('I unshout a post of another user', async () => { - // shout - await clientUser1.request(mutationShoutPost, { id: 'p12345' }) - const expected = { - unshout: true, - } - // unshout - const res = await clientUser1.request(mutationUnshoutPost, { id: 'p12345' }) - expect(res).toMatchObject(expected) - - const { Post } = await clientUser1.request(gql` - query { - Post(id: "p12345") { - shoutedByCurrentUser - } - } - `) - const expected2 = { - shoutedByCurrentUser: false, - } - expect(Post[0]).toMatchObject(expected2) + it("can unshout another user's post", async () => { + variables = { id: 'posted-by-another-user' } + await expect(mutate({ mutation: mutationUnshoutPost, variables })).resolves.toMatchObject({ + data: { unshout: true }, + }) + await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ + data: { Post: [{ id: 'posted-by-another-user', shoutedBy: [] }] }, + errors: undefined, + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/socialMedia.js b/backend/src/schema/resolvers/socialMedia.js index 49aa6788d..c5b9dcd91 100644 --- a/backend/src/schema/resolvers/socialMedia.js +++ b/backend/src/schema/resolvers/socialMedia.js @@ -1,14 +1,14 @@ -import { neode } from '../../bootstrap/neo4j' +import { getNeode } from '../../db/neo4j' import Resolver from './helpers/Resolver' -const instance = neode() +const neode = getNeode() export default { Mutation: { CreateSocialMedia: async (object, params, context, resolveInfo) => { const [user, socialMedia] = await Promise.all([ - instance.find('User', context.user.id), - instance.create('SocialMedia', params), + neode.find('User', context.user.id), + neode.create('SocialMedia', params), ]) await socialMedia.relateTo(user, 'ownedBy') const response = await socialMedia.toJson() @@ -16,14 +16,14 @@ export default { return response }, UpdateSocialMedia: async (object, params, context, resolveInfo) => { - const socialMedia = await instance.find('SocialMedia', params.id) + const socialMedia = await neode.find('SocialMedia', params.id) await socialMedia.update({ url: params.url }) const response = await socialMedia.toJson() return response }, DeleteSocialMedia: async (object, { id }, context, resolveInfo) => { - const socialMedia = await instance.find('SocialMedia', id) + const socialMedia = await neode.find('SocialMedia', id) if (!socialMedia) return null await socialMedia.delete() return socialMedia.toJson() diff --git a/backend/src/schema/resolvers/socialMedia.spec.js b/backend/src/schema/resolvers/socialMedia.spec.js index 1bbcb8d5b..f292b58a0 100644 --- a/backend/src/schema/resolvers/socialMedia.spec.js +++ b/backend/src/schema/resolvers/socialMedia.spec.js @@ -1,12 +1,12 @@ import { createTestClient } from 'apollo-server-testing' import createServer from '../../server' -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { neode, getDriver } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' const driver = getDriver() const factory = Factory() -const instance = neode() +const neode = getNeode() describe('SocialMedia', () => { let socialMediaAction, someUser, ownerNode, owner @@ -27,15 +27,15 @@ describe('SocialMedia', () => { const newUrl = 'https://twitter.com/bullerby' const setUpSocialMedia = async () => { - const socialMediaNode = await instance.create('SocialMedia', { url }) + const socialMediaNode = await neode.create('SocialMedia', { url }) await socialMediaNode.relateTo(ownerNode, 'ownedBy') return socialMediaNode.toJson() } beforeEach(async () => { - const someUserNode = await instance.create('User', userParams) + const someUserNode = await neode.create('User', userParams) someUser = await someUserNode.toJson() - ownerNode = await instance.create('User', ownerParams) + ownerNode = await neode.create('User', ownerParams) owner = await ownerNode.toJson() socialMediaAction = async (user, mutation, variables) => { diff --git a/backend/src/schema/resolvers/statistics.js b/backend/src/schema/resolvers/statistics.js index 466c1ef70..7ca9239f3 100644 --- a/backend/src/schema/resolvers/statistics.js +++ b/backend/src/schema/resolvers/statistics.js @@ -1,37 +1,45 @@ +import log from './helpers/databaseLogger' + export default { Query: { - statistics: async (parent, args, { driver, user }) => { + statistics: async (_parent, _args, { driver }) => { const session = driver.session() - const response = {} + const counts = {} try { const mapping = { countUsers: 'User', countPosts: 'Post', countComments: 'Comment', countNotifications: 'NOTIFIED', - countInvites: 'InvitationCode', + countEmails: 'EmailAddress', countFollows: 'FOLLOWS', countShouts: 'SHOUTED', } - const cypher = ` - CALL apoc.meta.stats() YIELD labels, relTypesCount - RETURN labels, relTypesCount - ` - const result = await session.run(cypher) - const [statistics] = await result.records.map(record => { - return { - ...record.get('labels'), - ...record.get('relTypesCount'), - } + const statisticsReadTxResultPromise = session.readTransaction(async transaction => { + const statisticsTransactionResponse = await transaction.run( + ` + CALL apoc.meta.stats() YIELD labels, relTypesCount + RETURN labels, relTypesCount + `, + ) + log(statisticsTransactionResponse) + return statisticsTransactionResponse.records.map(record => { + return { + ...record.get('labels'), + ...record.get('relTypesCount'), + } + }) }) + const [statistics] = await statisticsReadTxResultPromise Object.keys(mapping).forEach(key => { const stat = statistics[mapping[key]] - response[key] = stat ? stat.toNumber() : 0 + counts[key] = stat ? stat.toNumber() : 0 }) + counts.countInvites = counts.countEmails - counts.countUsers + return counts } finally { session.close() } - return response }, }, } diff --git a/backend/src/schema/resolvers/statistics.spec.js b/backend/src/schema/resolvers/statistics.spec.js new file mode 100644 index 000000000..e2b9dafe4 --- /dev/null +++ b/backend/src/schema/resolvers/statistics.spec.js @@ -0,0 +1,140 @@ +import { createTestClient } from 'apollo-server-testing' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' + +let query, authenticatedUser +const factory = Factory() +const instance = getNeode() +const driver = getDriver() + +const statisticsQuery = gql` + query { + statistics { + countUsers + countPosts + countComments + countNotifications + countInvites + countFollows + countShouts + } + } +` +beforeAll(() => { + authenticatedUser = undefined + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('statistics', () => { + describe('countUsers', () => { + beforeEach(async () => { + await Promise.all( + [...Array(6).keys()].map(() => { + return factory.create('User') + }), + ) + }) + + it('returns the count of all users', async () => { + await expect(query({ query: statisticsQuery })).resolves.toMatchObject({ + data: { statistics: { countUsers: 6 } }, + errors: undefined, + }) + }) + }) + + describe('countPosts', () => { + beforeEach(async () => { + await Promise.all( + [...Array(3).keys()].map(() => { + return factory.create('Post') + }), + ) + }) + + it('returns the count of all posts', async () => { + await expect(query({ query: statisticsQuery })).resolves.toMatchObject({ + data: { statistics: { countPosts: 3 } }, + errors: undefined, + }) + }) + }) + + describe('countComments', () => { + beforeEach(async () => { + await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('Comment') + }), + ) + }) + + it('returns the count of all comments', async () => { + await expect(query({ query: statisticsQuery })).resolves.toMatchObject({ + data: { statistics: { countComments: 2 } }, + errors: undefined, + }) + }) + }) + + describe('countFollows', () => { + let users + beforeEach(async () => { + users = await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('User') + }), + ) + await users[0].relateTo(users[1], 'following') + }) + + it('returns the count of all follows', async () => { + await expect(query({ query: statisticsQuery })).resolves.toMatchObject({ + data: { statistics: { countFollows: 1 } }, + errors: undefined, + }) + }) + }) + + describe('countShouts', () => { + let users, posts + beforeEach(async () => { + users = await Promise.all( + [...Array(2).keys()].map(() => { + return factory.create('User') + }), + ) + posts = await Promise.all( + [...Array(3).keys()].map(() => { + return factory.create('Post') + }), + ) + await Promise.all([ + users[0].relateTo(posts[1], 'shouted'), + users[1].relateTo(posts[0], 'shouted'), + ]) + }) + + it('returns the count of all shouts', async () => { + await expect(query({ query: statisticsQuery })).resolves.toMatchObject({ + data: { statistics: { countShouts: 2 } }, + errors: undefined, + }) + }) + }) +}) diff --git a/backend/src/schema/resolvers/user_management.js b/backend/src/schema/resolvers/user_management.js index e1528cc9e..4d40a6f63 100644 --- a/backend/src/schema/resolvers/user_management.js +++ b/backend/src/schema/resolvers/user_management.js @@ -1,9 +1,11 @@ import encode from '../../jwt/encode' import bcrypt from 'bcryptjs' import { AuthenticationError } from 'apollo-server' -import { neode } from '../../bootstrap/neo4j' +import { getNeode } from '../../db/neo4j' +import normalizeEmail from './helpers/normalizeEmail' +import log from './helpers/databaseLogger' -const instance = neode() +const neode = getNeode() export default { Query: { @@ -12,7 +14,7 @@ export default { }, currentUser: async (object, params, ctx, resolveInfo) => { if (!ctx.user) return null - const user = await instance.find('User', ctx.user.id) + const user = await neode.find('User', ctx.user.id) return user.toJson() }, }, @@ -21,34 +23,39 @@ export default { // if (user && user.id) { // throw new Error('Already logged in.') // } + email = normalizeEmail(email) const session = driver.session() - const result = await session.run( - ` - MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail}) - RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1 - `, - { userEmail: email }, - ) - session.close() - const [currentUser] = await result.records.map(record => { - return record.get('user') - }) - - if ( - currentUser && - (await bcrypt.compareSync(password, currentUser.encryptedPassword)) && - !currentUser.disabled - ) { - delete currentUser.encryptedPassword - return encode(currentUser) - } else if (currentUser && currentUser.disabled) { - throw new AuthenticationError('Your account has been disabled.') - } else { - throw new AuthenticationError('Incorrect email address or password.') + try { + const loginReadTxResultPromise = session.readTransaction(async transaction => { + const loginTransactionResponse = await transaction.run( + ` + MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail}) + RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1 + `, + { userEmail: email }, + ) + log(loginTransactionResponse) + return loginTransactionResponse.records.map(record => record.get('user')) + }) + const [currentUser] = await loginReadTxResultPromise + if ( + currentUser && + (await bcrypt.compareSync(password, currentUser.encryptedPassword)) && + !currentUser.disabled + ) { + delete currentUser.encryptedPassword + return encode(currentUser) + } else if (currentUser && currentUser.disabled) { + throw new AuthenticationError('Your account has been disabled.') + } else { + throw new AuthenticationError('Incorrect email address or password.') + } + } finally { + session.close() } }, changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => { - const currentUser = await instance.find('User', user.id) + const currentUser = await neode.find('User', user.id) const encryptedPassword = currentUser.get('encryptedPassword') if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) { diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js index 4fe21f92a..5e7043e74 100644 --- a/backend/src/schema/resolvers/user_management.spec.js +++ b/backend/src/schema/resolvers/user_management.spec.js @@ -1,31 +1,33 @@ import jwt from 'jsonwebtoken' import CONFIG from './../../config' -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' import { createTestClient } from 'apollo-server-testing' import createServer, { context } from '../../server' import encode from '../../jwt/encode' +import { getNeode } from '../../db/neo4j' const factory = Factory() -let query -let mutate -let variables -let req -let user +const neode = getNeode() +let query, mutate, variables, req, user const disable = async id => { - await factory.create('User', { id: 'u2', role: 'moderator' }) - const moderatorBearerToken = encode({ id: 'u2' }) - req = { headers: { authorization: `Bearer ${moderatorBearerToken}` } } - await mutate({ - mutation: gql` - mutation($id: ID!) { - disable(id: $id) - } - `, - variables: { id }, - }) - req = { headers: {} } + const moderator = await factory.create('User', { id: 'u2', role: 'moderator' }) + const user = await neode.find('User', id) + const reportAgainstUser = await factory.create('Report') + await Promise.all([ + reportAgainstUser.relateTo(moderator, 'filed', { + resourceId: id, + reasonCategory: 'discrimination_etc', + reasonDescription: 'This user is harassing me with bigoted remarks!', + }), + reportAgainstUser.relateTo(user, 'belongsTo'), + ]) + const disableVariables = { resourceId: user.id, disable: true, closed: false } + await Promise.all([ + reportAgainstUser.relateTo(moderator, 'reviewed', disableVariables), + user.update({ disabled: true, updatedAt: new Date().toISOString() }), + ]) } beforeEach(() => { @@ -214,6 +216,28 @@ describe('login', () => { }) }) }) + + describe('normalization', () => { + describe('email address is a gmail address ', () => { + beforeEach(async () => { + const email = await neode.first('EmailAddress', { email: 'test@example.org' }) + await email.update({ email: 'someuser@gmail.com' }) + }) + + describe('supplied email contains dots', () => { + beforeEach(() => { + variables = { ...variables, email: 'some.user@gmail.com' } + }) + + it('normalizes email, issue #2329', async () => { + await respondsWith({ + data: { login: expect.any(String) }, + errors: undefined, + }) + }) + }) + }) + }) }) describe('with a valid email but incorrect password', () => { diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index ea9220d5e..4af60f014 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -1,46 +1,33 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import fileUpload from './fileUpload' -import { neode } from '../../bootstrap/neo4j' +import { getNeode } from '../../db/neo4j' import { UserInputError, ForbiddenError } from 'apollo-server' import Resolver from './helpers/Resolver' +import log from './helpers/databaseLogger' +import createOrUpdateLocations from './users/location' -const instance = neode() +const neode = getNeode() -export const getBlockedUsers = async context => { +export const getMutedUsers = async context => { const { neode } = context const userModel = neode.model('User') - let blockedUsers = neode + let mutedUsers = neode .query() .match('user', userModel) .where('user.id', context.user.id) - .relationship(userModel.relationships().get('blocked')) - .to('blocked', userModel) - .return('blocked') - blockedUsers = await blockedUsers.execute() - blockedUsers = blockedUsers.records.map(r => r.get('blocked').properties) - return blockedUsers -} - -export const getBlockedByUsers = async context => { - const { neode } = context - const userModel = neode.model('User') - let blockedByUsers = neode - .query() - .match('user', userModel) - .relationship(userModel.relationships().get('blocked')) - .to('blocked', userModel) - .where('blocked.id', context.user.id) - .return('user') - blockedByUsers = await blockedByUsers.execute() - blockedByUsers = blockedByUsers.records.map(r => r.get('user').properties) - return blockedByUsers + .relationship(userModel.relationships().get('muted')) + .to('muted', userModel) + .return('muted') + mutedUsers = await mutedUsers.execute() + mutedUsers = mutedUsers.records.map(r => r.get('muted').properties) + return mutedUsers } export default { Query: { - blockedUsers: async (object, args, context, resolveInfo) => { + mutedUsers: async (object, args, context, resolveInfo) => { try { - return getBlockedUsers(context) + return getMutedUsers(context) } catch (e) { throw new UserInputError(e.message) } @@ -48,19 +35,61 @@ export default { User: async (object, args, context, resolveInfo) => { const { email } = args if (email) { - const e = await instance.first('EmailAddress', { email }) - let user = e.get('belongsTo') - user = await user.toJson() - return [user.node] + let session + try { + session = context.driver.session() + const readTxResult = await session.readTransaction(txc => { + const result = txc.run( + ` + MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $args.email}) + RETURN user`, + { args }, + ) + return result + }) + return readTxResult.records.map(r => r.get('user').properties) + } finally { + session.close() + } } - return neo4jgraphql(object, args, context, resolveInfo, false) + return neo4jgraphql(object, args, context, resolveInfo) }, }, Mutation: { + muteUser: async (_parent, params, context, _resolveInfo) => { + const { user: currentUser } = context + if (currentUser.id === params.id) return null + await neode.cypher( + ` + MATCH(u:User {id: $currentUser.id})-[previousRelationship:FOLLOWS]->(b:User {id: $params.id}) + DELETE previousRelationship + `, + { currentUser, params }, + ) + const [user, mutedUser] = await Promise.all([ + neode.find('User', currentUser.id), + neode.find('User', params.id), + ]) + await user.relateTo(mutedUser, 'muted') + return mutedUser.toJson() + }, + unmuteUser: async (_parent, params, context, _resolveInfo) => { + const { user: currentUser } = context + if (currentUser.id === params.id) return null + await neode.cypher( + ` + MATCH(u:User {id: $currentUser.id})-[previousRelationship:MUTED]->(b:User {id: $params.id}) + DELETE previousRelationship + `, + { currentUser, params }, + ) + const unmutedUser = await neode.find('User', params.id) + return unmutedUser.toJson() + }, block: async (object, args, context, resolveInfo) => { const { user: currentUser } = context if (currentUser.id === args.id) return null - await instance.cypher( + await neode.cypher( ` MATCH(u:User {id: $currentUser.id})-[r:FOLLOWS]->(b:User {id: $args.id}) DELETE r @@ -68,8 +97,8 @@ export default { { currentUser, args }, ) const [user, blockedUser] = await Promise.all([ - instance.find('User', currentUser.id), - instance.find('User', args.id), + neode.find('User', currentUser.id), + neode.find('User', args.id), ]) await user.relateTo(blockedUser, 'blocked') return blockedUser.toJson() @@ -77,82 +106,100 @@ export default { unblock: async (object, args, context, resolveInfo) => { const { user: currentUser } = context if (currentUser.id === args.id) return null - await instance.cypher( + await neode.cypher( ` MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(b:User {id: $args.id}) DELETE r `, { currentUser, args }, ) - const blockedUser = await instance.find('User', args.id) + const blockedUser = await neode.find('User', args.id) return blockedUser.toJson() }, - UpdateUser: async (object, args, context, resolveInfo) => { - const { termsAndConditionsAgreedVersion } = args + UpdateUser: async (_parent, params, context, _resolveInfo) => { + const { termsAndConditionsAgreedVersion } = params if (termsAndConditionsAgreedVersion) { const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) if (!regEx.test(termsAndConditionsAgreedVersion)) { throw new ForbiddenError('Invalid version format!') } - args.termsAndConditionsAgreedAt = new Date().toISOString() + params.termsAndConditionsAgreedAt = new Date().toISOString() } - args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' }) + params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' }) + const session = context.driver.session() + + const writeTxResultPromise = session.writeTransaction(async transaction => { + const updateUserTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $params.id}) + SET user += $params + SET user.updatedAt = toString(datetime()) + RETURN user + `, + { params }, + ) + return updateUserTransactionResponse.records.map(record => record.get('user').properties) + }) try { - const user = await instance.find('User', args.id) - if (!user) return null - await user.update({ ...args, updatedAt: new Date().toISOString() }) - return user.toJson() - } catch (e) { - throw new UserInputError(e.message) + const [user] = await writeTxResultPromise + await createOrUpdateLocations(params.id, params.locationName, session) + return user + } catch (error) { + throw new UserInputError(error.message) + } finally { + session.close() } }, DeleteUser: async (object, params, context, resolveInfo) => { const { resource } = params const session = context.driver.session() - - let user try { if (resource && resource.length) { - await Promise.all( - resource.map(async node => { - await session.run( + await session.writeTransaction(transaction => { + resource.map(node => { + return transaction.run( ` - MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId}) - OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment) - SET resource.deleted = true - SET resource.content = 'UNAVAILABLE' - SET resource.contentExcerpt = 'UNAVAILABLE' - SET comment.deleted = true - RETURN author`, + MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId}) + OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment) + SET resource.deleted = true + SET resource.content = 'UNAVAILABLE' + SET resource.contentExcerpt = 'UNAVAILABLE' + SET comment.deleted = true + RETURN author + `, { userId: context.user.id, }, ) - }), - ) + }) + }) } - // we cannot set slug to 'UNAVAILABE' because of unique constraints - const transactionResult = await session.run( - ` - MATCH (user:User {id: $userId}) - SET user.deleted = true - SET user.name = 'UNAVAILABLE' - SET user.about = 'UNAVAILABLE' - WITH user - OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress) - DETACH DELETE email - WITH user - OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia) - DETACH DELETE socialMedia - RETURN user`, - { userId: context.user.id }, - ) - user = transactionResult.records.map(r => r.get('user').properties)[0] + const deleteUserTxResultPromise = session.writeTransaction(async transaction => { + const deleteUserTransactionResponse = await transaction.run( + ` + MATCH (user:User {id: $userId}) + SET user.deleted = true + SET user.name = 'UNAVAILABLE' + SET user.about = 'UNAVAILABLE' + WITH user + OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress) + DETACH DELETE email + WITH user + OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia) + DETACH DELETE socialMedia + RETURN user + `, + { userId: context.user.id }, + ) + log(deleteUserTransactionResponse) + return deleteUserTransactionResponse.records.map(record => record.get('user').properties) + }) + const [user] = await deleteUserTxResultPromise + return user } finally { session.close() } - return user }, }, User: { @@ -160,7 +207,7 @@ export default { if (typeof parent.email !== 'undefined') return parent.email const { id } = parent const statement = `MATCH(u:User {id: {id}})-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e` - const result = await instance.cypher(statement, { id }) + const result = await neode.cypher(statement, { id }) const [{ email }] = result.records.map(r => r.get('e').properties) return email }, @@ -175,12 +222,17 @@ export default { 'about', 'termsAndConditionsAgreedVersion', 'termsAndConditionsAgreedAt', + 'allowEmbedIframes', + 'showShoutsPublicly', + 'locale', ], boolean: { followedByCurrentUser: 'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', isBlocked: 'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', + isMuted: + 'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', }, count: { contributionsCount: @@ -196,7 +248,6 @@ export default { }, hasOne: { invitedBy: '<-[:INVITED]-(related:User)', - disabledBy: '<-[:DISABLED]-(related:User)', location: '-[:IS_IN]->(related:Location)', }, hasMany: { diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 784a48c06..cfd84fcf7 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -1,6 +1,6 @@ -import Factory from '../../seed/factories' -import { gql } from '../../jest/helpers' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import Factory from '../../factories' +import { gql } from '../../helpers/jest' +import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -68,6 +68,22 @@ describe('User', () => { it('is permitted', async () => { await expect(query({ query: userQuery, variables })).resolves.toMatchObject({ data: { User: [{ name: 'Johnny' }] }, + errors: undefined, + }) + }) + + it('non-existing email address, issue #2294', async () => { + // see: https://github.com/Human-Connection/Human-Connection/issues/2294 + await expect( + query({ + query: userQuery, + variables: { + email: 'this-email-does-not-exist@example.org', + }, + }), + ).resolves.toMatchObject({ + data: { User: [] }, + errors: undefined, }) }) }) @@ -75,8 +91,7 @@ describe('User', () => { }) describe('UpdateUser', () => { - let userParams - let variables + let userParams, variables beforeEach(async () => { userParams = { @@ -86,6 +101,7 @@ describe('UpdateUser', () => { name: 'John Doe', termsAndConditionsAgreedVersion: null, termsAndConditionsAgreedAt: null, + allowEmbedIframes: false, } variables = { @@ -95,16 +111,23 @@ describe('UpdateUser', () => { }) const updateUserMutation = gql` - mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) { + mutation( + $id: ID! + $name: String + $termsAndConditionsAgreedVersion: String + $locationName: String + ) { UpdateUser( id: $id name: $name termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion + locationName: $locationName ) { id name termsAndConditionsAgreedVersion termsAndConditionsAgreedAt + locationName } } ` @@ -136,7 +159,7 @@ describe('UpdateUser', () => { authenticatedUser = await user.toJson() }) - it('name within specifications', async () => { + it('updates the name', async () => { const expected = { data: { UpdateUser: { @@ -144,36 +167,13 @@ describe('UpdateUser', () => { name: 'John Doughnut', }, }, + errors: undefined, } await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( expected, ) }) - it('with `null` as name', async () => { - const variables = { - id: 'u47', - name: null, - } - const { errors } = await mutate({ mutation: updateUserMutation, variables }) - expect(errors[0]).toHaveProperty( - 'message', - 'child "name" fails because ["name" contains an invalid value, "name" must be a string]', - ) - }) - - it('with too short name', async () => { - const variables = { - id: 'u47', - name: ' ', - } - const { errors } = await mutate({ mutation: updateUserMutation, variables }) - expect(errors[0]).toHaveProperty( - 'message', - 'child "name" fails because ["name" length must be at least 3 characters long]', - ) - }) - describe('given a new agreed version of terms and conditions', () => { beforeEach(async () => { variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' } @@ -186,6 +186,7 @@ describe('UpdateUser', () => { termsAndConditionsAgreedAt: expect.any(String), }), }, + errors: undefined, } await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( @@ -206,6 +207,7 @@ describe('UpdateUser', () => { termsAndConditionsAgreedAt: null, }), }, + errors: undefined, } await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( @@ -222,6 +224,14 @@ describe('UpdateUser', () => { const { errors } = await mutate({ mutation: updateUserMutation, variables }) expect(errors[0]).toHaveProperty('message', 'Invalid version format!') }) + + it('supports updating location', async () => { + variables = { ...variables, locationName: 'Hamburg, New Jersey, United States of America' } + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ + data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States of America' } }, + errors: undefined, + }) + }) }) }) @@ -356,6 +366,7 @@ describe('DeleteUser', () => { ], }, }, + errors: undefined, } await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject( expectedResponse, @@ -402,6 +413,7 @@ describe('DeleteUser', () => { ], }, }, + errors: undefined, } await expect( mutate({ mutation: deleteUserMutation, variables }), @@ -449,6 +461,7 @@ describe('DeleteUser', () => { ], }, }, + errors: undefined, } await expect( mutate({ mutation: deleteUserMutation, variables }), @@ -495,6 +508,7 @@ describe('DeleteUser', () => { ], }, }, + errors: undefined, } await expect( mutate({ mutation: deleteUserMutation, variables }), diff --git a/backend/src/middleware/nodes/locations.js b/backend/src/schema/resolvers/users/location.js similarity index 63% rename from backend/src/middleware/nodes/locations.js rename to backend/src/schema/resolvers/users/location.js index a90d8c0d7..3f3638bf5 100644 --- a/backend/src/middleware/nodes/locations.js +++ b/backend/src/schema/resolvers/users/location.js @@ -2,8 +2,8 @@ import request from 'request' import { UserInputError } from 'apollo-server' import isEmpty from 'lodash/isEmpty' import Debug from 'debug' -import asyncForEach from '../../helpers/asyncForEach' -import CONFIG from './../../config' +import asyncForEach from '../../../helpers/asyncForEach' +import CONFIG from '../../../config' const debug = Debug('human-connection:location') @@ -19,7 +19,7 @@ const fetch = url => { }) } -const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl'] +const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl', 'ru'] const createLocation = async (session, mapboxData) => { const data = { @@ -32,12 +32,13 @@ const createLocation = async (session, mapboxData) => { nameES: mapboxData.text_es, namePT: mapboxData.text_pt, namePL: mapboxData.text_pl, + nameRU: mapboxData.text_ru, type: mapboxData.id.split('.')[0].toLowerCase(), lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null, lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null, } - let query = + let mutation = 'MERGE (l:Location {id: $id}) ' + 'SET l.name = $nameEN, ' + 'l.nameEN = $nameEN, ' + @@ -48,21 +49,23 @@ const createLocation = async (session, mapboxData) => { 'l.nameES = $nameES, ' + 'l.namePT = $namePT, ' + 'l.namePL = $namePL, ' + + 'l.nameRU = $nameRU, ' + 'l.type = $type' if (data.lat && data.lng) { - query += ', l.lat = $lat, l.lng = $lng' + mutation += ', l.lat = $lat, l.lng = $lng' } - query += ' RETURN l.id' + mutation += ' RETURN l.id' - await session.run(query, data) + await session.writeTransaction(transaction => { + return transaction.run(mutation, data) + }) } -const createOrUpdateLocations = async (userId, locationName, driver) => { +const createOrUpdateLocations = async (userId, locationName, session) => { if (isEmpty(locationName)) { return } - const res = await fetch( `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( locationName, @@ -92,7 +95,6 @@ const createOrUpdateLocations = async (userId, locationName, driver) => { throw new UserInputError('locationName is invalid') } - const session = driver.session() if (data.place_type.length > 1) { data.id = 'region.' + data.id.split('.')[1] } @@ -103,33 +105,36 @@ const createOrUpdateLocations = async (userId, locationName, driver) => { if (data.context) { await asyncForEach(data.context, async ctx => { await createLocation(session, ctx) - - await session.run( - 'MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) ' + - 'MERGE (child)<-[:IS_IN]-(parent) ' + - 'RETURN child.id, parent.id', - { - parentId: parent.id, - childId: ctx.id, - }, - ) - + await session.writeTransaction(transaction => { + return transaction.run( + ` + MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) + MERGE (child)<-[:IS_IN]-(parent) + RETURN child.id, parent.id + `, + { + parentId: parent.id, + childId: ctx.id, + }, + ) + }) parent = ctx }) } - // delete all current locations from user - await session.run('MATCH (u:User {id: $userId})-[r:IS_IN]->(l:Location) DETACH DELETE r', { - userId: userId, + // delete all current locations from user and add new location + await session.writeTransaction(transaction => { + return transaction.run( + ` + MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location) + DETACH DELETE relationship + WITH user + MATCH (location:Location {id: $locationId}) + MERGE (user)-[:IS_IN]->(location) + RETURN location.id, user.id + `, + { userId: userId, locationId: data.id }, + ) }) - // connect user with location - await session.run( - 'MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id', - { - userId: userId, - locationId: data.id, - }, - ) - session.close() } export default createOrUpdateLocations diff --git a/backend/src/schema/resolvers/users/location.spec.js b/backend/src/schema/resolvers/users/location.spec.js new file mode 100644 index 000000000..f7315174c --- /dev/null +++ b/backend/src/schema/resolvers/users/location.spec.js @@ -0,0 +1,213 @@ +import { gql } from '../../../helpers/jest' +import Factory from '../../../factories' +import { getNeode, getDriver } from '../../../db/neo4j' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../../../server' + +const factory = Factory() +const neode = getNeode() +const driver = getDriver() +let authenticatedUser, mutate, variables + +const signupVerificationMutation = gql` + mutation( + $name: String! + $password: String! + $email: String! + $nonce: String! + $termsAndConditionsAgreedVersion: String! + $locationName: String + ) { + SignupVerification( + name: $name + password: $password + email: $email + nonce: $nonce + termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion + locationName: $locationName + ) { + locationName + } + } +` + +const updateUserMutation = gql` + mutation($id: ID!, $name: String!, $locationName: String) { + UpdateUser(id: $id, name: $name, locationName: $locationName) { + locationName + } + } +` + +let newlyCreatedNodesWithLocales = [ + { + city: { + lng: 41.1534, + nameES: 'Hamburg', + nameFR: 'Hamburg', + nameIT: 'Hamburg', + nameEN: 'Hamburg', + type: 'place', + namePT: 'Hamburg', + nameRU: 'Хамбург', + nameDE: 'Hamburg', + nameNL: 'Hamburg', + name: 'Hamburg', + namePL: 'Hamburg', + id: 'place.5977106083398860', + lat: -74.5763, + }, + state: { + namePT: 'Nova Jérsia', + nameRU: 'Нью-Джерси', + nameDE: 'New Jersey', + nameNL: 'New Jersey', + nameES: 'Nueva Jersey', + name: 'New Jersey', + namePL: 'New Jersey', + nameFR: 'New Jersey', + nameIT: 'New Jersey', + id: 'region.14919479731700330', + nameEN: 'New Jersey', + type: 'region', + }, + country: { + namePT: 'Estados Unidos', + nameRU: 'Соединённые Штаты Америки', + nameDE: 'Vereinigte Staaten', + nameNL: 'Verenigde Staten van Amerika', + nameES: 'Estados Unidos', + namePL: 'Stany Zjednoczone', + name: 'United States of America', + nameFR: 'États-Unis', + nameIT: "Stati Uniti d'America", + id: 'country.9053006287256050', + nameEN: 'United States of America', + type: 'country', + }, + }, +] + +beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + user: authenticatedUser, + neode, + driver, + } + }, + }) + mutate = createTestClient(server).mutate +}) + +beforeEach(() => { + variables = {} + authenticatedUser = null +}) + +afterEach(() => { + factory.cleanDatabase() +}) + +describe('userMiddleware', () => { + describe('SignupVerification', () => { + beforeEach(async () => { + variables = { + ...variables, + name: 'John Doe', + password: '123', + email: 'john@example.org', + nonce: '123456', + termsAndConditionsAgreedVersion: '0.1.0', + locationName: 'Hamburg, New Jersey, United States of America', + } + const args = { + email: 'john@example.org', + nonce: '123456', + } + await neode.model('EmailAddress').create(args) + }) + it('creates a Location node with localised city/state/country names', async () => { + await mutate({ mutation: signupVerificationMutation, variables }) + const locations = await neode.cypher( + `MATCH (city:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city, state, country`, + ) + expect( + locations.records.map(record => { + return { + city: record.get('city').properties, + state: record.get('state').properties, + country: record.get('country').properties, + } + }), + ).toEqual(newlyCreatedNodesWithLocales) + }) + }) + + describe('UpdateUser', () => { + let user, userParams + beforeEach(async () => { + newlyCreatedNodesWithLocales = [ + { + city: { + lng: 53.55, + nameES: 'Hamburgo', + nameFR: 'Hambourg', + nameIT: 'Amburgo', + nameEN: 'Hamburg', + type: 'region', + namePT: 'Hamburgo', + nameRU: 'Гамбург', + nameDE: 'Hamburg', + nameNL: 'Hamburg', + namePL: 'Hamburg', + name: 'Hamburg', + id: 'region.10793468240398860', + lat: 10, + }, + country: { + namePT: 'Alemanha', + nameRU: 'Германия', + nameDE: 'Deutschland', + nameNL: 'Duitsland', + nameES: 'Alemania', + name: 'Germany', + namePL: 'Niemcy', + nameFR: 'Allemagne', + nameIT: 'Germania', + id: 'country.10743216036480410', + nameEN: 'Germany', + type: 'country', + }, + }, + ] + userParams = { + id: 'updating-user', + } + user = await factory.create('User', userParams) + authenticatedUser = await user.toJson() + }) + + it('creates a Location node with localised city/state/country names', async () => { + variables = { + ...variables, + id: 'updating-user', + name: 'Updating user', + locationName: 'Hamburg, Germany', + } + await mutate({ mutation: updateUserMutation, variables }) + const locations = await neode.cypher( + `MATCH (city:Location)-[:IS_IN]->(country:Location) return city, country`, + ) + expect( + locations.records.map(record => { + return { + city: record.get('city').properties, + country: record.get('country').properties, + } + }), + ).toEqual(newlyCreatedNodesWithLocales) + }) + }) +}) diff --git a/backend/src/schema/resolvers/users/blockedUsers.spec.js b/backend/src/schema/resolvers/users/mutedUsers.spec.js similarity index 54% rename from backend/src/schema/resolvers/users/blockedUsers.spec.js rename to backend/src/schema/resolvers/users/mutedUsers.spec.js index 887c34494..130df08ce 100644 --- a/backend/src/schema/resolvers/users/blockedUsers.spec.js +++ b/backend/src/schema/resolvers/users/mutedUsers.spec.js @@ -1,15 +1,15 @@ import { createTestClient } from 'apollo-server-testing' import createServer from '../../../server' -import Factory from '../../../seed/factories' -import { gql } from '../../../jest/helpers' -import { neode, getDriver } from '../../../bootstrap/neo4j' +import Factory from '../../../factories' +import { gql } from '../../../helpers/jest' +import { getNeode, getDriver } from '../../../db/neo4j' const driver = getDriver() const factory = Factory() -const instance = neode() +const neode = getNeode() let currentUser -let blockedUser +let mutedUser let authenticatedUser let server @@ -20,7 +20,7 @@ beforeEach(() => { return { user: authenticatedUser, driver, - neode: instance, + neode, cypherParams: { currentUserId: authenticatedUser ? authenticatedUser.id : null, }, @@ -33,15 +33,15 @@ afterEach(async () => { await factory.cleanDatabase() }) -describe('blockedUsers', () => { - let blockedUserQuery +describe('mutedUsers', () => { + let mutedUserQuery beforeEach(() => { - blockedUserQuery = gql` + mutedUserQuery = gql` query { - blockedUsers { + mutedUsers { id name - isBlocked + isMuted } } ` @@ -49,34 +49,34 @@ describe('blockedUsers', () => { it('throws permission error', async () => { const { query } = createTestClient(server) - const result = await query({ query: blockedUserQuery }) + const result = await query({ query: mutedUserQuery }) expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') }) - describe('authenticated and given a blocked user', () => { + describe('authenticated and given a muted user', () => { beforeEach(async () => { - currentUser = await instance.create('User', { + currentUser = await neode.create('User', { name: 'Current User', id: 'u1', }) - blockedUser = await instance.create('User', { - name: 'Blocked User', + mutedUser = await neode.create('User', { + name: 'Muted User', id: 'u2', }) - await currentUser.relateTo(blockedUser, 'blocked') + await currentUser.relateTo(mutedUser, 'muted') authenticatedUser = await currentUser.toJson() }) - it('returns a list of blocked users', async () => { + it('returns a list of muted users', async () => { const { query } = createTestClient(server) - await expect(query({ query: blockedUserQuery })).resolves.toEqual( + await expect(query({ query: mutedUserQuery })).resolves.toEqual( expect.objectContaining({ data: { - blockedUsers: [ + mutedUsers: [ { - name: 'Blocked User', + name: 'Muted User', id: 'u2', - isBlocked: true, + isMuted: true, }, ], }, @@ -86,79 +86,81 @@ describe('blockedUsers', () => { }) }) -describe('block', () => { - let blockAction +describe('muteUser', () => { + let muteAction beforeEach(() => { currentUser = undefined - blockAction = variables => { + muteAction = variables => { const { mutate } = createTestClient(server) - const blockMutation = gql` + const muteUserMutation = gql` mutation($id: ID!) { - block(id: $id) { + muteUser(id: $id) { id name - isBlocked + isMuted } } ` - return mutate({ mutation: blockMutation, variables }) + return mutate({ mutation: muteUserMutation, variables }) } }) it('throws permission error', async () => { - const result = await blockAction({ id: 'u2' }) + const result = await muteAction({ id: 'u2' }) expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') }) describe('authenticated', () => { beforeEach(async () => { - currentUser = await instance.create('User', { + currentUser = await neode.create('User', { name: 'Current User', id: 'u1', }) authenticatedUser = await currentUser.toJson() }) - describe('block yourself', () => { + describe('mute yourself', () => { it('returns null', async () => { - await expect(blockAction({ id: 'u1' })).resolves.toEqual( - expect.objectContaining({ data: { block: null } }), + await expect(muteAction({ id: 'u1' })).resolves.toEqual( + expect.objectContaining({ data: { muteUser: null } }), ) }) }) - describe('block not existing user', () => { + describe('mute not existing user', () => { it('returns null', async () => { - await expect(blockAction({ id: 'u2' })).resolves.toEqual( - expect.objectContaining({ data: { block: null } }), + await expect(muteAction({ id: 'u2' })).resolves.toEqual( + expect.objectContaining({ data: { muteUser: null } }), ) }) }) - describe('given a to-be-blocked user', () => { + describe('given a to-be-muted user', () => { beforeEach(async () => { - blockedUser = await instance.create('User', { - name: 'Blocked User', + mutedUser = await neode.create('User', { + name: 'Muted User', id: 'u2', }) }) - it('blocks a user', async () => { - await expect(blockAction({ id: 'u2' })).resolves.toEqual( + it('mutes a user', async () => { + await expect(muteAction({ id: 'u2' })).resolves.toEqual( expect.objectContaining({ - data: { block: { id: 'u2', name: 'Blocked User', isBlocked: true } }, + data: { + muteUser: { id: 'u2', name: 'Muted User', isMuted: true }, + }, }), ) }) it('unfollows the user', async () => { - await currentUser.relateTo(blockedUser, 'following') + await currentUser.relateTo(mutedUser, 'following') const queryUser = gql` query { User(id: "u2") { id - isBlocked + isMuted followedByCurrentUser } } @@ -166,32 +168,32 @@ describe('block', () => { const { query } = createTestClient(server) await expect(query({ query: queryUser })).resolves.toEqual( expect.objectContaining({ - data: { User: [{ id: 'u2', isBlocked: false, followedByCurrentUser: true }] }, + data: { User: [{ id: 'u2', isMuted: false, followedByCurrentUser: true }] }, }), ) - await blockAction({ id: 'u2' }) + await muteAction({ id: 'u2' }) await expect(query({ query: queryUser })).resolves.toEqual( expect.objectContaining({ - data: { User: [{ id: 'u2', isBlocked: true, followedByCurrentUser: false }] }, + data: { User: [{ id: 'u2', isMuted: true, followedByCurrentUser: false }] }, }), ) }) - describe('given both the current user and the to-be-blocked user write a post', () => { + describe('given both the current user and the to-be-muted user write a post', () => { let postQuery beforeEach(async () => { - const post1 = await instance.create('Post', { + const post1 = await neode.create('Post', { id: 'p12', title: 'A post written by the current user', }) - const post2 = await instance.create('Post', { + const post2 = await neode.create('Post', { id: 'p23', - title: 'A post written by the blocked user', + title: 'A post written by the muted user', }) await Promise.all([ post1.relateTo(currentUser, 'author'), - post2.relateTo(blockedUser, 'author'), + post2.relateTo(mutedUser, 'author'), ]) postQuery = gql` query { @@ -223,9 +225,9 @@ describe('block', () => { }, { id: 'p23', - title: 'A post written by the blocked user', + title: 'A post written by the muted user', author: { - name: 'Blocked User', + name: 'Muted User', id: 'u2', }, }, @@ -238,12 +240,12 @@ describe('block', () => { describe('from the perspective of the current user', () => { it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed) - describe('but if the current user blocks the other user', () => { + describe('but if the current user mutes the other user', () => { beforeEach(async () => { - await currentUser.relateTo(blockedUser, 'blocked') + await currentUser.relateTo(mutedUser, 'muted') }) - it("the blocked user's post won't show up in the newsfeed of the current user", async () => { + it("the muted user's post won't show up in the newsfeed of the current user", async () => { const { query } = createTestClient(server) await expect(query({ query: postQuery })).resolves.toEqual( expect.objectContaining({ @@ -262,29 +264,34 @@ describe('block', () => { }) }) - describe('from the perspective of the blocked user', () => { + describe('from the perspective of the muted user', () => { beforeEach(async () => { - authenticatedUser = await blockedUser.toJson() + authenticatedUser = await mutedUser.toJson() }) it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed) - describe('but if the current user blocks the other user', () => { + describe('but if the current user mutes the other user', () => { beforeEach(async () => { - await currentUser.relateTo(blockedUser, 'blocked') + await currentUser.relateTo(mutedUser, 'muted') }) - it("the current user's post won't show up in the newsfeed of the blocked user", async () => { + it("the current user's post will show up in the newsfeed of the muted user", async () => { const { query } = createTestClient(server) await expect(query({ query: postQuery })).resolves.toEqual( expect.objectContaining({ data: { - Post: [ + Post: expect.arrayContaining([ { id: 'p23', - title: 'A post written by the blocked user', - author: { name: 'Blocked User', id: 'u2' }, + title: 'A post written by the muted user', + author: { name: 'Muted User', id: 'u2' }, }, - ], + { + id: 'p12', + title: 'A post written by the current user', + author: { name: 'Current User', id: 'u1' }, + }, + ]), }, }), ) @@ -296,93 +303,103 @@ describe('block', () => { }) }) -describe('unblock', () => { - let unblockAction +describe('unmuteUser', () => { + let unmuteAction beforeEach(() => { currentUser = undefined - unblockAction = variables => { + unmuteAction = variables => { const { mutate } = createTestClient(server) - const unblockMutation = gql` + const unmuteUserMutation = gql` mutation($id: ID!) { - unblock(id: $id) { + unmuteUser(id: $id) { id name - isBlocked + isMuted } } ` - return mutate({ mutation: unblockMutation, variables }) + return mutate({ mutation: unmuteUserMutation, variables }) } }) it('throws permission error', async () => { - const result = await unblockAction({ id: 'u2' }) + const result = await unmuteAction({ id: 'u2' }) expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') }) describe('authenticated', () => { beforeEach(async () => { - currentUser = await instance.create('User', { + currentUser = await neode.create('User', { name: 'Current User', id: 'u1', }) authenticatedUser = await currentUser.toJson() }) - describe('unblock yourself', () => { + describe('unmute yourself', () => { it('returns null', async () => { - await expect(unblockAction({ id: 'u1' })).resolves.toEqual( - expect.objectContaining({ data: { unblock: null } }), + await expect(unmuteAction({ id: 'u1' })).resolves.toEqual( + expect.objectContaining({ data: { unmuteUser: null } }), ) }) }) - describe('unblock not-existing user', () => { + describe('unmute not-existing user', () => { it('returns null', async () => { - await expect(unblockAction({ id: 'lksjdflksfdj' })).resolves.toEqual( - expect.objectContaining({ data: { unblock: null } }), + await expect(unmuteAction({ id: 'lksjdflksfdj' })).resolves.toEqual( + expect.objectContaining({ data: { unmuteUser: null } }), ) }) }) describe('given another user', () => { beforeEach(async () => { - blockedUser = await instance.create('User', { - name: 'Blocked User', + mutedUser = await neode.create('User', { + name: 'Muted User', id: 'u2', }) }) - describe('unblocking a not yet blocked user', () => { + describe('unmuting a not yet muted user', () => { it('does not hurt', async () => { - await expect(unblockAction({ id: 'u2' })).resolves.toEqual( + await expect(unmuteAction({ id: 'u2' })).resolves.toEqual( expect.objectContaining({ - data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } }, + data: { + unmuteUser: { id: 'u2', name: 'Muted User', isMuted: false }, + }, }), ) }) }) - describe('given a blocked user', () => { + describe('given a muted user', () => { beforeEach(async () => { - await currentUser.relateTo(blockedUser, 'blocked') + await currentUser.relateTo(mutedUser, 'muted') }) - it('unblocks a user', async () => { - await expect(unblockAction({ id: 'u2' })).resolves.toEqual( + it('unmutes a user', async () => { + await expect(unmuteAction({ id: 'u2' })).resolves.toEqual( expect.objectContaining({ - data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } }, + data: { + unmuteUser: { id: 'u2', name: 'Muted User', isMuted: false }, + }, }), ) }) - describe('unblocking twice', () => { + describe('unmuting twice', () => { it('has no effect', async () => { - await unblockAction({ id: 'u2' }) - await expect(unblockAction({ id: 'u2' })).resolves.toEqual( + await unmuteAction({ id: 'u2' }) + await expect(unmuteAction({ id: 'u2' })).resolves.toEqual( expect.objectContaining({ - data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } }, + data: { + unmuteUser: { + id: 'u2', + name: 'Muted User', + isMuted: false, + }, + }, }), ) }) diff --git a/backend/src/schema/types/Location.gql b/backend/src/schema/types/Location.gql index e7053e345..78bc07656 100644 --- a/backend/src/schema/types/Location.gql +++ b/backend/src/schema/types/Location.gql @@ -9,6 +9,7 @@ type Location { nameES: String namePT: String namePL: String + nameRU: String type: String! lat: Float lng: Float diff --git a/backend/src/schema/types/index.js b/backend/src/schema/types/index.js index 068af64da..d49becffc 100644 --- a/backend/src/schema/types/index.js +++ b/backend/src/schema/types/index.js @@ -1,30 +1,5 @@ -import fs from 'fs' import path from 'path' -import { mergeTypes } from 'merge-graphql-schemas' - -const findGqlFiles = dir => { - var results = [] - var list = fs.readdirSync(dir) - list.forEach(file => { - file = path.join(dir, file).toString('utf-8') - var stat = fs.statSync(file) - if (stat && stat.isDirectory()) { - // Recurse into a subdirectory - results = results.concat(findGqlFiles(file)) - } else { - if (path.extname(file) === '.gql') { - // Is a gql file - results.push(file) - } - } - }) - return results -} - -const typeDefs = [] - -findGqlFiles(__dirname).forEach(file => { - typeDefs.push(fs.readFileSync(file).toString('utf-8')) -}) +import { mergeTypes, fileLoader } from 'merge-graphql-schemas' +const typeDefs = fileLoader(path.join(__dirname, './**/*.gql')) export default mergeTypes(typeDefs, { all: true }) diff --git a/backend/src/schema/types/scalar/Date.gql_ b/backend/src/schema/types/scalar/Date.gql_ deleted file mode 100644 index 7b0004ea3..000000000 --- a/backend/src/schema/types/scalar/Date.gql_ +++ /dev/null @@ -1 +0,0 @@ -scalar Date \ No newline at end of file diff --git a/backend/src/schema/types/scalar/DateTime.gql_ b/backend/src/schema/types/scalar/DateTime.gql_ deleted file mode 100644 index af973932f..000000000 --- a/backend/src/schema/types/scalar/DateTime.gql_ +++ /dev/null @@ -1 +0,0 @@ -scalar DateTime \ No newline at end of file diff --git a/backend/src/schema/types/scalar/Time.gql_ b/backend/src/schema/types/scalar/Time.gql_ deleted file mode 100644 index 53becdd66..000000000 --- a/backend/src/schema/types/scalar/Time.gql_ +++ /dev/null @@ -1 +0,0 @@ -scalar Time \ No newline at end of file diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index c641763f0..23c2ded4d 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -1,52 +1,15 @@ -type Query { - isLoggedIn: Boolean! - # Get the currently logged in User based on the given JWT Token - currentUser: User - findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]! - @cypher( - statement: """ - CALL db.index.fulltext.queryNodes('full_text_search', $query) - YIELD node as post, score - MATCH (post)<-[:WROTE]-(user:User) - WHERE score >= 0.2 - AND NOT user.deleted = true AND NOT user.disabled = true - AND NOT post.deleted = true AND NOT post.disabled = true - AND NOT user.id in COALESCE($filter.author_not.id_in, []) - RETURN post - LIMIT $limit - """ - ) -} - type Mutation { # Get a JWT Token for the given Email and password login(email: String!, password: String!): String! changePassword(oldPassword: String!, newPassword: String!): String! requestPasswordReset(email: String!): Boolean! resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean! - report(id: ID!, description: String): Report - disable(id: ID!): ID - enable(id: ID!): ID # Shout the given Type and ID shout(id: ID!, type: ShoutTypeEnum): Boolean! # Unshout the given Type and ID unshout(id: ID!, type: ShoutTypeEnum): Boolean! - # Follow the given Type and ID - follow(id: ID!, type: FollowTypeEnum): Boolean! - # Unfollow the given Type and ID - unfollow(id: ID!, type: FollowTypeEnum): Boolean! -} - -type Report { - id: ID! - submitter: User @relation(name: "REPORTED", direction: "IN") - description: String - type: String! - @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]") - createdAt: String - comment: Comment @relation(name: "REPORTED", direction: "OUT") - post: Post @relation(name: "REPORTED", direction: "OUT") - user: User @relation(name: "REPORTED", direction: "OUT") + followUser(id: ID!): User + unfollowUser(id: ID!): User } enum Deletable { @@ -57,9 +20,6 @@ enum Deletable { enum ShoutTypeEnum { Post } -enum FollowTypeEnum { - User -} type Reward { id: ID! diff --git a/backend/src/schema/types/type/Badge.gql b/backend/src/schema/types/type/Badge.gql index 99015a518..dff1de89a 100644 --- a/backend/src/schema/types/type/Badge.gql +++ b/backend/src/schema/types/type/Badge.gql @@ -3,8 +3,6 @@ type Badge { type: BadgeType! status: BadgeStatus! icon: String! - #createdAt: DateTime - #updatedAt: DateTime createdAt: String updatedAt: String diff --git a/backend/src/schema/types/type/Category.gql b/backend/src/schema/types/type/Category.gql index 9ee628d76..39efeff9d 100644 --- a/backend/src/schema/types/type/Category.gql +++ b/backend/src/schema/types/type/Category.gql @@ -1,13 +1,41 @@ +enum _CategoryOrdering { + id_asc + id_desc + name_asc + name_desc + slug_asc + slug_desc + icon_asc + icon_desc + createdAt_asc + createdAt_desc + updatedAt_asc + updatedAt_desc + postCount_asc + postCount_desc +} + type Category { id: ID! name: String! slug: String icon: String! - #createdAt: DateTime - #updatedAt: DateTime createdAt: String updatedAt: String - posts: [Post]! @relation(name: "CATEGORIZED", direction: "IN") postCount: Int! @cypher(statement: "MATCH (this)<-[:CATEGORIZED]-(r:Post) RETURN COUNT(r)") } + +type Query { + Category( + id: ID + name: String + slug: String + icon: String + createdAt: String + updatedAt: String + first: Int + offset: Int + orderBy: [_CategoryOrdering] + ): [Category] +} diff --git a/backend/src/schema/types/type/Comment.gql b/backend/src/schema/types/type/Comment.gql index 1ccf617ef..cf53df41d 100644 --- a/backend/src/schema/types/type/Comment.gql +++ b/backend/src/schema/types/type/Comment.gql @@ -1,3 +1,41 @@ +enum _CommentOrdering { + id_asc + id_desc + content_asc + content_desc + createdAt_asc + createdAt_desc + updatedAt_asc + updatedAt_desc +} + +input _CommentFilter { + AND: [_CommentFilter!] + OR: [_CommentFilter!] + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + author: _UserFilter + author_not: _UserFilter + author_in: [_UserFilter!] + author_not_in: [_UserFilter!] + content: String + content_not: String + content_in: [String!] + content_not_in: [String!] + content_contains: String + content_not_contains: String + content_starts_with: String + content_not_starts_with: String + content_ends_with: String + content_not_ends_with: String + post: _PostFilter + post_not: _PostFilter + post_in: [_PostFilter!] + post_not_in: [_PostFilter!] +} + type Comment { id: ID! activityId: String @@ -9,7 +47,19 @@ type Comment { updatedAt: String deleted: Boolean disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") +} + +type Query { + Comment( + id: ID + content: String + createdAt: String + updatedAt: String + first: Int + offset: Int + orderBy: [_CommentOrdering] + filter: _CommentFilter + ): [Comment] } type Mutation { diff --git a/backend/src/schema/types/type/Donations.gql b/backend/src/schema/types/type/Donations.gql new file mode 100644 index 000000000..39cfe9b71 --- /dev/null +++ b/backend/src/schema/types/type/Donations.gql @@ -0,0 +1,15 @@ +type Donations { + id: ID! + goal: Int! + progress: Int! + createdAt: String + updatedAt: String +} + +type Query { + Donations: [Donations] +} + +type Mutation { + UpdateDonations(goal: Int, progress: Int): Donations +} \ No newline at end of file diff --git a/backend/src/schema/types/type/EMOTED.gql b/backend/src/schema/types/type/EMOTED.gql index ee1576517..7a8b47b73 100644 --- a/backend/src/schema/types/type/EMOTED.gql +++ b/backend/src/schema/types/type/EMOTED.gql @@ -3,8 +3,6 @@ type EMOTED @relation(name: "EMOTED") { to: Post emotion: Emotion - # createdAt: DateTime - # updatedAt: DateTime createdAt: String updatedAt: String } diff --git a/backend/src/schema/types/type/EmailAddress.gql b/backend/src/schema/types/type/EmailAddress.gql index 4bf8ff724..99e309602 100644 --- a/backend/src/schema/types/type/EmailAddress.gql +++ b/backend/src/schema/types/type/EmailAddress.gql @@ -19,5 +19,11 @@ type Mutation { locationName: String about: String termsAndConditionsAgreedVersion: String! + locale: String ): User + AddEmailAddress(email: String!): EmailAddress + VerifyEmailAddress( + nonce: String! + email: String! + ): EmailAddress } diff --git a/backend/src/schema/types/type/FILED.gql b/backend/src/schema/types/type/FILED.gql new file mode 100644 index 000000000..cdce62116 --- /dev/null +++ b/backend/src/schema/types/type/FILED.gql @@ -0,0 +1,18 @@ +type FILED { + createdAt: String! + reasonCategory: ReasonCategory! + reasonDescription: String! + submitter: User +} + +# this list equals the strings of an array in file "webapp/constants/modals.js" +enum ReasonCategory { + other + discrimination_etc + pornographic_content_links + glorific_trivia_of_cruel_inhuman_acts + doxing + intentional_intimidation_stalking_persecution + advert_products_services_commercial + criminal_behavior_violation_german_law +} diff --git a/backend/src/schema/types/type/InvitationCode.gql b/backend/src/schema/types/type/InvitationCode.gql deleted file mode 100644 index 044967286..000000000 --- a/backend/src/schema/types/type/InvitationCode.gql +++ /dev/null @@ -1,13 +0,0 @@ -type InvitationCode { - id: ID! - token: String - generatedBy: User @relation(name: "GENERATED", direction: "IN") - - #createdAt: DateTime - #usedAt: DateTime - createdAt: String -} - -type Mutation { - CreateInvitationCode: InvitationCode -} diff --git a/backend/src/schema/types/type/NOTIFIED.gql b/backend/src/schema/types/type/NOTIFIED.gql index 5082b5f7f..af91460f7 100644 --- a/backend/src/schema/types/type/NOTIFIED.gql +++ b/backend/src/schema/types/type/NOTIFIED.gql @@ -1,8 +1,9 @@ type NOTIFIED { + id: ID! from: NotificationSource to: User - createdAt: String - updatedAt: String + createdAt: String! + updatedAt: String! read: Boolean reason: NotificationReason } @@ -23,9 +24,9 @@ enum NotificationReason { } type Query { - notifications(read: Boolean, orderBy: NotificationOrdering): [NOTIFIED] + notifications(read: Boolean, orderBy: NotificationOrdering, first: Int, offset: Int): [NOTIFIED] } - + type Mutation { markAsRead(id: ID!): NOTIFIED } diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 5b11757d3..71fcb9605 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -1,21 +1,138 @@ +input _PostFilter { + AND: [_PostFilter!] + OR: [_PostFilter!] + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + author: _UserFilter + author_not: _UserFilter + author_in: [_UserFilter!] + author_not_in: [_UserFilter!] + title: String + title_not: String + title_in: [String!] + title_not_in: [String!] + title_contains: String + title_not_contains: String + title_starts_with: String + title_not_starts_with: String + title_ends_with: String + title_not_ends_with: String + shoutedBy_some: _UserFilter + slug: String + slug_not: String + slug_in: [String!] + slug_not_in: [String!] + slug_contains: String + slug_not_contains: String + slug_starts_with: String + slug_not_starts_with: String + slug_ends_with: String + slug_not_ends_with: String + content: String + content_not: String + content_in: [String!] + content_not_in: [String!] + content_contains: String + content_not_contains: String + content_starts_with: String + content_not_starts_with: String + content_ends_with: String + content_not_ends_with: String + image: String + visibility: Visibility + visibility_not: Visibility + visibility_in: [Visibility!] + visibility_not_in: [Visibility!] + language: String + language_not: String + language_in: [String!] + language_not_in: [String!] + pinned: Boolean # required for `maintainPinnedPost` + tags: _TagFilter + tags_not: _TagFilter + tags_in: [_TagFilter!] + tags_not_in: [_TagFilter!] + tags_some: _TagFilter + tags_none: _TagFilter + tags_single: _TagFilter + tags_every: _TagFilter + categories: _CategoryFilter + categories_not: _CategoryFilter + categories_in: [_CategoryFilter!] + categories_not_in: [_CategoryFilter!] + categories_some: _CategoryFilter + categories_none: _CategoryFilter + categories_single: _CategoryFilter + categories_every: _CategoryFilter + comments: _CommentFilter + comments_not: _CommentFilter + comments_in: [_CommentFilter!] + comments_not_in: [_CommentFilter!] + comments_some: _CommentFilter + comments_none: _CommentFilter + comments_single: _CommentFilter + comments_every: _CommentFilter + emotions: _PostEMOTEDFilter + emotions_not: _PostEMOTEDFilter + emotions_in: [_PostEMOTEDFilter!] + emotions_not_in: [_PostEMOTEDFilter!] + emotions_some: _PostEMOTEDFilter + emotions_none: _PostEMOTEDFilter + emotions_single: _PostEMOTEDFilter + emotions_every: _PostEMOTEDFilter + imageBlurred: Boolean +} + +enum _PostOrdering { + id_asc + id_desc + title_asc + title_desc + slug_asc + slug_desc + content_asc + content_desc + image_asc + image_desc + visibility_asc + visibility_desc + createdAt_asc + createdAt_desc + updatedAt_asc + updatedAt_desc + language_asc + language_desc + pinned_asc + pinned_desc +} + + type Post { id: ID! activityId: String objectId: String author: User @relation(name: "WROTE", direction: "IN") title: String! - slug: String + slug: String! content: String! contentExcerpt: String image: String imageUpload: Upload + imageAspectRatio: Float visibility: Visibility deleted: Boolean disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") + pinned: Boolean createdAt: String updatedAt: String language: String + imageBlurred: Boolean + pinnedAt: String @cypher( + statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt" + ) + pinnedBy: User @relation(name:"PINNED", direction: "IN") relatedContributions: [Post]! @cypher( statement: """ @@ -25,7 +142,6 @@ type Post { LIMIT 10 """ ) - tags: [Tag]! @relation(name: "TAGGED", direction: "OUT") categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") @@ -68,6 +184,8 @@ type Mutation { language: String categoryIds: [ID] contentExcerpt: String + imageBlurred: Boolean + imageAspectRatio: Float ): Post UpdatePost( id: ID! @@ -80,13 +198,50 @@ type Mutation { visibility: Visibility language: String categoryIds: [ID] + imageBlurred: Boolean + imageAspectRatio: Float ): Post DeletePost(id: ID!): Post AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED + pinPost(id: ID!): Post + unpinPost(id: ID!): Post } type Query { + Post( + id: ID + title: String + slug: String + content: String + image: String + visibility: Visibility + pinned: Boolean + createdAt: String + updatedAt: String + language: String + imageBlurred: Boolean + first: Int + offset: Int + orderBy: [_PostOrdering] + filter: _PostFilter + imageAspectRatio: Float + ): [Post] PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int! PostsEmotionsByCurrentUser(postId: ID!): [String] + profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post] + findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]! + @cypher( + statement: """ + CALL db.index.fulltext.queryNodes('post_fulltext_search', $query) + YIELD node as post, score + MATCH (post)<-[:WROTE]-(user:User) + WHERE score >= 0.2 + AND NOT user.deleted = true AND NOT user.disabled = true + AND NOT post.deleted = true AND NOT post.disabled = true + AND NOT user.id in COALESCE($filter.author_not.id_in, []) + RETURN post + LIMIT $limit + """ + ) } diff --git a/backend/src/schema/types/type/REVIEWED.gql b/backend/src/schema/types/type/REVIEWED.gql new file mode 100644 index 000000000..aea005abe --- /dev/null +++ b/backend/src/schema/types/type/REVIEWED.gql @@ -0,0 +1,15 @@ +type REVIEWED { + createdAt: String! + updatedAt: String! + disable: Boolean! + closed: Boolean! + report: Report + # @cypher(statement: "MATCH (report:Report)<-[this:REVIEWED]-(:User) RETURN report") + moderator: User + resource: ReviewedResource +} +union ReviewedResource = User | Post | Comment + +type Mutation { + review(resourceId: ID!, disable: Boolean, closed: Boolean): REVIEWED +} diff --git a/backend/src/schema/types/type/Report.gql b/backend/src/schema/types/type/Report.gql new file mode 100644 index 000000000..ad0015d01 --- /dev/null +++ b/backend/src/schema/types/type/Report.gql @@ -0,0 +1,30 @@ +type Report { + id: ID! + createdAt: String! + updatedAt: String! + rule: ReportRule! + disable: Boolean! + closed: Boolean! + filed: [FILED] + reviewed: [REVIEWED]! + resource: ReportedResource +} + +union ReportedResource = User | Post | Comment + +enum ReportRule { + latestReviewUpdatedAtRules +} + +type Mutation { + fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): Report +} + +type Query { + reports(orderBy: ReportOrdering, first: Int, offset: Int, reviewed: Boolean, closed: Boolean): [Report] +} + +enum ReportOrdering { + createdAt_asc + createdAt_desc +} diff --git a/backend/src/schema/types/type/Search.gql b/backend/src/schema/types/type/Search.gql new file mode 100644 index 000000000..2c22fa61f --- /dev/null +++ b/backend/src/schema/types/type/Search.gql @@ -0,0 +1,5 @@ +union SearchResult = Post | User + +type Query { + findResources(query: String!, limit: Int = 5): [SearchResult]! +} diff --git a/backend/src/schema/types/type/Tag.gql b/backend/src/schema/types/type/Tag.gql index 84c6ee7e7..41a772e4d 100644 --- a/backend/src/schema/types/type/Tag.gql +++ b/backend/src/schema/types/type/Tag.gql @@ -1,3 +1,20 @@ +input _TagFilter { + AND: [_TagFilter!] + OR: [_TagFilter!] + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + taggedPosts: _PostFilter + taggedPosts_not: _PostFilter + taggedPosts_in: [_PostFilter!] + taggedPosts_not_in: [_PostFilter!] + taggedPosts_some: _PostFilter + taggedPosts_none: _PostFilter + taggedPosts_single: _PostFilter + taggedPosts_every: _PostFilter +} + type Tag { id: ID! taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN") @@ -6,3 +23,22 @@ type Tag { deleted: Boolean disabled: Boolean } + +enum _TagOrdering { + id_asc + id_desc + taggedCount_asc + taggedCount_desc + taggedCountUnique_asc + taggedCountUnique_desc +} + +type Query { + Tag( + id: ID + first: Int + offset: Int + orderBy: [_TagOrdering] + filter: _TagFilter + ): [Tag] +} diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index f1c38b8d6..4eb04a638 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -1,176 +1,212 @@ +enum _UserOrdering { + id_asc + id_desc + name_asc + name_desc + slug_asc + slug_desc + avatar_asc + avatar_desc + coverImg_asc + coverImg_desc + role_asc + role_desc + locationName_asc + locationName_desc + about_asc + about_desc + createdAt_asc + createdAt_desc + updatedAt_asc + updatedAt_desc + locale_asc + locale_desc +} + type User { - id: ID! - actorId: String - name: String - email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email") - slug: String! - avatar: String - coverImg: String - deleted: Boolean - disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") - role: UserGroup! - publicKey: String - invitedBy: User @relation(name: "INVITED", direction: "IN") - invited: [User] @relation(name: "INVITED", direction: "OUT") + id: ID! + actorId: String + name: String + email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email") + slug: String! + avatar: String + coverImg: String + deleted: Boolean + disabled: Boolean + role: UserGroup! + publicKey: String + invitedBy: User @relation(name: "INVITED", direction: "IN") + invited: [User] @relation(name: "INVITED", direction: "OUT") - location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l") - locationName: String - about: String - socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") + location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l") + locationName: String + about: String + socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") - # createdAt: DateTime - # updatedAt: DateTime - createdAt: String - updatedAt: String + createdAt: String + updatedAt: String - termsAndConditionsAgreedVersion: String - termsAndConditionsAgreedAt: String + termsAndConditionsAgreedVersion: String + termsAndConditionsAgreedAt: String - friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") - friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)") + allowEmbedIframes: Boolean + showShoutsPublicly: Boolean + locale: String + friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") + friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)") - following: [User]! @relation(name: "FOLLOWS", direction: "OUT") - followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)") + following: [User]! @relation(name: "FOLLOWS", direction: "OUT") + followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)") - followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") - followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)") + followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") + followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)") - # Is the currently logged in user following that user? - followedByCurrentUser: Boolean! @cypher( - statement: """ - MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) - isBlocked: Boolean! @cypher( - statement: """ - MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) + # Is the currently logged in user following that user? + followedByCurrentUser: Boolean! @cypher( + statement: """ + MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId}) + RETURN COUNT(u) >= 1 + """ + ) + isBlocked: Boolean! @cypher( + statement: """ + MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId}) + RETURN COUNT(u) >= 1 + """ + ) - # contributions: [WrittenPost]! - # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! - # @cypher( - # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" - # ) - contributions: [Post]! @relation(name: "WROTE", direction: "OUT") - contributionsCount: Int! @cypher( - statement: """ - MATCH (this)-[: WROTE]->(r: Post) - WHERE NOT r.deleted = true AND NOT r.disabled = true - RETURN COUNT(r) - """ - ) + isMuted: Boolean! @cypher( + statement: """ + MATCH (this)<-[:MUTED]-(user:User { id: $cypherParams.currentUserId}) + RETURN COUNT(user) >= 1 + """ + ) - comments: [Comment]! @relation(name: "WROTE", direction: "OUT") - commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") + # contributions: [WrittenPost]! + # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! + # @cypher( + # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" + # ) + contributions: [Post]! @relation(name: "WROTE", direction: "OUT") + contributionsCount: Int! @cypher( + statement: """ + MATCH (this)-[: WROTE]->(r: Post) + WHERE NOT r.deleted = true AND NOT r.disabled = true + RETURN COUNT(r) + """ + ) - shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") - shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") + comments: [Comment]! @relation(name: "WROTE", direction: "OUT") + commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") - categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") + shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") + shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") - badges: [Badge]! @relation(name: "REWARDED", direction: "IN") - badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)") + categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") - emotions: [EMOTED] + badges: [Badge]! @relation(name: "REWARDED", direction: "IN") + badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)") + + emotions: [EMOTED] } input _UserFilter { - AND: [_UserFilter!] - OR: [_UserFilter!] - name_contains: String - about_contains: String - slug_contains: String - id: ID - id_not: ID - id_in: [ID!] - id_not_in: [ID!] - id_contains: ID - id_not_contains: ID - id_starts_with: ID - id_not_starts_with: ID - id_ends_with: ID - id_not_ends_with: ID - friends: _UserFilter - friends_not: _UserFilter - friends_in: [_UserFilter!] - friends_not_in: [_UserFilter!] - friends_some: _UserFilter - friends_none: _UserFilter - friends_single: _UserFilter - friends_every: _UserFilter - following: _UserFilter - following_not: _UserFilter - following_in: [_UserFilter!] - following_not_in: [_UserFilter!] - following_some: _UserFilter - following_none: _UserFilter - following_single: _UserFilter - following_every: _UserFilter - followedBy: _UserFilter - followedBy_not: _UserFilter - followedBy_in: [_UserFilter!] - followedBy_not_in: [_UserFilter!] - followedBy_some: _UserFilter - followedBy_none: _UserFilter - followedBy_single: _UserFilter - followedBy_every: _UserFilter + AND: [_UserFilter!] + OR: [_UserFilter!] + name_contains: String + about_contains: String + slug_contains: String + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + friends: _UserFilter + friends_not: _UserFilter + friends_in: [_UserFilter!] + friends_not_in: [_UserFilter!] + friends_some: _UserFilter + friends_none: _UserFilter + friends_single: _UserFilter + friends_every: _UserFilter + following: _UserFilter + following_not: _UserFilter + following_in: [_UserFilter!] + following_not_in: [_UserFilter!] + following_some: _UserFilter + following_none: _UserFilter + following_single: _UserFilter + following_every: _UserFilter + followedBy: _UserFilter + followedBy_not: _UserFilter + followedBy_in: [_UserFilter!] + followedBy_not_in: [_UserFilter!] + followedBy_some: _UserFilter + followedBy_none: _UserFilter + followedBy_single: _UserFilter + followedBy_every: _UserFilter + role_in: [UserGroup!] } type Query { - User( - id: ID - email: String - actorId: String - name: String - slug: String - avatar: String - coverImg: String - role: UserGroup - locationName: String - about: String - createdAt: String - updatedAt: String - friendsCount: Int - followingCount: Int - followedByCount: Int - followedByCurrentUser: Boolean - contributionsCount: Int - commentedCount: Int - shoutedCount: Int - badgesCount: Int - first: Int - offset: Int - orderBy: [_UserOrdering] - filter: _UserFilter - ): [User] + User( + id: ID + email: String # admins need to search for a user sometimes + name: String + slug: String + avatar: String + coverImg: String + role: UserGroup + locationName: String + about: String + createdAt: String + updatedAt: String + first: Int + offset: Int + orderBy: [_UserOrdering] + filter: _UserFilter + ): [User] - blockedUsers: [User] - currentUser: User + mutedUsers: [User] + blockedUsers: [User] + isLoggedIn: Boolean! + currentUser: User + findUsers(query: String!,limit: Int = 10, filter: _UserFilter): [User]! + @cypher( + statement: """ + CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) + YIELD node as post, score + MATCH (user) + WHERE score >= 0.2 + AND NOT user.deleted = true AND NOT user.disabled = true + RETURN user + LIMIT $limit + """ + ) } type Mutation { - UpdateUser ( - id: ID! - name: String - email: String - slug: String - avatar: String - coverImg: String - avatarUpload: Upload - locationName: String - about: String - termsAndConditionsAgreedVersion: String - termsAndConditionsAgreedAt: String - ): User + UpdateUser ( + id: ID! + name: String + email: String + slug: String + avatar: String + coverImg: String + avatarUpload: Upload + locationName: String + about: String + termsAndConditionsAgreedVersion: String + termsAndConditionsAgreedAt: String + allowEmbedIframes: Boolean + showShoutsPublicly: Boolean + locale: String + ): User - DeleteUser(id: ID!, resource: [Deletable]): User + DeleteUser(id: ID!, resource: [Deletable]): User - - block(id: ID!): User - unblock(id: ID!): User + muteUser(id: ID!): User + unmuteUser(id: ID!): User + block(id: ID!): User + unblock(id: ID!): User } diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js deleted file mode 100644 index c0a684715..000000000 --- a/backend/src/seed/factories/index.js +++ /dev/null @@ -1,173 +0,0 @@ -import { GraphQLClient, request } from 'graphql-request' -import { getDriver, neode } from '../../bootstrap/neo4j' -import createBadge from './badges.js' -import createUser from './users.js' -import createPost from './posts.js' -import createComment from './comments.js' -import createCategory from './categories.js' -import createTag from './tags.js' -import createSocialMedia from './socialMedia.js' -import createLocation from './locations.js' -import createEmailAddress from './emailAddresses.js' - -export const seedServerHost = 'http://127.0.0.1:4001' - -const authenticatedHeaders = async ({ email, password }, host) => { - const mutation = ` - mutation { - login(email:"${email}", password:"${password}") - }` - const response = await request(host, mutation) - return { - authorization: `Bearer ${response.login}`, - } -} -const factories = { - Badge: createBadge, - User: createUser, - Post: createPost, - Comment: createComment, - Category: createCategory, - Tag: createTag, - SocialMedia: createSocialMedia, - Location: createLocation, - EmailAddress: createEmailAddress, -} - -export const cleanDatabase = async (options = {}) => { - const { driver = getDriver() } = options - const session = driver.session() - const cypher = 'MATCH (n) DETACH DELETE n' - try { - return await session.run(cypher) - } finally { - session.close() - } -} - -export default function Factory(options = {}) { - const { - seedServerHost = 'http://127.0.0.1:4001', - neo4jDriver = getDriver(), - neodeInstance = neode(), - } = options - - const graphQLClient = new GraphQLClient(seedServerHost) - - const result = { - neo4jDriver, - seedServerHost, - graphQLClient, - factories, - lastResponse: null, - neodeInstance, - async authenticateAs({ email, password }) { - const headers = await authenticatedHeaders( - { - email, - password, - }, - seedServerHost, - ) - this.lastResponse = headers - this.graphQLClient = new GraphQLClient(seedServerHost, { - headers, - }) - return this - }, - async create(node, args = {}) { - const { factory, mutation, variables } = this.factories[node](args) - if (factory) { - this.lastResponse = await factory({ - args, - neodeInstance, - factoryInstance: this, - }) - return this.lastResponse - } else { - this.lastResponse = await this.graphQLClient.request(mutation, variables) - } - return this - }, - async relate(node, relationship, { from, to }) { - const mutation = ` - mutation { - Add${node}${relationship}( - from: { id: "${from}" }, - to: { id: "${to}" } - ) { from { id } } - } - ` - this.lastResponse = await this.graphQLClient.request(mutation) - return this - }, - async mutate(mutation, variables) { - this.lastResponse = await this.graphQLClient.request(mutation, variables) - return this - }, - async shout(properties) { - const { id, type } = properties - const mutation = ` - mutation { - shout( - id: "${id}", - type: ${type} - ) - } - ` - this.lastResponse = await this.graphQLClient.request(mutation) - return this - }, - async follow(properties) { - const { id, type } = properties - const mutation = ` - mutation { - follow( - id: "${id}", - type: ${type} - ) - } - ` - this.lastResponse = await this.graphQLClient.request(mutation) - return this - }, - async invite({ email }) { - const mutation = ` mutation($email: String!) { invite( email: $email) } ` - this.lastResponse = await this.graphQLClient.request(mutation, { - email, - }) - return this - }, - async cleanDatabase() { - this.lastResponse = await cleanDatabase({ - driver: this.neo4jDriver, - }) - return this - }, - async emote({ to, data }) { - const mutation = ` - mutation { - AddPostEmotions( - to: { id: "${to}" }, - data: { emotion: ${data} } - ) { - from { id } - to { id } - emotion - } - } - ` - this.lastResponse = await this.graphQLClient.request(mutation) - return this - }, - } - result.authenticateAs.bind(result) - result.create.bind(result) - result.relate.bind(result) - result.mutate.bind(result) - result.shout.bind(result) - result.follow.bind(result) - result.invite.bind(result) - result.cleanDatabase.bind(result) - return result -} diff --git a/backend/src/seed/seed-helpers.js b/backend/src/seed/seed-helpers.js deleted file mode 100644 index 913ca1d54..000000000 --- a/backend/src/seed/seed-helpers.js +++ /dev/null @@ -1,134 +0,0 @@ -const _ = require('lodash') -const faker = require('faker') -const unsplashTopics = [ - 'love', - 'family', - 'spring', - 'business', - 'nature', - 'travel', - 'happy', - 'landscape', - 'health', - 'friends', - 'computer', - 'autumn', - 'space', - 'animal', - 'smile', - 'face', - 'people', - 'portrait', - 'amazing', -] -let unsplashTopicsTmp = [] - -const ngoLogos = [ - 'http://www.fetchlogos.com/wp-content/uploads/2015/11/Girl-Scouts-Of-The-Usa-Logo.jpg', - 'http://logos.textgiraffe.com/logos/logo-name/Ngo-designstyle-friday-m.png', - 'http://seeklogo.com/images/N/ngo-logo-BD53A3E024-seeklogo.com.png', - 'https://dcassetcdn.com/design_img/10133/25833/25833_303600_10133_image.jpg', - 'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/20.jpg', - 'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/33.jpg', - null, -] - -const difficulties = ['easy', 'medium', 'hard'] - -export default { - randomItem: (items, filter) => { - const ids = filter - ? Object.keys(items).filter(id => { - return filter(items[id]) - }) - : _.keys(items) - const randomIds = _.shuffle(ids) - return items[randomIds.pop()] - }, - randomItems: (items, key = 'id', min = 1, max = 1) => { - const randomIds = _.shuffle(_.keys(items)) - const res = [] - - const count = _.random(min, max) - - for (let i = 0; i < count; i++) { - let r = items[randomIds.pop()][key] - if (key === 'id') { - r = r.toString() - } - res.push(r) - } - return res - }, - random: items => { - return _.shuffle(items).pop() - }, - randomDifficulty: () => { - return _.shuffle(difficulties).pop() - }, - randomLogo: () => { - return _.shuffle(ngoLogos).pop() - }, - randomUnsplashUrl: () => { - if (Math.random() < 0.6) { - // do not attach images in 60 percent of the cases (faster seeding) - return - } - if (unsplashTopicsTmp.length < 2) { - unsplashTopicsTmp = _.shuffle(unsplashTopics) - } - return ( - 'https://source.unsplash.com/daily?' + unsplashTopicsTmp.pop() + ',' + unsplashTopicsTmp.pop() - ) - }, - randomCategories: (seederstore, allowEmpty = false) => { - let count = Math.round(Math.random() * 3) - if (allowEmpty === false && count === 0) { - count = 1 - } - const categorieIds = _.shuffle(_.keys(seederstore.categories)) - const ids = [] - for (let i = 0; i < count; i++) { - ids.push(categorieIds.pop()) - } - return ids - }, - randomAddresses: () => { - const count = Math.round(Math.random() * 3) - const addresses = [] - for (let i = 0; i < count; i++) { - addresses.push({ - city: faker.address.city(), - zipCode: faker.address.zipCode(), - street: faker.address.streetAddress(), - country: faker.address.countryCode(), - lat: 54.032726 - Math.random() * 10, - lng: 6.558838 + Math.random() * 10, - }) - } - return addresses - }, - /** - * Get array of ids from the given seederstore items after mapping them by the key in the values - * - * @param items items from the seederstore - * @param values values for which you need the ids - * @param key the field key that is represented in the values (slug, name, etc.) - */ - mapIdsByKey: (items, values, key) => { - const res = [] - values.forEach(value => { - res.push(_.find(items, [key, value]).id.toString()) - }) - return res - }, - genInviteCode: () => { - const chars = '23456789abcdefghkmnpqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ' - let code = '' - for (let i = 0; i < 8; i++) { - const n = _.random(0, chars.length - 1) - code += chars.substr(n, 1) - } - return code - }, -} diff --git a/backend/src/server.js b/backend/src/server.js index 70eae86f1..02e166b71 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -1,19 +1,12 @@ import express from 'express' import helmet from 'helmet' import { ApolloServer } from 'apollo-server-express' -import CONFIG, { requiredConfigs } from './config' +import CONFIG from './config' import middleware from './middleware' -import { neode as getNeode, getDriver } from './bootstrap/neo4j' +import { getNeode, getDriver } from './db/neo4j' import decode from './jwt/decode' import schema from './schema' - -// check required configs and throw error -// TODO check this directly in config file - currently not possible due to testsetup -Object.entries(requiredConfigs).map(entry => { - if (!entry[1]) { - throw new Error(`ERROR: "${entry[0]}" env variable is missing.`) - } -}) +import webfinger from './activitypub/routes/webfinger' const driver = getDriver() const neode = getNeode() @@ -37,11 +30,20 @@ const createServer = options => { schema: middleware(schema), debug: !!CONFIG.DEBUG, tracing: !!CONFIG.DEBUG, + formatError: error => { + if (error.message === 'ERROR_VALIDATION') { + return new Error(error.originalError.details.map(d => d.message)) + } + return error + }, } const server = new ApolloServer(Object.assign({}, defaults, options)) const app = express() + + app.set('driver', driver) app.use(helmet()) + app.use('/.well-known/', webfinger()) app.use(express.static('public')) server.applyMiddleware({ app, path: '/' }) diff --git a/backend/test/features/support/steps.js b/backend/test/features/support/steps.js index fdbca289c..70802f4e2 100644 --- a/backend/test/features/support/steps.js +++ b/backend/test/features/support/steps.js @@ -3,8 +3,7 @@ import { Given, When, Then, AfterAll } from 'cucumber' import { expect } from 'chai' // import { client } from '../../../src/activitypub/apollo-client' import { GraphQLClient } from 'graphql-request' -import Factory from '../../../src/seed/factories' -import { host } from '../../../src/jest/helpers' +import Factory from '../../../src/factories' const debug = require('debug')('ea:test:steps') const factory = Factory() diff --git a/backend/test/features/webfinger.feature b/backend/test/features/webfinger.feature index 72062839a..cbca5ac10 100644 --- a/backend/test/features/webfinger.feature +++ b/backend/test/features/webfinger.feature @@ -9,32 +9,6 @@ Feature: Webfinger discovery | Slug | | peter-lustiger | - Scenario: Search - When I send a GET request to "/.well-known/webfinger?resource=acct:peter-lustiger@localhost" - Then I receive the following json: - """ - { - "subject": "acct:peter-lustiger@localhost:4123", - "links": [ - { - "rel": "self", - "type": "application/activity+json", - "href": "http://localhost:4123/activitypub/users/peter-lustiger" - } - ] - } - """ - And I expect the Content-Type to be "application/jrd+json; charset=utf-8" - - Scenario: User does not exist - When I send a GET request to "/.well-known/webfinger?resource=acct:nonexisting@localhost" - Then I receive the following json: - """ - { - "error": "No record found for nonexisting@localhost." - } - """ - Scenario: Receiving an actor object When I send a GET request to "/activitypub/users/peter-lustiger" Then I receive the following json: diff --git a/backend/yarn.lock b/backend/yarn.lock index e78b7eadd..8f4cfc9a2 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,683 +2,772 @@ # yarn lockfile v1 -"@apollographql/apollo-tools@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.4.0.tgz#8a1a0ab7a0bb12ccc03b72e4a104cfa5d969fd5f" - integrity sha512-7wEO+S+zgz/wVe3ilFQqICufRBYYDSNUkd1V03JWvXuSydbYq2SM5EgvWmFF+04iadt+aQ0XCCsRzCzRPQODfQ== +"@apollo/protobufjs@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.0.3.tgz#02c655aedd4ba7c7f64cbc3d2b1dd9a000a391ba" + integrity sha512-gqeT810Ect9WIqsrgfUvr+ljSB5m1PyBae9HGdrRyQ3HjHjTcjVvxpsMYXlUk4rUHnrfUqyoGvLSy2yLlRGEOw== dependencies: - apollo-env "0.5.1" + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + +"@apollographql/apollo-tools@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.4.3.tgz#938a50aea0935973a75155a73417f2f6fc7ac2ef" + integrity sha512-CtC1bmohB1owdGMT2ZZKacI94LcPAZDN2WvCe+4ZXT5d7xO5PHOAb70EP/LcFbvnS8QI+pkYRSCGFQnUcv9efg== + dependencies: + apollo-env "^0.6.1" "@apollographql/graphql-playground-html@1.6.24": version "1.6.24" resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc" integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ== -"@babel/cli@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.0.tgz#1470a04394eaf37862989ea4912adf440fa6ff8d" - integrity sha512-1CTDyGUjQqW3Mz4gfKZ04KGOckyyaNmKneAMlABPS+ZyuxWv3FrVEVz7Ag08kNIztVx8VaJ8YgvYLSNlMKAT5Q== +"@babel/cli@~7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.3.tgz#121beb7c273e0521eb2feeb3883a2b7435d12328" + integrity sha512-K2UXPZCKMv7KwWy9Bl4sa6+jTNP7JyDiHKzoOiUUygaEDbC60vaargZDnO9oFMvlq8pIKOOyUUgeMYrsaN9djA== dependencies: - commander "^2.8.1" + commander "^4.0.1" convert-source-map "^1.1.0" fs-readdir-recursive "^1.1.0" glob "^7.0.0" lodash "^4.17.13" - mkdirp "^0.5.1" - output-file-sync "^2.0.0" + make-dir "^2.1.0" slash "^2.0.0" source-map "^0.5.0" optionalDependencies: chokidar "^2.1.8" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.8.3" -"@babel/core@^7.1.0", "@babel/core@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.0.tgz#9b00f73554edd67bebc86df8303ef678be3d7b48" - integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw== +"@babel/compat-data@^7.8.0", "@babel/compat-data@^7.8.1": + version "7.8.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.1.tgz#fc0bbbb7991e4fb2b47e168e60f2cc2c41680be9" + integrity sha512-Z+6ZOXvyOWYxJ50BwxzdhRnRsGST8Y3jaZgxYig575lTjVSs3KtJnmESwZegg6e2Dn0td1eDhoWlp1wI4BTCPw== dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helpers" "^7.6.0" - "@babel/parser" "^7.6.0" - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" - convert-source-map "^1.1.0" + browserslist "^4.8.2" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@~7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.3.tgz#30b0ebb4dd1585de6923a0b4d179e0b9f5d82941" + integrity sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.3" + "@babel/helpers" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + convert-source-map "^1.7.0" debug "^4.1.0" + gensync "^1.0.0-beta.1" json5 "^2.1.0" lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56" - integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA== +"@babel/generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" + integrity sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug== dependencies: - "@babel/types" "^7.6.0" + "@babel/types" "^7.8.3" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/helper-annotate-as-pure@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" - integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" - integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== dependencies: - "@babel/helper-explode-assignable-expression" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-call-delegate@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43" - integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ== +"@babel/helper-call-delegate@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz#de82619898aa605d409c42be6ffb8d7204579692" + integrity sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A== dependencies: - "@babel/helper-hoist-variables" "^7.4.4" - "@babel/traverse" "^7.4.4" - "@babel/types" "^7.4.4" + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-define-map@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369" - integrity sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg== +"@babel/helper-compilation-targets@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.3.tgz#2deedc816fd41dca7355ef39fd40c9ea69f0719a" + integrity sha512-JLylPCsFjhLN+6uBSSh3iYdxKdeO9MNmoY96PE/99d8kyBFaXLORtAVhqN6iHa+wtPeqxKLghDOZry0+Aiw9Tw== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.5.5" + "@babel/compat-data" "^7.8.1" + browserslist "^4.8.2" + invariant "^2.2.4" + levenary "^1.1.0" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79" + integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q== + dependencies: + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.6.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" - integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== dependencies: - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-hoist-variables@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" - integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w== +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== dependencies: - "@babel/types" "^7.4.4" + "@babel/types" "^7.8.3" -"@babel/helper-member-expression-to-functions@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" - integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA== +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== dependencies: - "@babel/types" "^7.5.5" + "@babel/types" "^7.8.3" -"@babel/helper-module-imports@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" - integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a" - integrity sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw== +"@babel/helper-module-transforms@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz#d305e35d02bee720fbc2c3c3623aa0c316c01590" + integrity sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/template" "^7.4.4" - "@babel/types" "^7.5.5" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" lodash "^4.17.13" -"@babel/helper-optimise-call-expression@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" - integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== -"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" - integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw== +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" - integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-wrap-function" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-replace-supers@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" - integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg== +"@babel/helper-replace-supers@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc" + integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.5.5" - "@babel/types" "^7.5.5" + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-simple-access@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" - integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== dependencies: - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== dependencies: - "@babel/types" "^7.4.4" + "@babel/types" "^7.8.3" -"@babel/helper-wrap-function@^7.1.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" - integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ== +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.2.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helpers@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.0.tgz#21961d16c6a3c3ab597325c34c465c0887d31c6e" - integrity sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ== +"@babel/helpers@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.3.tgz#382fbb0382ce7c4ce905945ab9641d688336ce85" + integrity sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ== dependencies: - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== dependencies: chalk "^2.0.0" esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/node@~7.6.1": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.6.1.tgz#84f8f4f1d86647d99537a681f32e65e70bb59f19" - integrity sha512-q2sJw+7aES/5wwjccECJfOuIgM1XIbZcn7b63JZM6VpaZwvOq913jL+tXRIn41Eg/Hr+BeIGWnvnjLTuT579pA== +"@babel/node@~7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.8.3.tgz#29784d445e135ca7214a9ac40535f2b8d2f980aa" + integrity sha512-GZpHg1gPnZTk1PvHRc4g/M5c50nHERkk3ojb5AuUTZFAjEKzDhBJcqvwWa7NrNT3W3Nf8t8Sj8JjA6rtXJ1z/g== dependencies: - "@babel/polyfill" "^7.6.0" - "@babel/register" "^7.6.0" - commander "^2.8.1" + "@babel/register" "^7.8.3" + commander "^4.0.1" + core-js "^3.2.1" lodash "^4.17.13" node-environment-flags "^1.0.5" + regenerator-runtime "^0.13.3" + resolve "^1.13.1" v8flags "^3.1.1" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b" - integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081" + integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ== -"@babel/plugin-proposal-async-generator-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" - integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" - "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.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== +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.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" - integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-object-rest-spread@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58" - integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" - integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== +"@babel/plugin-proposal-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" + integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" -"@babel/plugin-proposal-throw-expressions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz#2d9e452d370f139000e51db65d0a85dc60c64739" - integrity sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw== +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-throw-expressions" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78" - integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA== +"@babel/plugin-proposal-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" + integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-syntax-async-generators@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" - integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== +"@babel/plugin-proposal-throw-expressions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.8.3.tgz#155f36ae40c2a88ae685c35e3220f8a0d426cf24" + integrity sha512-tH40s9JnoR+r45ZXKWW+PC5xzPQfVJix3pR1D8Ty5l9sn5NnrbZUzw8MtnNxu/Bz7p0imyeSYj9FQVccEymOEg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-throw-expressions" "^7.8.3" -"@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== +"@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz#b646c3adea5f98800c9ab45105ac34d06cd4a47f" + integrity sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@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" - integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.0.tgz#e6c3dba5a61ecf72ba00a3f3f5f1234989a58e6a" + integrity sha512-a8w8k7pK8nYhem07rXdAq03T+DlTX8LFojUptrh9JEx80AgLqGiuoFIyQOGTWif39kFnDOQqbzl1s6KQqrfV+A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" - integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== +"@babel/plugin-syntax-bigint@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" - integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.0.tgz#3a6c1cd36af923db602df83c5aa72e08bb14353a" + integrity sha512-Mx2RzpCHJaBfmFdA2abXDKRHVJdzJ6R0Wqwb6TxCgM7NRR5wcC4cyiAsRL7Ga+lwG8GG1cKvb+4ENjic8y15jA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-throw-expressions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.2.0.tgz#79001ee2afe1b174b1733cdc2fc69c9a46a0f1f8" - integrity sha512-ngwynuqu1Rx0JUS9zxSDuPgW1K8TyVZCi2hHehrL4vyjqE7RGoNHWlZsS7KQT2vw9Yjk4YLa0+KldBXTRdPLRg== +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.0.tgz#7f798eb7e8cfd3821388120679d23d530bae6e53" + integrity sha512-LPykaAbH86L5NnDfCRSpNxtEHZk+6GaFzXfWEFU/6R4v69EXQr6GOp7hwH+Uw0QlYVN++s6TukTJ3flFcspahA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-arrow-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" - integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.0.tgz#5d8f24ecffa4ae74164e53264953c5ea8ba6d149" + integrity sha512-Rv2hnBToN6rbA9hO2a4vtwXZLzNa+TWkoSIMMvUezFz5+D9NPeX7SFrArwtFzzbwndmWiqboTr5rNpzAz0MPpA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@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== +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.0.tgz#9b37d580d459682364d8602494c69145b394fd4c" + integrity sha512-dt89fDlkfkTrQcy5KavMQPyF2A6tR0kYp8HAnIoQv5hO34iAUffHghP/hMGd7Gf/+uYTmLQO0ar7peX1SUWyIA== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-block-scoped-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" - integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.0.tgz#180c7bdd6b7fd81cc6d18269de12d5ddd60cabce" + integrity sha512-EIgJVy+u1RvR2gJfX4ReLwAupO/twllUue1wPrRxhu18+eC3bGTEcOSXLQdaE9ya9NG1rE0eQs0GSiloUGFEwg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-block-scoping@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.0.tgz#c49e21228c4bbd4068a35667e6d951c75439b1dc" - integrity sha512-tIt4E23+kw6TgL/edACZwP1OUKrjOTyMrFMLoT5IOFrfMRabCgekjqFd5o6PaAMildBu46oFkekIdMuGkkPEpA== +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.0.tgz#c40f4d4d6a4f5e71d2bfd949b0a7f1e1e6792fe0" + integrity sha512-LV1c+TTAO8Vawe3t+WXBHYWbS7endP8MSlqKPKEZOyWPEJX2akl3jfvFG828/OE7RpyoC3JXfLJDFj/jN7A8hg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-throw-expressions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.8.3.tgz#c763bcf26d202ddb65f1299a29d63aad312adb54" + integrity sha512-Mv3shY1i7ZssY4OY+eLZJAmNCwqTcpv2qOKO9x6irELSygfKWVSMXk0igJsA9UhU4hOdw0qMGkjj9TAk4MqzwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9" - integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg== +"@babel/plugin-transform-classes@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz#46fd7a9d2bb9ea89ce88720477979fe0d71b21b8" + integrity sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.5.5" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" - "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" - integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-destructuring@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6" - integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ== +"@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b" + integrity sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" - integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== +"@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@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== +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" - integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-for-of@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" - integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ== +"@babel/plugin-transform-for-of@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.3.tgz#15f17bce2fc95c7d59a24b299e83e81cedc22e18" + integrity sha512-ZjXznLNTxhpf4Q5q3x1NsngzGA38t9naWH8Gt+0qYZEJAcvPI9waSStSh56u19Ofjr7QmD0wUsQ8hw8s/p1VnA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-function-name@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad" - integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA== +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" - integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-member-expression-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" - integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA== +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@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== +"@babel/plugin-transform-modules-amd@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" + integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz#39dfe957de4420445f1fcf88b68a2e4aa4515486" - integrity sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g== +"@babel/plugin-transform-modules-commonjs@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" + integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== dependencies: - "@babel/helper-module-transforms" "^7.4.4" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@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== +"@babel/plugin-transform-modules-systemjs@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" + integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== dependencies: - "@babel/helper-hoist-variables" "^7.4.4" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-umd@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" - integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw== +"@babel/plugin-transform-modules-umd@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" + integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-named-capturing-groups-regex@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.0.tgz#1e6e663097813bb4f53d42df0750cf28ad3bb3f1" - integrity sha512-jem7uytlmrRl3iCAuQyw8BpB4c4LWvSpvIeXKpMb+7j84lkx4m4mYr5ErAcmN5KM7B6BqrAvRGjBIbbzqCczew== +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== dependencies: - regexp-tree "^0.1.13" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" -"@babel/plugin-transform-new-target@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" - integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA== +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-object-super@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9" - integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ== +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" -"@babel/plugin-transform-parameters@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" - integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw== +"@babel/plugin-transform-parameters@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.3.tgz#7890576a13b17325d8b7d44cb37f21dc3bbdda59" + integrity sha512-/pqngtGb54JwMBZ6S/D3XYylQDFtGjWrnoCF4gXZOUpFV/ujbxnoNGNvDGu6doFWRPBveE72qTx/RRU44j5I/Q== dependencies: - "@babel/helper-call-delegate" "^7.4.4" - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-call-delegate" "^7.8.3" + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-property-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" - integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ== +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-regenerator@^7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz#629dc82512c55cee01341fb27bdfcb210354680f" - integrity sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA== +"@babel/plugin-transform-regenerator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz#b31031e8059c07495bf23614c97f3d9698bc6ec8" + integrity sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA== dependencies: regenerator-transform "^0.14.0" -"@babel/plugin-transform-reserved-words@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" - integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw== +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-shorthand-properties@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" - integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-spread@^7.2.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" - integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-sticky-regex@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" - integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" -"@babel/plugin-transform-template-literals@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" - integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g== +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-typeof-symbol@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" - integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== +"@babel/plugin-transform-typeof-symbol@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.3.tgz#5cffb216fb25c8c64ba6bf5f76ce49d3ab079f4d" + integrity sha512-3TrkKd4LPqm4jHs6nPtSDI/SV9Cm5PRJkHLUgTcqRQQTMAZ44ZaAdDZJtvWFSaRcvT0a1rTmJ5ZA5tDKjleF3g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-unicode-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f" - integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA== +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/polyfill@^7.2.3", "@babel/polyfill@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.6.0.tgz#6d89203f8b6cd323e8d946e47774ea35dc0619cc" - integrity sha512-q5BZJI0n/B10VaQQvln1IlDK3BTBJFbADx7tv+oXDPIDZuTo37H5Adb9jhlXm/fEN4Y7/64qD9mnrJJG7rmaTw== +"@babel/preset-env@~7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.3.tgz#dc0fb2938f52bbddd79b3c861a4b3427dd3a6c54" + integrity sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg== dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.2" - -"@babel/preset-env@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.0.tgz#aae4141c506100bb2bfaa4ac2a5c12b395619e50" - integrity sha512-1efzxFv/TcPsNXlRhMzRnkBFMeIqBBgzwmZwlFDw5Ubj0AGLeufxugirwZmkkX/ayi3owsSqoQ4fw8LkfK9SYg== - 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.5.5" - "@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.5.0" - "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.0" - "@babel/plugin-transform-classes" "^7.5.5" - "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@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.5.0" - "@babel/plugin-transform-modules-commonjs" "^7.6.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.6.0" - "@babel/plugin-transform-new-target" "^7.4.4" - "@babel/plugin-transform-object-super" "^7.5.5" - "@babel/plugin-transform-parameters" "^7.4.4" - "@babel/plugin-transform-property-literals" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.4.5" - "@babel/plugin-transform-reserved-words" "^7.2.0" - "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.2.0" - "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@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.6.0" - browserslist "^4.6.0" - core-js-compat "^3.1.1" + "@babel/compat-data" "^7.8.0" + "@babel/helper-compilation-targets" "^7.8.3" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.8.3" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.8.3" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.8.3" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.8.3" + "@babel/plugin-transform-modules-commonjs" "^7.8.3" + "@babel/plugin-transform-modules-systemjs" "^7.8.3" + "@babel/plugin-transform-modules-umd" "^7.8.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.3" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.3" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.3" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/types" "^7.8.3" + browserslist "^4.8.2" + core-js-compat "^3.6.2" invariant "^2.2.2" - js-levenshtein "^1.1.3" + levenary "^1.1.0" semver "^5.5.0" -"@babel/register@^7.6.0", "@babel/register@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.6.0.tgz#76b6f466714680f4becafd45beeb2a7b87431abf" - integrity sha512-78BomdN8el+x/nkup9KwtjJXuptW5oXMFmP11WoM2VJBjxrKv4grC3qjpLL8RGGUYUGsm57xnjYFM2uom+jWUQ== +"@babel/register@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.3.tgz#5d5d30cfcc918437535d724b8ac1e4a60c5db1f8" + integrity sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg== dependencies: find-cache-dir "^2.0.0" lodash "^4.17.13" - mkdirp "^0.5.1" + make-dir "^2.1.0" pirates "^4.0.0" - source-map-support "^0.5.9" + source-map-support "^0.5.16" "@babel/runtime-corejs2@^7.2.0", "@babel/runtime-corejs2@^7.5.5": version "7.5.5" @@ -689,45 +778,50 @@ regenerator-runtime "^0.13.2" "@babel/runtime@^7.0.0", "@babel/runtime@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" - integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd" + integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg== dependencies: regenerator-runtime "^0.13.2" -"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" - integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== +"@babel/template@^7.7.4", "@babel/template@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" + integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516" - integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.3.tgz#a826215b011c9b4f73f3a893afbc05151358bf9a" + integrity sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg== dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/types" "^7.8.3" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" - integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" + integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== dependencies: esutils "^2.0.2" lodash "^4.17.13" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" @@ -736,27 +830,81 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@graphql-toolkit/common@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.9.0.tgz#24d1744fa0d6b9331e8032097fcebb74390bad92" + integrity sha512-bLuyt4yV/XIHUS+gP4aF5xjnb5M2K+uuB35Hojw0er+tkNhWiOuWQzRMWPovds/4WN2C9PuknQby/+ntgBOm/g== + dependencies: + "@kamilkisiela/graphql-tools" "4.0.6" + aggregate-error "3.0.1" + lodash "4.17.15" + +"@graphql-toolkit/file-loading@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/file-loading/-/file-loading-0.9.0.tgz#745cfe22adb0d710d09f43bdd67a2d9b54e0257a" + integrity sha512-Vs7n8VsZm1Oyw/1kKy/Y7fE8CznFA4SK4SEnWll9gpCmVE2vbATUYUlqSqm/W2npDdYB3FODEzhr5HIsQrjg6g== + dependencies: + globby "11.0.0" + unixify "1.0.0" + +"@graphql-toolkit/schema-merging@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/schema-merging/-/schema-merging-0.9.0.tgz#f4fb380e6be57e0235c25a77fc0aec842414c27e" + integrity sha512-vrzkqkFXxZ4dXQrHeNGDDWONbOAVDeJmGPwK0cRu2aVszftvkYVJXBrmkMYzZJHwk+tGVkNywf1r00GR6prpOw== + dependencies: + "@graphql-toolkit/common" "0.9.0" + "@kamilkisiela/graphql-tools" "4.0.6" + deepmerge "4.2.2" + tslib "1.10.0" + "@hapi/address@2.x.x": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" - integrity sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw== + version "2.1.2" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.2.tgz#1c794cd6dbf2354d1eb1ef10e0303f573e1c7222" + integrity sha512-O4QDrx+JoGKZc6aN64L04vqa7e41tIiLU+OvKdcYaEMP97UttL0f9GIi9/0A4WAMx0uBd6SidDIhktZhgOcN8Q== + +"@hapi/address@^2.1.2": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + +"@hapi/address@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.0.0.tgz#36affb4509b5a6adc628bcc394450f2a7d51d111" + integrity sha512-GDDpkCdSUfkQCznmWUHh9dDN85BWf/V8TFKQ2JLuHdGB4Yy3YTEGBzZxoBNxfNBEvreSR/o+ZxBBSNNEVzY+lQ== + dependencies: + "@hapi/hoek" "^9.0.0" "@hapi/bourne@1.x.x": version "1.3.2" resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== -"@hapi/formula@1.x.x": +"@hapi/formula@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-1.2.0.tgz#994649c7fea1a90b91a0a1e6d983523f680e10cd" integrity sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA== -"@hapi/hoek@8.x.x": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.2.1.tgz#924af04cbb22e17359c620d2a9c946e63f58eb77" - integrity sha512-JPiBy+oSmsq3St7XlipfN5pNA6bDJ1kpa73PrK/zR29CVClDVqy04AanM/M/qx5bSF+I61DdCfAvRrujau+zRg== +"@hapi/formula@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" + integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/joi@^15.0.3", "@hapi/joi@^15.1.0": +"@hapi/hoek@8.x.x": + version "8.2.4" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.2.4.tgz#684a14f4ca35d46f44abc87dfc696e5e4fe8a020" + integrity sha512-Ze5SDNt325yZvNO7s5C4fXDscjJ6dcqLFXJQ/M7dZRQCewuDj2iDUuBi6jLQt+APbW9RjjVEvLr35FXuOEqjow== + +"@hapi/hoek@^8.2.4", "@hapi/hoek@^8.3.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.0.tgz#2f9ce301c8898e1c3248b0a8564696b24d1a9a5a" + integrity sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw== + +"@hapi/hoek@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.0.tgz#ba83436edfac1d1ffd0e94797d43419c20ad49b8" + integrity sha512-XxD4A5YMIH70ddjG7BJBUz7RWVQAwIP/36Eoyh0DsaWp92OAeXkrbtSEaYkynBPTsN9Uv2mZq9QWZYILl2Svrw== + +"@hapi/joi@^15.1.0": version "15.1.1" resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== @@ -766,22 +914,38 @@ "@hapi/hoek" "8.x.x" "@hapi/topo" "3.x.x" -"@hapi/joi@^16.0.1": - version "16.0.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.0.1.tgz#37c78878db0bc04c35996515e0aa186c0e2b5398" - integrity sha512-5TdjUnNAaK7+lWZ2HRXtgOnxe4VBoJLoX0XOrfkmw+2n4/VJ6wwOJMoD7u/F9alLsP31kOWDbnQhtS0WAKwD4Q== +"@hapi/joi@^16.1.8": + version "16.1.8" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.8.tgz#84c1f126269489871ad4e2decc786e0adef06839" + integrity sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg== dependencies: - "@hapi/address" "2.x.x" - "@hapi/formula" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/pinpoint" "1.x.x" - "@hapi/topo" "3.x.x" + "@hapi/address" "^2.1.2" + "@hapi/formula" "^1.2.0" + "@hapi/hoek" "^8.2.4" + "@hapi/pinpoint" "^1.0.2" + "@hapi/topo" "^3.1.3" -"@hapi/pinpoint@1.x.x": +"@hapi/joi@^17.1.0": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.0.tgz#cc4000b6c928a6a39b9bef092151b6bdee10ce55" + integrity sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw== + dependencies: + "@hapi/address" "^4.0.0" + "@hapi/formula" "^2.0.0" + "@hapi/hoek" "^9.0.0" + "@hapi/pinpoint" "^2.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/pinpoint@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-1.0.2.tgz#025b7a36dbbf4d35bf1acd071c26b20ef41e0d13" integrity sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ== +"@hapi/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" + integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== + "@hapi/topo@3.x.x": version "3.1.3" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.3.tgz#c7a02e0d936596d29f184e6d7fdc07e8b5efce11" @@ -789,153 +953,193 @@ dependencies: "@hapi/hoek" "8.x.x" -"@jest/console@^24.7.1", "@jest/console@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" - integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== +"@hapi/topo@^3.1.3": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== dependencies: - "@jest/source-map" "^24.9.0" - chalk "^2.0.1" - slash "^2.0.0" + "@hapi/hoek" "^8.3.0" -"@jest/core@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" - integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== dependencies: - "@jest/console" "^24.7.1" - "@jest/reporters" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" + "@hapi/hoek" "^9.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" + integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.1.0.tgz#1fc765d44a1e11aec5029c08e798246bd37075ab" + integrity sha512-3P1DpqAMK/L07ag/Y9/Jup5iDEG9P4pRAuZiMQnU0JB3UOvCyYCjCoxr7sIA80SeyUCUKrr24fKAxVpmBgQonA== + dependencies: + "@jest/source-map" "^25.1.0" + chalk "^3.0.0" + jest-util "^25.1.0" + slash "^3.0.0" + +"@jest/core@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.1.0.tgz#3d4634fc3348bb2d7532915d67781cdac0869e47" + integrity sha512-iz05+NmwCmZRzMXvMo6KFipW7nzhbpEawrKrkkdJzgytavPse0biEnCNr2wRlyCsp3SmKaEY+SGv7YWYQnIdig== + dependencies: + "@jest/console" "^25.1.0" + "@jest/reporters" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" exit "^0.1.2" - graceful-fs "^4.1.15" - jest-changed-files "^24.9.0" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-resolve-dependencies "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - jest-watcher "^24.9.0" - micromatch "^3.1.10" - p-each-series "^1.0.0" + graceful-fs "^4.2.3" + jest-changed-files "^25.1.0" + jest-config "^25.1.0" + jest-haste-map "^25.1.0" + jest-message-util "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-resolve-dependencies "^25.1.0" + jest-runner "^25.1.0" + jest-runtime "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + jest-watcher "^25.1.0" + micromatch "^4.0.2" + p-each-series "^2.1.0" realpath-native "^1.1.0" - rimraf "^2.5.4" - slash "^2.0.0" - strip-ansi "^5.0.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" -"@jest/environment@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" - integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== +"@jest/environment@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.1.0.tgz#4a97f64770c9d075f5d2b662b5169207f0a3f787" + integrity sha512-cTpUtsjU4cum53VqBDlcW0E4KbQF03Cn0jckGPW/5rrE9tb+porD3+hhLtHAwhthsqfyF+bizyodTlsRA++sHg== dependencies: - "@jest/fake-timers" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" -"@jest/fake-timers@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" - integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== +"@jest/fake-timers@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.1.0.tgz#a1e0eff51ffdbb13ee81f35b52e0c1c11a350ce8" + integrity sha512-Eu3dysBzSAO1lD7cylZd/CVKdZZ1/43SF35iYBNV1Lvvn2Undp3Grwsv8PrzvbLhqwRzDd4zxrY4gsiHc+wygQ== dependencies: - "@jest/types" "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" + "@jest/types" "^25.1.0" + jest-message-util "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + lolex "^5.0.0" -"@jest/reporters@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" - integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== +"@jest/reporters@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.1.0.tgz#9178ecf136c48f125674ac328f82ddea46e482b0" + integrity sha512-ORLT7hq2acJQa8N+NKfs68ZtHFnJPxsGqmofxW7v7urVhzJvpKZG9M7FAcgh9Ee1ZbCteMrirHA3m5JfBtAaDg== dependencies: - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - 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" - istanbul-reports "^2.2.6" - jest-haste-map "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" - node-notifier "^5.4.2" - slash "^2.0.0" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.0" + jest-haste-map "^25.1.0" + jest-resolve "^25.1.0" + jest-runtime "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" + slash "^3.0.0" source-map "^0.6.0" - string-length "^2.0.0" + string-length "^3.1.0" + terminal-link "^2.0.0" + v8-to-istanbul "^4.0.1" + optionalDependencies: + node-notifier "^6.0.0" -"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" - integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== +"@jest/source-map@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.1.0.tgz#b012e6c469ccdbc379413f5c1b1ffb7ba7034fb0" + integrity sha512-ohf2iKT0xnLWcIUhL6U6QN+CwFWf9XnrM2a6ybL9NXxJjgYijjLSitkYHIdzkd8wFliH73qj/+epIpTiWjRtAA== dependencies: callsites "^3.0.0" - graceful-fs "^4.1.15" + graceful-fs "^4.2.3" source-map "^0.6.0" -"@jest/test-result@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" - integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== +"@jest/test-result@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.1.0.tgz#847af2972c1df9822a8200457e64be4ff62821f7" + integrity sha512-FZzSo36h++U93vNWZ0KgvlNuZ9pnDnztvaM7P/UcTx87aPDotG18bXifkf1Ji44B7k/eIatmMzkBapnAzjkJkg== dependencies: - "@jest/console" "^24.9.0" - "@jest/types" "^24.9.0" + "@jest/console" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" - integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== +"@jest/test-sequencer@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.1.0.tgz#4df47208542f0065f356fcdb80026e3c042851ab" + integrity sha512-WgZLRgVr2b4l/7ED1J1RJQBOharxS11EFhmwDqknpknE0Pm87HLZVS2Asuuw+HQdfQvm2aXL2FvvBLxOD1D0iw== dependencies: - "@jest/test-result" "^24.9.0" - jest-haste-map "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" + "@jest/test-result" "^25.1.0" + jest-haste-map "^25.1.0" + jest-runner "^25.1.0" + jest-runtime "^25.1.0" -"@jest/transform@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" - integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== +"@jest/transform@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.1.0.tgz#221f354f512b4628d88ce776d5b9e601028ea9da" + integrity sha512-4ktrQ2TPREVeM+KxB4zskAT84SnmG1vaz4S+51aTefyqn3zocZUnliLLm5Fsl85I3p/kFPN4CRp1RElIfXGegQ== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.9.0" - babel-plugin-istanbul "^5.1.0" - chalk "^2.0.1" + "@jest/types" "^25.1.0" + babel-plugin-istanbul "^6.0.0" + chalk "^3.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.1.15" - jest-haste-map "^24.9.0" - jest-regex-util "^24.9.0" - jest-util "^24.9.0" - micromatch "^3.1.10" + graceful-fs "^4.2.3" + jest-haste-map "^25.1.0" + jest-regex-util "^25.1.0" + jest-util "^25.1.0" + micromatch "^4.0.2" pirates "^4.0.1" realpath-native "^1.1.0" - slash "^2.0.0" + slash "^3.0.0" source-map "^0.6.1" - write-file-atomic "2.4.1" + write-file-atomic "^3.0.0" -"@jest/types@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" - integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== +"@jest/types@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395" + integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" "@kamilkisiela/graphql-tools@4.0.6": version "4.0.6" @@ -971,13 +1175,13 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.6.6", "@metascraper/helpers@^5.7.0", "@metascraper/helpers@^5.7.4": - version "5.7.4" - resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.4.tgz#c91c1b11ce585fa973a544a9d24c5d88d50a9354" - integrity sha512-GMLFu8j7e65n04w+dfOVF8RWOqNHCqimITtTHYSa1XdLR8vSqE2PjvSOhGoS5ELU5fRlRQKy9EOrKDeRV3/K0w== +"@metascraper/helpers@^5.10.6": + version "5.10.6" + resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.10.6.tgz#0b786607212925a577926fd0cd0313a49de3499c" + integrity sha512-/jvhlM3RKGYMoUK8D8S1r3tN03/EYizCqWF7zDx0aBMC8Ihp33DRGs9oNdsgkgwzVF7O/YpDm55l9K+qVJlsyQ== dependencies: audio-extensions "0.0.0" - chrono-node "~1.3.11" + chrono-node "~1.4.2" condense-whitespace "~2.0.0" entities "~2.0.0" file-extension "~4.0.5" @@ -985,18 +1189,39 @@ image-extensions "~1.1.0" is-relative-url "~3.0.0" is-uri "~1.2.0" - iso-639-3 "~1.2.0" + iso-639-3 "~2.0.0" isostring "0.0.1" lodash "~4.17.15" - mem "~5.1.1" - mime-types "~2.1.24" - normalize-url "~4.3.0" + memoize-one "~5.1.1" + mime-types "~2.1.26" + normalize-url "~4.5.0" smartquotes "~2.3.1" title "~3.4.1" truncate "~2.1.0" url-regex "~5.0.0" video-extensions "~1.1.0" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -1050,60 +1275,83 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@sentry/core@5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.6.2.tgz#8c5477654a83ebe41a72e86a79215deb5025e418" - integrity sha512-grbjvNmyxP5WSPR6UobN2q+Nss7Hvz+BClBT8QTr7VTEG5q89TwNddn6Ej3bGkaUVbct/GpVlI3XflWYDsnU6Q== +"@sentry/apm@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e" + integrity sha512-4iZH11p/7w9IMLT9hqNY1+EqLESltiIoF6/YsbpK93sXWGEs8VQ83IuvGuKWxajvHgDmj4ND0TxIliTsYqTqFw== dependencies: - "@sentry/hub" "5.6.1" - "@sentry/minimal" "5.6.1" - "@sentry/types" "5.6.1" - "@sentry/utils" "5.6.1" + "@sentry/browser" "5.11.1" + "@sentry/hub" "5.11.1" + "@sentry/minimal" "5.11.1" + "@sentry/types" "5.11.0" + "@sentry/utils" "5.11.1" tslib "^1.9.3" -"@sentry/hub@5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.6.1.tgz#9f355c0abcc92327fbd10b9b939608aa4967bece" - integrity sha512-m+OhkIV5yTAL3R1+XfCwzUQka0UF/xG4py8sEfPXyYIcoOJ2ZTX+1kQJLy8QQJ4RzOBwZA+DzRKP0cgzPJ3+oQ== +"@sentry/browser@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.1.tgz#337ffcb52711b23064c847a07629e966f54a5ebb" + integrity sha512-oqOX/otmuP92DEGRyZeBuQokXdeT9HQRxH73oqIURXXNLMP3PWJALSb4HtT4AftEt/2ROGobZLuA4TaID6My/Q== dependencies: - "@sentry/types" "5.6.1" - "@sentry/utils" "5.6.1" + "@sentry/core" "5.11.1" + "@sentry/types" "5.11.0" + "@sentry/utils" "5.11.1" tslib "^1.9.3" -"@sentry/minimal@5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.6.1.tgz#09d92b26de0b24555cd50c3c33ba4c3e566009a1" - integrity sha512-ercCKuBWHog6aS6SsJRuKhJwNdJ2oRQVWT2UAx1zqvsbHT9mSa8ZRjdPHYOtqY3DoXKk/pLUFW/fkmAnpdMqRw== +"@sentry/core@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.1.tgz#9e2da485e196ae32971545c1c49ee6fe719930e2" + integrity sha512-BpvPosVNT20Xso4gAV54Lu3KqDmD20vO63HYwbNdST5LUi8oYV4JhvOkoBraPEM2cbBwQvwVcFdeEYKk4tin9A== dependencies: - "@sentry/hub" "5.6.1" - "@sentry/types" "5.6.1" + "@sentry/hub" "5.11.1" + "@sentry/minimal" "5.11.1" + "@sentry/types" "5.11.0" + "@sentry/utils" "5.11.1" tslib "^1.9.3" -"@sentry/node@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.6.2.tgz#4b62f056031da65cad78220d48c546b8bfbfaed7" - integrity sha512-A9CELco6SjF4zt8iS1pO3KdUVI2WVhtTGhSH6X04OVf2en1fimPR+Vs8YVY/04udwd7o+3mI6byT+rS9+/Qzow== +"@sentry/hub@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.1.tgz#ddcb865563fae53852d405885c46b4c6de68a91b" + integrity sha512-ucKprYCbGGLLjVz4hWUqHN9KH0WKUkGf5ZYfD8LUhksuobRkYVyig0ZGbshECZxW5jcDTzip4Q9Qimq/PkkXBg== dependencies: - "@sentry/core" "5.6.2" - "@sentry/hub" "5.6.1" - "@sentry/types" "5.6.1" - "@sentry/utils" "5.6.1" - cookie "0.3.1" - https-proxy-agent "2.2.1" - lru_map "0.3.3" + "@sentry/types" "5.11.0" + "@sentry/utils" "5.11.1" tslib "^1.9.3" -"@sentry/types@5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.6.1.tgz#5915e1ee4b7a678da3ac260c356b1cb91139a299" - integrity sha512-Kub8TETefHpdhvtnDj3kKfhCj0u/xn3Zi2zIC7PB11NJHvvPXENx97tciz4roJGp7cLRCJsFqCg4tHXniqDSnQ== - -"@sentry/utils@5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.6.1.tgz#69d9e151e50415bc91f2428e3bcca8beb9bc2815" - integrity sha512-rfgha+UsHW816GqlSRPlniKqAZylOmQWML2JsujoUP03nPu80zdN43DK9Poy/d9OxBxv0gd5K2n+bFdM2kqLQQ== +"@sentry/minimal@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.1.tgz#0e705d01a567282d8fbbda2aed848b4974cc3cec" + integrity sha512-HK8zs7Pgdq7DsbZQTThrhQPrJsVWzz7MaluAbQA0rTIAJ3TvHKQpsVRu17xDpjZXypqWcKCRsthDrC4LxDM1Bg== dependencies: - "@sentry/types" "5.6.1" + "@sentry/hub" "5.11.1" + "@sentry/types" "5.11.0" + tslib "^1.9.3" + +"@sentry/node@^5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.11.1.tgz#2a9c18cd1209cfdf7a69b9d91303413149d2c910" + integrity sha512-FbJs0blJ36gEzE0rc2yBfA/KE+kXOLl8MUfFTcyJCBdCGF8XMETDCmgINnJ4TyBUJviwKoPw2TCk9TL2pa/A1w== + dependencies: + "@sentry/apm" "5.11.1" + "@sentry/core" "5.11.1" + "@sentry/hub" "5.11.1" + "@sentry/types" "5.11.0" + "@sentry/utils" "5.11.1" + cookie "^0.3.1" + https-proxy-agent "^4.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/types@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6" + integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg== + +"@sentry/utils@5.11.1": + version "5.11.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607" + integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA== + dependencies: + "@sentry/types" "5.11.0" tslib "^1.9.3" "@sindresorhus/is@^0.14.0": @@ -1111,6 +1359,13 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sinonjs/commons@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" + integrity sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg== + dependencies: + type-detect "4.0.8" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -1166,6 +1421,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/connect@*": version "3.4.32" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" @@ -1190,11 +1450,6 @@ dependencies: "@types/express" "*" -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - "@types/express-serve-static-core@*": version "4.16.9" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz#69e00643b0819b024bdede95ced3ff239bb54558" @@ -1203,10 +1458,10 @@ "@types/node" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@4.17.1": - version "4.17.1" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.1.tgz#4cf7849ae3b47125a567dfee18bfca4254b88c5c" - integrity sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w== +"@types/express@*", "@types/express@4.17.2": + version "4.17.2" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c" + integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" @@ -1219,15 +1474,6 @@ dependencies: "@types/node" "*" -"@types/glob@7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - "@types/graphql-upload@^8.0.0": version "8.0.1" resolved "https://registry.yarnpkg.com/@types/graphql-upload/-/graphql-upload-8.0.1.tgz#12c804255c681fd06a904b67bde7506f2484f1a8" @@ -1248,7 +1494,7 @@ resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": 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== @@ -1307,10 +1553,12 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/node-fetch@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.4.tgz#5245b6d8841fc3a6208b82291119bc11c4e0ce44" + integrity sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ== + dependencies: + "@types/node" "*" "@types/node@*", "@types/node@>=6": version "12.7.2" @@ -1352,39 +1600,42 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" integrity sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw== -"@types/yargs@^13.0.0": - version "13.0.2" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653" - integrity sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ== +"@types/yargs@^15.0.0": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.1.tgz#9266a9d7be68cfcc982568211085a49a277f7c96" + integrity sha512-sYlwNU7zYi6eZbMzFvG6eHD7VsEvFdoDtlD7eI1JTg7YNnuguzmiGsc6MPSq5l8n+h21AsNof0je+9sgOe4+dg== dependencies: "@types/yargs-parser" "*" -"@types/yup@0.26.23": - version "0.26.23" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.23.tgz#00721a3b675e7609e5bcccb94234e86b754bcd04" - integrity sha512-+tipAL6prdInS/avA6QityIFBDvHnqk1Tv9L5JMEws5IZC6agymBGAoDsrPyYp42wGcktyQtYKv9kvGPEKd4Qg== +"@types/yup@0.26.28": + version "0.26.28" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.28.tgz#6b8a98fb56ddb5c8a3a7f60996165221671c1ca1" + integrity sha512-DiT584YBKBENDzgk50LwiJLLOh+XgHpiy9p8PwSoOY696LpfrJRIeJ2AoBCG9KyjE8gWL4J64xDZgw15itecag== "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/experimental-utils@^1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" - integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== +"@typescript-eslint/experimental-utils@^2.5.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.6.0.tgz#ed70bef72822bff54031ff0615fc888b9e2b6e8a" + integrity sha512-34BAFpNOwHXeqT+AvdalLxOvcPYnCxA5JGmBAFL64RGMdP0u65rXjii7l/nwpgk5aLEE1LaqF+SsCU0/Cb64xA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-scope "^4.0.0" + "@typescript-eslint/typescript-estree" "2.6.0" + eslint-scope "^5.0.0" -"@typescript-eslint/typescript-estree@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" - integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== +"@typescript-eslint/typescript-estree@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.6.0.tgz#d3e9d8e001492e2b9124c4d4bd4e7f03c0fd7254" + integrity sha512-A3lSBVIdj2Gp0lFEL6in2eSPqJ33uAc3Ko+Y4brhjkxzjbzLnwBH22CwsW2sCo+iwogfIyvb56/AJri15H0u5Q== dependencies: + debug "^4.1.1" + glob "^7.1.4" + is-glob "^4.0.1" lodash.unescape "4.0.1" - semver "5.5.0" + semver "^6.3.0" "@wry/context@^0.4.0": version "0.4.4" @@ -1419,74 +1670,48 @@ accepts@^1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-globals@^4.1.0: - version "4.3.3" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.3.tgz#a86f75b69680b8780d30edd21eee4e0ea170c05e" - integrity sha512-vkR40VwS2SYO98AIeFvzWWh+xyc2qi9s7OoXSFEGIP/rOJKzjnhykaZJNnHdoq4BL2gGxI5EZOU16z896EYnOQ== +acorn-globals@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f" - integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw== +acorn-jsx@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" + integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== acorn-walk@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== -acorn@^5.5.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== - acorn@^6.0.1: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== -acorn@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a" - integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ== +acorn@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" + integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== -activitystrea.ms@~2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/activitystrea.ms/-/activitystrea.ms-2.1.3.tgz#553548733e367dc0b6a7badc25fa6f8996cd80c3" - integrity sha512-iiG5g5fYgfdaaqqFPaFIZC/KX8/4mOWkvniK+BNwJY6XDDKdIu56wmc9r0x1INHVnbFOTGuM8mZEntaM3I+YXw== +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + +aggregate-error@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" + integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== dependencies: - activitystreams-context "^3.0.0" - jsonld "^0.4.11" - jsonld-signatures "^1.1.5" - moment "^2.17.1" - readable-stream "^2.2.3" - reasoner "2.0.0" - rfc5646 "^2.0.0" - vocabs-as "^3.0.0" - vocabs-asx "^0.11.1" - vocabs-interval "^0.11.1" - vocabs-ldp "^0.1.0" - vocabs-owl "^0.11.1" - vocabs-rdf "^0.11.1" - vocabs-rdfs "^0.11.1" - vocabs-social "^0.11.1" - vocabs-xsd "^0.11.1" + clean-stack "^2.0.0" + indent-string "^4.0.0" -activitystreams-context@>=3.0.0, activitystreams-context@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/activitystreams-context/-/activitystreams-context-3.1.0.tgz#28334e129f17cfb937e8c702c52c1bcb1d2830c7" - integrity sha512-KBQ+igwf1tezMXGVw5MvRSEm0gp97JI1hTZ45I6MEkWv25lEgNoA9L6wqfaOiCX8wnMRWw9pwRsPZKypdtxAtg== - -agent-base@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -aggregate-error@3.0.0, aggregate-error@^3.0.0: +aggregate-error@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.0.tgz#5b5a3c95e9095f311c9ab16c19fb4f3527cd3f79" integrity sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA== @@ -1511,11 +1736,6 @@ ansi-align@^2.0.0: dependencies: string-width "^2.0.0" -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - ansi-escapes@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228" @@ -1533,11 +1753,21 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.0.0, ansi-regex@^4.1.0: +ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + ansi-styles@^3.1.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1545,6 +1775,14 @@ ansi-styles@^3.1.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1558,80 +1796,90 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-cache-control@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.4.tgz#a3650d5e4173953e2a3af995bea62147f1ffe4d7" - integrity sha512-IZ1d3AXZtkZhLYo0kWqTbZ6nqLFaeUvLdMESs+9orMadBZ7mvzcAfBwrhKyCWPGeAAZ/jKv8FtYHybpchHgFAg== +anymatch@^3.0.3, anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +apollo-cache-control@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.11.tgz#726e4e3c5685bacbf26c8fbba1f41b4e6252c597" + integrity sha512-8yz4qbRBIFDWRHdT8uPh0HHh+VbQXxoFGJQRAG8hyMRvR+EuURXX1ltXYkn5J3YJ3MKEqgsvwGaq60dFZq63UQ== dependencies: apollo-server-env "^2.4.3" - graphql-extensions "^0.10.3" + graphql-extensions "^0.10.10" -apollo-cache-inmemory@~1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d" - integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg== +apollo-cache-inmemory@~1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a" + integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA== dependencies: - apollo-cache "^1.3.2" - apollo-utilities "^1.3.2" + apollo-cache "^1.3.4" + apollo-utilities "^1.3.3" optimism "^0.10.0" ts-invariant "^0.4.0" - tslib "^1.9.3" + tslib "^1.10.0" -apollo-cache@1.3.2, apollo-cache@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.2.tgz#df4dce56240d6c95c613510d7e409f7214e6d26a" - integrity sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg== +apollo-cache@1.3.4, apollo-cache@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42" + integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA== dependencies: - apollo-utilities "^1.3.2" - tslib "^1.9.3" + apollo-utilities "^1.3.3" + tslib "^1.10.0" -apollo-client@~2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.4.tgz#872c32927263a0d34655c5ef8a8949fbb20b6140" - integrity sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ== +apollo-client@~2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537" + integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw== dependencies: "@types/zen-observable" "^0.8.0" - apollo-cache "1.3.2" + apollo-cache "1.3.4" apollo-link "^1.0.0" - apollo-utilities "1.3.2" + apollo-utilities "1.3.3" symbol-observable "^1.0.2" ts-invariant "^0.4.0" - tslib "^1.9.3" + tslib "^1.10.0" zen-observable "^0.8.0" -apollo-datasource@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.3.tgz#b31e089e52adb92fabb536ab8501c502573ffe13" - integrity sha512-gRYyFVpJgHE2hhS+VxMeOerxXQ/QYxWG7T6QddfugJWYAG9DRCl65e2b7txcGq2NP3r+O1iCm4GNwhRBDJbd8A== +apollo-datasource@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.4.tgz#c0d1604b1a97e004844d4b61bd819a9a6b0a409f" + integrity sha512-u4eu6Q94q6KuZacZfdo4vCevA81F4QWeTYEXUvoksQMJpiacPHHe0DJrofKVKvxngUp5kCi1RnPXSc6kBY+/oA== dependencies: - apollo-server-caching "^0.5.0" + apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" -apollo-engine-reporting-protobuf@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.0.tgz#e34c192d86493b33a73181fd6be75721559111ec" - integrity sha512-cXHZSienkis8v4RhqB3YG3DkaksqLpcxApRLTpRMs7IXNozgV7CUPYGFyFBEra1ZFgUyHXx4G9MpelV+n2cCfA== +apollo-engine-reporting-protobuf@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.4.tgz#73a064f8c9f2d6605192d1673729c66ec47d9cb7" + integrity sha512-SGrIkUR7Q/VjU8YG98xcvo340C4DaNUhg/TXOtGsMlfiJDzHwVau/Bv6zifAzBafp2lj0XND6Daj5kyT/eSI/w== dependencies: - protobufjs "^6.8.6" + "@apollo/protobufjs" "^1.0.3" -apollo-engine-reporting@^1.4.6: - version "1.4.6" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.6.tgz#83af6689c4ab82d1c62c3f5dde7651975508114f" - integrity sha512-acfb7oFnru/8YQdY4x6+7WJbZfzdVETI8Cl+9ImgUrvUnE8P+f2SsGTKXTC1RuUvve4c56PAvaPgE+z8X1a1Mw== +apollo-engine-reporting@^1.4.14: + version "1.4.14" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.14.tgz#71a6509ebe86385da43df500cd0940525a3e8674" + integrity sha512-cCG9qDOPwbh87ZjQGHgmnP3oPqhqjIZcNmm/lNtWkWXGTlxV/jmUEqpVi+wsDbE5gR7d1OFk6GqSy2ZQh+S+Bw== dependencies: - apollo-engine-reporting-protobuf "^0.4.0" - apollo-graphql "^0.3.3" - apollo-server-caching "^0.5.0" + apollo-engine-reporting-protobuf "^0.4.4" + apollo-graphql "^0.3.7" + apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" - apollo-server-types "^0.2.4" + apollo-server-errors "^2.3.4" + apollo-server-types "^0.2.10" async-retry "^1.2.1" - graphql-extensions "^0.10.3" + graphql-extensions "^0.10.10" -apollo-env@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.5.1.tgz#b9b0195c16feadf0fe9fd5563edb0b9b7d9e97d3" - integrity sha512-fndST2xojgSdH02k5hxk1cbqA9Ti8RX4YzzBoAB4oIe1Puhq7+YlhXGXfXB5Y4XN0al8dLg+5nAkyjNAR2qZTw== +apollo-env@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.6.1.tgz#12cc869c4276a5f794edf5e5f243676038d4fb07" + integrity sha512-B9BgpQGR1ndeDtb4Gtor0J4CITQ+OPACZrVW6lgStnljKEe9ZB76DZ1dAd3OCeizAswW6Lo9uvfK8jhVS5nBhQ== dependencies: + "@types/node-fetch" "2.5.4" core-js "^3.0.1" node-fetch "^2.2.0" sha.js "^2.4.11" @@ -1644,12 +1892,12 @@ apollo-errors@^1.9.0: assert "^1.4.1" extendable-error "^0.1.5" -apollo-graphql@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.3.tgz#ce1df194f6e547ad3ce1e35b42f9c211766e1658" - integrity sha512-t3CO/xIDVsCG2qOvx2MEbuu4b/6LzQjcBBwiVnxclmmFyAxYCIe7rpPlnLHSq7HyOMlCWDMozjoeWfdqYSaLqQ== +apollo-graphql@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.7.tgz#533232ed48b0b6dbcf5635f65e66cf8677a5b768" + integrity sha512-ghW16xx9tRcyL38Pw6G5OidMnYn+CNUGZWmvqQgEO2nRy4T0ONPZZBOvGrIMtJQ70oEykNMKGm0zm6PdHdxd8Q== dependencies: - apollo-env "0.5.1" + apollo-env "^0.6.1" lodash.sortby "^4.7.0" apollo-link-context@~1.0.19: @@ -1688,33 +1936,33 @@ apollo-link@^1.0.0, apollo-link@^1.2.13, apollo-link@^1.2.3: tslib "^1.9.3" zen-observable-ts "^0.8.20" -apollo-server-caching@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.0.tgz#446a37ce2d4e24c81833e276638330a634f7bd46" - integrity sha512-l7ieNCGxUaUAVAAp600HjbUJxVaxjJygtPV0tPTe1Q3HkPy6LEWoY6mNHV7T268g1hxtPTxcdRu7WLsJrg7ufw== +apollo-server-caching@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz#5cd0536ad5473abb667cc82b59bc56b96fb35db6" + integrity sha512-L7LHZ3k9Ao5OSf2WStvQhxdsNVplRQi7kCAPfqf9Z3GBEnQ2uaL0EgO0hSmtVHfXTbk5CTRziMT1Pe87bXrFIw== dependencies: lru-cache "^5.0.0" -apollo-server-core@^2.9.3: - version "2.9.3" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.3.tgz#918f836c8215d371935c831c72d0840c7bf0250f" - integrity sha512-KQpOM3nAXdMqKVE0HHcOkH/EVhyDqFEKLNFlsyGHGOn9ujpI6RsltX+YpXRyAdbfQHpTk11v/IAo6XksWN+g1Q== +apollo-server-core@^2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.16.tgz#b4c869a6babfa6906fbbf1e6facf3b7231dbf777" + integrity sha512-4ftdjSfs/3aEare9QNTVSF0yUvXETxiohuDLZ7gmMIQxNnZhUjVXiZL1rYKuIZ12uH7xLvh/DwkXRt6nLG/lZA== dependencies: - "@apollographql/apollo-tools" "^0.4.0" + "@apollographql/apollo-tools" "^0.4.3" "@apollographql/graphql-playground-html" "1.6.24" "@types/graphql-upload" "^8.0.0" "@types/ws" "^6.0.0" - apollo-cache-control "^0.8.4" - apollo-datasource "^0.6.3" - apollo-engine-reporting "^1.4.6" - apollo-server-caching "^0.5.0" + apollo-cache-control "^0.8.11" + apollo-datasource "^0.6.4" + apollo-engine-reporting "^1.4.14" + apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" - apollo-server-errors "^2.3.3" - apollo-server-plugin-base "^0.6.4" - apollo-server-types "^0.2.4" - apollo-tracing "^0.8.4" + apollo-server-errors "^2.3.4" + apollo-server-plugin-base "^0.6.10" + apollo-server-types "^0.2.10" + apollo-tracing "^0.8.11" fast-json-stable-stringify "^2.0.0" - graphql-extensions "^0.10.3" + graphql-extensions "^0.10.10" graphql-tag "^2.9.2" graphql-tools "^4.0.0" graphql-upload "^8.0.2" @@ -1730,24 +1978,24 @@ apollo-server-env@^2.4.3: node-fetch "^2.1.2" util.promisify "^1.0.0" -apollo-server-errors@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.3.tgz#83763b00352c10dc68fbb0d41744ade66de549ff" - integrity sha512-MO4oJ129vuCcbqwr5ZwgxqGGiLz3hCyowz0bstUF7MR+vNGe4oe3DWajC9lv4CxrhcqUHQOeOPViOdIo1IxE3g== +apollo-server-errors@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz#b70ef01322f616cbcd876f3e0168a1a86b82db34" + integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA== -apollo-server-express@^2.9.0, apollo-server-express@^2.9.3: - version "2.9.3" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.3.tgz#67573404030c2676be49a7bf97d423b8462e295c" - integrity sha512-Hkfs+ce6GqaoSzDOJs8Pj7W3YUjH0BzGglo5HMsOXOnjPZ0pJE9v8fmK76rlkITLw7GjvIq5GKlafymC31FMBw== +apollo-server-express@^2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.16.tgz#4c30b1769426c010b37943c0fb7766e5825973a0" + integrity sha512-ZDc7GP+piUm67alJ0DIE9f36tHcCiNm3PHMLIVJlVE/rcGwzRjV5rardRqeslljQiO2J+1IwXKwJ0/kRrZ4JvQ== dependencies: "@apollographql/graphql-playground-html" "1.6.24" "@types/accepts" "^1.3.5" "@types/body-parser" "1.17.1" "@types/cors" "^2.8.4" - "@types/express" "4.17.1" + "@types/express" "4.17.2" accepts "^1.3.5" - apollo-server-core "^2.9.3" - apollo-server-types "^0.2.4" + apollo-server-core "^2.9.16" + apollo-server-types "^0.2.10" body-parser "^1.18.3" cors "^2.8.4" express "^4.17.1" @@ -1757,57 +2005,57 @@ apollo-server-express@^2.9.0, apollo-server-express@^2.9.3: subscriptions-transport-ws "^0.9.16" type-is "^1.6.16" -apollo-server-plugin-base@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.4.tgz#63ea4fd0bbb6c4510bc8d0d2ad0a0684c8d0da8c" - integrity sha512-4rY+cBAIpQomGWYBtk8hHkLQWHrh5hgIBPQqmhXh00YFdcY+Ob1/cU2/2iqTcIzhtcaezsc8OZ63au6ahSBQqg== +apollo-server-plugin-base@^0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.10.tgz#33d3e2bb82fca22a00b6648a2f1c6b2cc032a8a0" + integrity sha512-/xT7UT/tbCDIoTQ4lcEQsJ0ACh7h7QG0BDmeSlDXjwDuENRI50bQ2QoluCMPitZXGe+FCQfLhvzFgzbsZGT0IA== dependencies: - apollo-server-types "^0.2.4" + apollo-server-types "^0.2.10" -apollo-server-testing@~2.9.3: - version "2.9.3" - resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.3.tgz#38a86b5fa0bce57f8ec4fb581e5419437178b3e2" - integrity sha512-n2bIcVXQNFzr84FZK1S0o4PFqwb1pPuIg/fymjPYjtFP2OHmLLvGRm+KaXhUjxEAUh+/9zAQLhmgx+p6GMUAhA== +apollo-server-testing@~2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.16.tgz#35e9b0b102a11bac8db2fce04281cb43e7993d45" + integrity sha512-CLfYZY2Htwzw6iPlFO32/SNXNstWQsvGd5/FQ8KEwRpNfYM4g0rAE98y/THEQTvTh0xPH+qWxA7CVQcc7/FMbQ== dependencies: - apollo-server-core "^2.9.3" + apollo-server-core "^2.9.16" -apollo-server-types@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.4.tgz#28864900ffc7f9711a859297c143a833fdb6aa43" - integrity sha512-G4FvBVgGQcTW6ZBS2+hvcDQkSfdOIKV+cHADduXA275v+5zl42g+bCaGd/hCCKTDRjmQvObLiMxH/BJ6pDMQgA== +apollo-server-types@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.10.tgz#017ee0c812e70b0846826834eb2c9eda036c1c7a" + integrity sha512-ke9ViPEWfW+2XLe66CaKGVZdS7duSLbamSKSprmmeMBd8s6tmjf0FumUVxV7X4quxPZi0OPo8x0LoLU7GWsmaA== dependencies: - apollo-engine-reporting-protobuf "^0.4.0" - apollo-server-caching "^0.5.0" + apollo-engine-reporting-protobuf "^0.4.4" + apollo-server-caching "^0.5.1" apollo-server-env "^2.4.3" -apollo-server@~2.9.3: - version "2.9.3" - resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.3.tgz#2a79fcee25da0b0673eb70d73839c40c3c4b8cca" - integrity sha512-JQoeseSo3yOBu3WJzju0NTreoqYckNILybgXNUOhdurE55VFpZ8dsBEO6nMfdO2y1A70W14mnnVWCBEm+1rE8w== +apollo-server@~2.9.16: + version "2.9.16" + resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.16.tgz#c0054ed70ecb637cb3f585ff46fb4a060730076f" + integrity sha512-dqB1shkjl9ne7DfSHXDH5sT70llr9zswLL+/g/4zt4/H+k+2pkD1BShQkNIK7PBYcVa8KvRAHXiHTXZ36GCspA== dependencies: - apollo-server-core "^2.9.3" - apollo-server-express "^2.9.3" + apollo-server-core "^2.9.16" + apollo-server-express "^2.9.16" express "^4.0.0" graphql-subscriptions "^1.0.0" graphql-tools "^4.0.0" -apollo-tracing@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.4.tgz#0117820c3f0ad3aa6daf7bf13ddbb923cbefa6de" - integrity sha512-DjbFW0IvHicSlTVG+vK+1WINfBMRCdPPHJSW/j65JMir9Oe56WGeqL8qz8hptdUUmLYEb+azvcyyGsJsiR3zpQ== +apollo-tracing@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.11.tgz#55822aac7381da77c703b52d35c4dab9393ec33c" + integrity sha512-Z0wDZ5QOBmpGoajB74ZKGTM7GzG6rqZRzAph4kxud6axcyNqUDKiKZ3Eere+NSLwvvt8M3qnPW4UJSUy/wwOXg== dependencies: apollo-server-env "^2.4.3" - graphql-extensions "^0.10.3" + graphql-extensions "^0.10.10" -apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9" - integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg== +apollo-utilities@1.3.3, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c" + integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw== dependencies: "@wry/equality" "^0.1.2" fast-json-stable-stringify "^2.0.0" ts-invariant "^0.4.0" - tslib "^1.9.3" + tslib "^1.10.0" aproba@^1.0.3: version "1.2.0" @@ -1872,6 +2120,11 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -1882,6 +2135,15 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.2.tgz#8f3c71d245ba349b6b64b4078f76f5576f1fd723" + integrity sha512-VXjh7lAL4KXKF2hY4FnEW9eRW6IhdvFW1sN/JwLbmECbCgACCnBHNyP3lFiYuttr0jxRN9Bsc5+G27dMseSWqQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.15.0" + function-bind "^1.1.1" + arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" @@ -1907,12 +2169,12 @@ assert@^1.4.1: object-assign "^4.1.1" util "0.10.3" -assertion-error-formatter@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-2.0.1.tgz#6bbdffaec8e2fa9e2b0eb158bfe353132d7c0a9b" - integrity sha512-cjC3jUCh9spkroKue5PDSKH5RFQ/KNuZJhk3GwHYmB/8qqETxLOmMdLH+ohi/VukNzxDlMvIe7zScvLoOdhb6Q== +assertion-error-formatter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz#be9c8825dee6a8a6c72183d915912d9b57d5d265" + integrity sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ== dependencies: - diff "^3.0.0" + diff "^4.0.1" pad-right "^0.2.2" repeat-string "^1.6.1" @@ -1958,21 +2220,11 @@ async-retry@^1.2.1: dependencies: retry "0.12.0" -async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -asyncro@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/asyncro/-/asyncro-3.0.0.tgz#3c7a732e263bc4a42499042f48d7d858e9c0134e" - integrity sha512-nEnWYfrBmA3taTiuiOoZYmgJ/CNrSoQLeLs29SeLcPu60yaw/mHDBHV0iOZ051fTvsTHxpCY+gXibqT9wbQYfg== - atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -2010,18 +2262,18 @@ babel-eslint@~10.0.3: eslint-visitor-keys "^1.0.0" resolve "^1.12.0" -babel-jest@^24.9.0, babel-jest@~24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" - integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== +babel-jest@^25.1.0, babel-jest@~25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.1.0.tgz#206093ac380a4b78c4404a05b3277391278f80fb" + integrity sha512-tz0VxUhhOE2y+g8R2oFrO/2VtVjA1lkJeavlhExuRBg3LdNJY9gwQ+Vcvqt9+cqy71MCTJhewvTB7Qtnnr9SWg== dependencies: - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" "@types/babel__core" "^7.1.0" - babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.9.0" - chalk "^2.4.2" - slash "^2.0.0" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^25.1.0" + chalk "^3.0.0" + slash "^3.0.0" babel-plugin-dynamic-import-node@^2.3.0: version "2.3.0" @@ -2030,20 +2282,21 @@ babel-plugin-dynamic-import-node@^2.3.0: dependencies: object.assign "^4.1.0" -babel-plugin-istanbul@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" - integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - find-up "^3.0.0" - istanbul-lib-instrument "^3.3.0" - test-exclude "^5.2.3" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" -babel-plugin-jest-hoist@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" - integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== +babel-plugin-jest-hoist@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.1.0.tgz#fb62d7b3b53eb36c97d1bc7fec2072f9bd115981" + integrity sha512-oIsopO41vW4YFZ9yNYoLQATnnN46lp+MZ6H4VvPKFkcc2/fkl3CfE/NZZSmnEIEsJRmJAgkVEK0R7Zbl50CpTw== dependencies: "@types/babel__traverse" "^7.0.6" @@ -2054,13 +2307,14 @@ babel-plugin-transform-runtime@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-preset-jest@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" - integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== +babel-preset-jest@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.1.0.tgz#d0aebfebb2177a21cde710996fce8486d34f1d33" + integrity sha512-eCGn64olaqwUMaugXsTtGAM2I0QTahjEtnRu0ql8Ie+gDWAc1N6wqN0k2NilnyTunM69Pad7gJY7LOtwLimoFQ== dependencies: + "@babel/plugin-syntax-bigint" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.9.0" + babel-plugin-jest-hoist "^25.1.0" babel-runtime@^6.22.0: version "6.26.0" @@ -2105,7 +2359,7 @@ bcryptjs@~2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= -becke-ch--regex--s0-0-v1--base--pl--lib@^1.2.0: +becke-ch--regex--s0-0-v1--base--pl--lib@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz#429ceebbfa5f7e936e78d73fbdc7da7162b20e20" integrity sha1-Qpzuu/pffpNueNc/vcfacWKyDiA= @@ -2115,39 +2369,16 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -bitcore-lib@^0.13.7: - version "0.13.19" - resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc" - integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w= - dependencies: - bn.js "=2.0.4" - bs58 "=2.0.0" - buffer-compare "=1.0.0" - elliptic "=3.0.3" - inherits "=2.0.1" - lodash "=3.10.1" - -"bitcore-message@github:CoMakery/bitcore-message#dist": - version "1.0.2" - resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf" - dependencies: - bitcore-lib "^0.13.7" +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== bluebird@^3.4.1: version "3.5.5" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== -bn.js@=2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480" - integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA= - -bn.js@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625" - integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU= - body-parser@1.19.0, body-parser@^1.18.3: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -2169,10 +2400,10 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bowser@2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.5.4.tgz#850fccfebde92165440279b5ab19be3c7f05cfe1" - integrity sha512-74GGwfc2nzYD19JCiA0RwCxdq7IY5jHeEaSrrgm/5kusEuK+7UK0qDG3gyzN47c4ViNyO4osaKtZE+aSV6nlpQ== +bowser@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.7.0.tgz#96eab1fa07fab08c1ec4c75977a7c8ddf8e0fe1f" + integrity sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w== boxen@^1.2.1: version "1.3.0" @@ -2211,10 +2442,12 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" browser-process-hrtime@^0.1.2: version "0.1.3" @@ -2228,19 +2461,14 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserslist@^4.6.0, browserslist@^4.6.6: - version "4.6.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" - integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA== +browserslist@^4.8.2, browserslist@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.3.tgz#65802fcd77177c878e015f0e3189f2c4f627ba44" + integrity sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg== dependencies: - caniuse-lite "^1.0.30000984" - electron-to-chromium "^1.3.191" - node-releases "^1.1.25" - -bs58@=2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" - integrity sha1-crcTvtIjoKxRi72g484/SBfznrU= + caniuse-lite "^1.0.30001017" + electron-to-chromium "^1.3.322" + node-releases "^1.1.44" bser@^2.0.0: version "2.1.0" @@ -2249,11 +2477,6 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" -buffer-compare@=1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2" - integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI= - buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -2324,10 +2547,10 @@ camelize@1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= -caniuse-lite@^1.0.30000984: - version "1.0.30000989" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" - integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== +caniuse-lite@^1.0.30001017: + version "1.0.30001020" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001020.tgz#3f04c1737500ffda78be9beb0b5c1e2070e15926" + integrity sha512-yWIvwA68wRHKanAVS1GjN8vajAv7MBFshullKCeq/eKpK7pJBVDgFFEqvgWTkcP2+wIDeQGYFRXECjKZnLkUjA== capture-exit@^2.0.0: version "2.0.0" @@ -2367,6 +2590,17 @@ chalk@2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -2376,6 +2610,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -2391,7 +2633,7 @@ cheerio-advanced-selectors@~2.0.1: resolved "https://registry.yarnpkg.com/cheerio-advanced-selectors/-/cheerio-advanced-selectors-2.0.1.tgz#fb5ec70a4599e8cec1cf669c6d9b90a3fa969c48" integrity sha512-5wHR8bpiD5pdUtaS81A6hnJezzoDzL1TLWfK6bxnLkIgEKPV26BlOdMCcvuj3fTE7JSalsTUeNU7AOD/u6bYhw== -cheerio@~1.0.0-rc.2, cheerio@~1.0.0-rc.3: +cheerio@~1.0.0-rc.3: version "1.0.0-rc.3" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== @@ -2403,7 +2645,7 @@ cheerio@~1.0.0-rc.2, cheerio@~1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@^2.1.5, chokidar@^2.1.8: +chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -2422,6 +2664,21 @@ chokidar@^2.1.5, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.2.2: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" @@ -2434,6 +2691,13 @@ chrono-node@~1.3.11: dependencies: moment "2.21.0" +chrono-node@~1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-1.4.2.tgz#0c7fc1f264e60a660c2b2dab753a3f285dbfd8c9" + integrity sha512-fsb82wPDHVZl3xtche8k4ZZtNwf81/ZMueil2ANpSfogUAEa3BuzZAar7ObLXi1ptMjBzdzA6ys/bFq1oBjO8w== + dependencies: + dayjs "^1.8.19" + ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" @@ -2454,16 +2718,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-stack@^2.0.0: +clean-stack@^2.0.0, clean-stack@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -clean-stack@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.0.0.tgz#301bfa9e8dd2d3d984c0e542f7aa67b996f63e0a" - integrity sha512-VEoL9Qh7I8s8iHnV53DaeWSt8NJ0g3khMfK6NiCPB7H657juhro+cSw2O88uo3bo0c0X5usamtXk0/Of0wXa5A== - cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" @@ -2499,14 +2758,14 @@ clipboardy@1.2.2: arch "^2.1.0" execa "^0.8.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" clone-response@^1.0.2: version "1.0.2" @@ -2530,6 +2789,11 @@ collapse-white-space@^1.0.3: resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.5.tgz#c2495b699ab1ed380d29a1091e01063e75dbbe3a" integrity sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ== +collect-v8-coverage@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz#150ee634ac3650b71d9c985eb7f608942334feb1" + integrity sha512-VKIhJgvk8E1W28m5avZ2Gv2Ruv5YiF56ug2oclvaG9md69BuZImMG2sk9g7QNKLUbtYAKQjXjYxbYZVUlMMKmQ== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -2545,11 +2809,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colors@^1.1.2: version "1.3.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" @@ -2562,17 +2838,20 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.8.1, commander@^2.9.0, commander@~2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@^2.9.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@~2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= - dependencies: - graceful-readlink ">= 1.0.0" +commander@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + +commander@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c" + integrity sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA== commondir@^1.0.1: version "1.0.1" @@ -2638,10 +2917,10 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.1.0, convert-source-map@^1.4.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" - integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" @@ -2650,16 +2929,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + cookiejar@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -2670,20 +2949,20 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-compat@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.2.1.tgz#0cbdbc2e386e8e00d3b85dc81c848effec5b8150" - integrity sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A== +core-js-compat@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.3.tgz#41e281ca771209d5f2eb63ce34f96037d0928538" + integrity sha512-Y3YNGU3bU1yrnzVodop23ghArbKv4IqkZg9MMOWv/h7KT6NRk1/SzHhWDDlubg2+tlcUzAqgj1/GyeJ9fUKMeg== dependencies: - browserslist "^4.6.6" - semver "^6.3.0" + browserslist "^4.8.3" + semver "7.0.0" core-js@^2.4.0, core-js@^2.6.5: version "2.6.9" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.1: +core-js@^3.0.1, core-js@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09" integrity sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw== @@ -2708,28 +2987,12 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" -cross-env@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" - integrity sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ== +cross-env@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.0.tgz#5a3b2ddce51ec713ea58f2fb79ce22e65b4f5479" + integrity sha512-rV6M9ldNgmwP7bx5u6rZsTbYidzwvrwIYZnT08hSGLcQCcggofgFW+sNe7IhA1SRauPS0QuLbbX+wdNtpqE5CQ== dependencies: - cross-spawn "^6.0.5" - -cross-fetch@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723" - integrity sha1-pH/09/xxLauo9qaVoRyUhEDUVyM= - dependencies: - node-fetch "2.1.2" - whatwg-fetch "2.0.4" - -cross-fetch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== - dependencies: - node-fetch "2.6.0" - whatwg-fetch "3.0.0" + cross-spawn "^7.0.1" cross-spawn@^5.0.1: version "5.1.0" @@ -2751,6 +3014,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.0, cross-spawn@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" + integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -2771,61 +3043,65 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": +cssom@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" - integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== +cssstyle@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.1.0.tgz#99f50a3aa21d4af16e758ae3e454dcf5940b9122" + integrity sha512-1iwCdymVYhMdQWiZ+9mB7x+urdNLPGTWsIZt6euFk8Yi+dOERK2ccoAUA3Bl8I5vmK5qfz/eLkBRyLbs42ov4A== dependencies: - cssom "0.3.x" + cssom "~0.3.6" -cucumber-expressions@^6.0.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-6.6.2.tgz#d89640eccc72a78380b6c210eae36a64e7462b81" - integrity sha512-WcFSVBiWNLJbIcAAC3t/ACU46vaOKfe1UIF5H3qveoq+Y4XQm9j3YwHurQNufRKBBg8nCnpU7Ttsx7egjS3hwA== +cucumber-expressions@^8.1.0: + version "8.2.1" + resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-8.2.1.tgz#e250063993350df106a8664c90a414814f555e2d" + integrity sha512-6n5JKbAzXfIiwyu2UyUcOmO83QmuSme25+Dw2taK6VNOybOfRkh4yNMA9VtuAJHOmsX3/8l0OVjTbE8lHnjOHA== dependencies: - becke-ch--regex--s0-0-v1--base--pl--lib "^1.2.0" + becke-ch--regex--s0-0-v1--base--pl--lib "^1.4.0" + xregexp "^4.2.4" -cucumber-tag-expressions@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-1.1.1.tgz#7f5c7b70009bc2b666591bfe64854578bedee85a" - integrity sha1-f1x7cACbwrZmWRv+ZIVFeL7e6Fo= +cucumber-tag-expressions@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-2.0.2.tgz#aac27aae3690818ec15235bd056282dad8a2d2b8" + integrity sha512-DohmT4X641KX/sb96bdb7J2kXNcQBPrYmf3Oc5kiHCLfzFMWx/o2kB4JvjvQPZnYuA9lRt6pqtArM5gvUn4uzw== -cucumber@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-5.1.0.tgz#7b166812c255bec7eac4b0df7007a40d089c895d" - integrity sha512-zrl2VYTBRgvxucwV2GKAvLqcfA1Naeax8plPvWgPEzl3SCJiuPPv3WxBHIRHtPYcEdbHDR6oqLpZP4bJ8UIdmA== +cucumber@~6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-6.0.5.tgz#cdc752ad18b551bcf7bc92774c925302f4408714" + integrity sha512-x+W9Fwk6TvcapQsYMxwFU5AsQJDOIJVGrPKmH15OC7jzb9/Dk7Hb0ZAyw4WcpaDcUDRc8bi2k2yJejDp5eTRlg== dependencies: - "@babel/polyfill" "^7.2.3" - assertion-error-formatter "^2.0.1" + assertion-error-formatter "^3.0.0" bluebird "^3.4.1" cli-table3 "^0.5.1" colors "^1.1.2" - commander "^2.9.0" - cross-spawn "^6.0.5" - cucumber-expressions "^6.0.0" - cucumber-tag-expressions "^1.1.1" + commander "^3.0.1" + cucumber-expressions "^8.1.0" + cucumber-tag-expressions "^2.0.2" duration "^0.2.1" - escape-string-regexp "^1.0.5" - figures "2.0.0" - gherkin "^5.0.0" + escape-string-regexp "^2.0.0" + figures "^3.0.0" + gherkin "5.0.0" glob "^7.1.3" - indent-string "^3.1.0" + indent-string "^4.0.0" is-generator "^1.0.2" - is-stream "^1.1.0" + is-stream "^2.0.0" knuth-shuffle-seeded "^1.0.6" - lodash "^4.17.10" + lodash "^4.17.14" mz "^2.4.0" progress "^2.0.0" resolve "^1.3.3" - serialize-error "^3.0.0" + serialize-error "^4.1.0" stack-chain "^2.0.0" stacktrace-js "^2.0.0" - string-argv "0.1.1" + string-argv "^0.3.0" title-case "^2.1.1" util-arity "^1.0.2" verror "^1.9.0" @@ -2850,7 +3126,7 @@ dasherize@2.0.0: resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= -data-urls@^1.0.0: +data-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== @@ -2859,18 +3135,35 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-fns@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.1.0.tgz#0d7e806c3cefe14a943532dbf968995ccfd46bd9" - integrity sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA== +date-fns@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.9.0.tgz#d0b175a5c37ed5f17b97e2272bbc1fa5aec677d2" + integrity sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= + +dayjs@^1.8.19: + version "1.8.19" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" + integrity sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg== + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2878,13 +3171,6 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2919,10 +3205,10 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09" - integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww== +deepmerge@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== defer-to-connect@^1.0.1: version "1.0.2" @@ -2993,10 +3279,10 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-newline@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== dicer@0.3.0: version "0.3.0" @@ -3005,15 +3291,22 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -diff-sequences@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" - integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" + integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== -diff@^3.0.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" dns-prefetch-control@0.2.0: version "0.2.0" @@ -3108,10 +3401,10 @@ dotenv@^4.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= -dotenv@~8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.1.0.tgz#d811e178652bfb8a1e593c6dd704ec7e90d85ea2" - integrity sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA== +dotenv@~8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== duplexer3@^0.1.4: version "0.1.4" @@ -3146,20 +3439,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.191: - version "1.3.237" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.237.tgz#39c5d1da59d6fd16ff705b97e772bb3b5dfda7e4" - integrity sha512-SPAFjDr/7iiVK2kgTluwxela6eaWjjFkS9rO/iYpB/KGXgccUom5YC7OIf19c8m8GGptWxLU0Em8xM64A/N7Fg== - -elliptic@=3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" - integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU= - dependencies: - bn.js "^2.0.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" +electron-to-chromium@^1.3.322: + version "1.3.322" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" + integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA== emoji-regex@^7.0.1: version "7.0.3" @@ -3219,6 +3502,22 @@ es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.7.0: is-regex "^1.0.4" object-keys "^1.0.12" +es-abstract@^1.15.0: + version "1.16.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.3.tgz#52490d978f96ff9f89ec15b5cf244304a5bca161" + integrity sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.7.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" + es-to-primitive@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" @@ -3228,6 +3527,15 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.46: version "0.10.50" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778" @@ -3246,33 +3554,6 @@ es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.3.0.tgz#96edb9f2fdb01995822b263dd8aadab6748181bc" - integrity sha1-lu258v2wGZWCKyY92KratnSBgbw= - -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promise@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.0.1.tgz#ccc4963e679f0ca9fb187c777b9e583d3c7573c2" - integrity sha1-zMSWPmefDKn7GHx3e55YPTx1c8I= - -es6-promise@~4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.0.5.tgz#7882f30adde5b240ccfa7f7d78c548330951ae42" - integrity sha1-eILzCt3lskDM+n99eMVIMwlRrkI= - -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" @@ -3286,27 +3567,32 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.9.1: - version "1.12.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541" - integrity sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg== +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.11.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29" + integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw== dependencies: - esprima "^3.1.3" + esprima "^4.0.1" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@~6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.3.0.tgz#e73b48e59dc49d950843f3eb96d519e2248286a3" - integrity sha512-EWaGjlDAZRzVFveh2Jsglcere2KK5CJBhkNSa1xs3KfMUGdRiT7lG089eqPdvlzWHpAqaekubOsOMu8W8Yk71A== +eslint-config-prettier@~6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz#430d24822e82f7deb1e22a435bfa3999fae4ad64" + integrity sha512-k4E14HBtcLv0uqThaI6I/n1LEqROp8XaPu6SO9Z32u5NlGRC07Enu1Bh2KEFw4FNHbekH8yzbIU9kUGxbiGmCA== dependencies: get-stdin "^6.0.0" @@ -3323,62 +3609,64 @@ eslint-import-resolver-node@^0.3.2: debug "^2.6.9" resolve "^1.5.0" -eslint-module-utils@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw== +eslint-module-utils@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c" + integrity sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw== dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== +eslint-plugin-es@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b" + integrity sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng== dependencies: - eslint-utils "^1.4.2" + eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@~2.18.2: - version "2.18.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" - integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== +eslint-plugin-import@~2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz#d749a7263fb6c29980def8e960d380a6aa6aecaa" + integrity sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ== dependencies: array-includes "^3.0.3" + array.prototype.flat "^1.2.1" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" + eslint-module-utils "^2.4.1" has "^1.0.3" minimatch "^3.0.4" object.values "^1.1.0" read-pkg-up "^2.0.0" - resolve "^1.11.0" + resolve "^1.12.0" -eslint-plugin-jest@~22.17.0: - version "22.17.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6" - integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q== +eslint-plugin-jest@~23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.6.0.tgz#508b32f80d44058c8c01257c0ee718cfbd521e9d" + integrity sha512-GH8AhcFXspOLqak7fqnddLXEJsrFyvgO8Bm60SexvKSn1+3rWYESnCiWUOCUcBTprNSDSE4CtAZdM4EyV6gPPw== dependencies: - "@typescript-eslint/experimental-utils" "^1.13.0" + "@typescript-eslint/experimental-utils" "^2.5.0" + micromatch "^4.0.2" -eslint-plugin-node@~10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== +eslint-plugin-node@~11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz#365944bb0804c5d1d501182a9bc41a0ffefed726" + integrity sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg== dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" ignore "^5.1.1" minimatch "^3.0.4" resolve "^1.10.1" semver "^6.1.0" -eslint-plugin-prettier@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz#8695188f95daa93b0dc54b249347ca3b79c4686d" - integrity sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA== +eslint-plugin-prettier@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" + integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== dependencies: prettier-linter-helpers "^1.0.0" @@ -3392,14 +3680,6 @@ eslint-plugin-standard@~4.0.1: resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== -eslint-scope@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -3408,22 +3688,29 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" - integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q== +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== dependencies: - eslint-visitor-keys "^1.0.0" + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@~6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.3.0.tgz#1f1a902f67bfd4c354e7288b81e40654d927eb6a" - integrity sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow== +eslint@~6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -3432,19 +3719,19 @@ eslint@~6.3.0: debug "^4.0.1" doctrine "^3.0.0" eslint-scope "^5.0.0" - eslint-utils "^1.4.2" + eslint-utils "^1.4.3" eslint-visitor-keys "^1.1.0" - espree "^6.1.1" + espree "^6.1.2" esquery "^1.0.1" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^11.7.0" + globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.4.1" + inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" @@ -3453,7 +3740,7 @@ eslint@~6.3.0: minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.2" + optionator "^0.8.3" progress "^2.0.0" regexpp "^2.0.1" semver "^6.1.2" @@ -3463,21 +3750,16 @@ eslint@~6.3.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de" - integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ== +espree@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" + integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== dependencies: - acorn "^7.0.0" - acorn-jsx "^5.0.2" + acorn "^7.1.0" + acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3560,6 +3842,22 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3583,17 +3881,17 @@ expect-ct@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.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" - integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== +expect@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-25.1.0.tgz#7e8d7b06a53f7d66ec927278db3304254ee683ee" + integrity sha512-wqHzuoapQkhc3OKPlrpetsfueuEiMf3iWh0R8+duCu9PIjXoP7HgD5aeypwTnXUAjC8aMsiVDaWwlbJ1RlQ38g== dependencies: - "@jest/types" "^24.9.0" - ansi-styles "^3.2.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.9.0" + "@jest/types" "^25.1.0" + ansi-styles "^4.0.0" + jest-get-type "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-regex-util "^25.1.0" express@^4.0.0, express@^4.17.1: version "4.17.1" @@ -3691,7 +3989,7 @@ extsprintf@^1.2.0: faker@Marak/faker.js#master: version "4.1.0" - resolved "https://codeload.github.com/Marak/faker.js/tar.gz/10bfb9f467b0ac2b8912ffc15690b50ef3244f09" + resolved "https://codeload.github.com/Marak/faker.js/tar.gz/3b2fa4aebccee52ae1bafc15d575061fb30c3cf1" fast-deep-equal@^2.0.1: version "2.0.1" @@ -3703,16 +4001,34 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== +fast-glob@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@~2.0.4: +fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" + fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" @@ -3725,13 +4041,6 @@ feature-policy@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: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - figures@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9" @@ -3761,6 +4070,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -3797,6 +4113,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -3908,6 +4232,11 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" +fsevents@^2.1.2, fsevents@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3932,6 +4261,11 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3964,7 +4298,7 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" -get-stream@^5.1.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== @@ -3990,10 +4324,10 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gherkin@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5" - integrity sha1-aEu7A63STq9731RPWAM+so+zxtU= +gherkin@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.0.0.tgz#96def41198ec3908258b511af74f655a2764d2a1" + integrity sha1-lt70EZjsOQgli1Ea909lWidk0qE= glob-parent@^3.1.0: version "3.1.0" @@ -4003,17 +4337,17 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" - integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: is-glob "^4.0.1" -glob@7.1.4, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -4029,11 +4363,30 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" -globals@^11.1.0, globals@^11.7.0: +globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.3.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13" + integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw== + dependencies: + type-fest "^0.8.1" + +globby@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154" + integrity sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -4068,15 +4421,15 @@ got@^9.6.0, got@~9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= +graceful-fs@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== graphql-auth-directives@^2.1.0: version "2.1.0" @@ -4097,56 +4450,40 @@ graphql-custom-directives@~0.2.14: moment "^2.22.2" numeral "^2.0.6" -graphql-extensions@^0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.3.tgz#9e37f3bd26309c40b03a0be0e63e02b3f99d52ea" - integrity sha512-kwU0gUe+Qdfr8iZYT91qrPSwQNgPhB/ClF1m1LEPdxlptk5FhFmjpxAcbMZ8q7j0kjfnbp2IeV1OhRDCEPqz2w== +graphql-extensions@^0.10.10: + version "0.10.10" + resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.10.tgz#6b89d6b171f02a83bd4252f1e71c8d69147e7e2d" + integrity sha512-pNb1DmUk6vsGtCjCRecpKoXadKNMyKxyLyE9IX65N9aKSmLL+AF7dJOOc4MWhdaAXlzxaDDhe54GpaOfoH7AOw== dependencies: - "@apollographql/apollo-tools" "^0.4.0" + "@apollographql/apollo-tools" "^0.4.3" apollo-server-env "^2.4.3" - apollo-server-types "^0.2.4" - -graphql-import@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223" - integrity sha512-YpwpaPjRUVlw2SN3OPljpWbVRWAhMAyfSba5U47qGMOSsPLi2gYeJtngGpymjm9nk57RFWEpjqwh4+dpYuFAPw== - dependencies: - lodash "^4.17.4" - resolve-from "^4.0.0" + apollo-server-types "^0.2.10" graphql-iso-date@~3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96" integrity sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q== -graphql-middleware-sentry@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/graphql-middleware-sentry/-/graphql-middleware-sentry-3.2.0.tgz#a72872ae4b9fbe286980023a3c531286b8258df7" - integrity sha512-tIrxJIL+MRh2pxoot+EYJIrFfPgB3OF4nqmmh1gi54q/V1rgv7w46Ahn2jgl2nTu4bKw6mtGVF4CKikWCMPa5Q== +graphql-middleware-sentry@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/graphql-middleware-sentry/-/graphql-middleware-sentry-3.2.1.tgz#b5653d78903d655cf4212a602cfa6e26689cda07" + integrity sha512-lAwmHwsyey1db6scQg32javmqAFifabhqPIr0SUzx46O4kvjQlLZZn7KrRT12XDwgW7i6goAotdSPl9Fq+TBrQ== -graphql-middleware@~3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-3.0.5.tgz#8cbce45ce4be95bd4600ffe5332e1936ec721e10" - integrity sha512-84HBmvJlMKxgavbygbRgn2i1fOSrZ7KhxWToWDpe4xOgnWxGb5WY1rwRIkyK25ajh4qRwLtGPbBxtClzwGngCQ== +graphql-middleware@~4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-4.0.2.tgz#eb589bf428e1795e48cd6a3cfeeba0807b996ebd" + integrity sha512-ESVDvMXeN00S1BNsjNS18uExcR16J8zbT31CuKcpyeBa7IMbidG0Pnqnu5P1wKkJLmPmKOfCljWlhXpD/Fawqg== dependencies: graphql-tools "^4.0.5" -graphql-request@~1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe" - integrity sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg== +graphql-shield@~7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.8.tgz#5ade058610e1b247b0762cb2424d121e9a5f5b46" + integrity sha512-KxYMhoiv5lsHcO0HZDhYjjWLbwzreDCmqmnkLRsLNY+6P0q81KSowoNVPuoAsItkjr9m5Fa6IDObOVxSTSt5Lw== dependencies: - cross-fetch "2.2.2" - -graphql-shield@~6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.1.0.tgz#7298af72167e7c9fd19a36fac9b425b94025a393" - integrity sha512-dIZ6ABnUn3XQtIzw9/9f8wFmZoY5XZlsHgkxSKF+N/oXmKvQoi11J5/y/jxJTBmKYi/2JZ12C1JjDn5TOopn+w== - dependencies: - "@types/yup" "0.26.23" - lightercollective "^0.3.0" - object-hash "^1.3.1" - yup "^0.27.0" + "@types/yup" "0.26.28" + object-hash "^2.0.0" + yup "^0.28.0" graphql-subscriptions@^1.0.0: version "1.1.0" @@ -4160,25 +4497,6 @@ graphql-tag@^2.9.2, graphql-tag@~2.10.1: resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02" integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg== -graphql-toolkit@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/graphql-toolkit/-/graphql-toolkit-0.5.0.tgz#7371c21809898286b2a9e164b45469586cf64498" - integrity sha512-tBgqyWPHI/Pgt+jp+uLZZy2EBCzjd6yWAu73oUlmrhgg7XM6f1ONotVHvIO2MK7j8khR+ex/cUe8FgpS1i845w== - dependencies: - "@kamilkisiela/graphql-tools" "4.0.6" - "@types/glob" "7.1.1" - aggregate-error "3.0.0" - asyncro "^3.0.0" - cross-fetch "^3.0.4" - deepmerge "4.0.0" - glob "7.1.4" - graphql-import "0.7.1" - is-glob "4.0.1" - is-valid-path "0.1.1" - lodash "4.17.15" - tslib "^1.9.3" - valid-url "1.0.9" - graphql-tools@^4.0.0, graphql-tools@^4.0.4, graphql-tools@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.5.tgz#d2b41ee0a330bfef833e5cdae7e1f0b0d86b1754" @@ -4200,10 +4518,10 @@ graphql-upload@^8.0.2: http-errors "^1.7.2" object-path "^0.11.4" -graphql@^14.2.1, graphql@^14.5.4: - version "14.5.4" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.4.tgz#b33fe957854e90c10d4c07c7d26b6c8e9f159a13" - integrity sha512-dPLvHoxy5m9FrkqWczPPRnH0X80CyvRE6e7Fa5AWEqEAzg9LpxHvKh24po/482E6VWHigOkAmb4xCp6P9yT9gw== +graphql@^14.2.1, graphql@^14.6.0: + version "14.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.6.0.tgz#57822297111e874ea12f5cd4419616930cd83e49" + integrity sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg== dependencies: iterall "^1.2.2" @@ -4212,17 +4530,6 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -handlebars@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4236,6 +4543,13 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -4246,11 +4560,21 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -4301,38 +4625,35 @@ has@^1.0.1, has@^1.0.3: dependencies: function-bind "^1.1.1" -hash.js@^1.0.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - he@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/he/-/he-0.5.0.tgz#2c05ffaef90b68e860f3fd2b54ef580989277ee2" integrity sha1-LAX/rvkLaOhg8/0rVO9YCYknfuI= +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + helmet-crossdomain@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== -helmet-csp@2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.1.tgz#39939a84ca3657ee3cba96f296169ccab02f97d5" - integrity sha512-HgdXSJ6AVyXiy5ohVGpK6L7DhjI9KVdKVB1xRoixxYKsFXFwoVqtLKgDnfe3u8FGGKf9Ml9k//C9rnncIIAmyA== +helmet-csp@2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.4.tgz#801382bac98f2f88706dc5c89d95c7e31af3a4a9" + integrity sha512-qUgGx8+yk7Xl8XFEGI4MFu1oNmulxhQVTlV8HP8tV3tpfslCs30OZz/9uQqsWPvDISiu/NwrrCowsZBhFADYqg== dependencies: - bowser "2.5.4" + bowser "^2.7.0" camelize "1.0.0" content-security-policy-builder "2.1.0" dasherize "2.0.0" -helmet@~3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.21.0.tgz#e7c5e2ed3b8b7f42d2e387004a87198b295132cc" - integrity sha512-TS3GryQMPR7n/heNnGC0Cl3Ess30g8C6EtqZyylf+Y2/kF4lM8JinOR90rzIICsw4ymWTvji4OhDmqsqxkLrcg== +helmet@~3.21.2: + version "3.21.2" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.21.2.tgz#7e2a19d5f6d898a77b5d2858e8e4bb2cda59f19f" + integrity sha512-okUo+MeWgg00cKB8Csblu8EXgcIoDyb5ZS/3u0W4spCimeVuCUvVZ6Vj3O2VJ1Sxpyb8jCDvzu0L1KKT11pkIg== dependencies: depd "2.0.0" dns-prefetch-control "0.2.0" @@ -4341,7 +4662,7 @@ helmet@~3.21.0: feature-policy "0.3.0" frameguard "3.1.0" helmet-crossdomain "0.4.0" - helmet-csp "2.9.1" + helmet-csp "2.9.4" hide-powered-by "1.1.0" hpkp "2.0.0" hsts "2.2.0" @@ -4386,7 +4707,22 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" -htmlparser2@^3.10.0, htmlparser2@^3.9.1: +html-escaper@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" + integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + +html-to-text@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-5.1.1.tgz#2d89db7bf34bc7bcb7d546b1b228991a16926e87" + integrity sha512-Bci6bD/JIfZSvG4s0gW/9mMKwBRoe/1RWLxUME/d6WUSZCdY7T60bssf/jFf7EYXRyqU4P5xdClVqiYU0/ypdA== + dependencies: + he "^1.2.0" + htmlparser2 "^3.10.1" + lodash "^4.17.11" + minimist "^1.2.0" + +htmlparser2@^3.10.0, htmlparser2@^3.10.1, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4434,13 +4770,18 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -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== +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== dependencies: - agent-base "^4.1.0" - debug "^3.1.0" + agent-base "5" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" @@ -4471,7 +4812,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1: +ignore@^5.1.1, ignore@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== @@ -4494,24 +4835,29 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^3.1.0, indent-string@^3.2.0: +indent-string@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -4525,7 +4871,7 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.1, inherits@=2.0.1: +inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= @@ -4540,10 +4886,10 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^6.4.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.1.tgz#8bfb7a5ac02dac6ff641ac4c5ff17da112fcdb42" - integrity sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw== +inquirer@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a" + integrity sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ== dependencies: ansi-escapes "^4.2.1" chalk "^2.4.2" @@ -4579,6 +4925,11 @@ ip-regex@^1.0.1: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" integrity sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0= +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + ip-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.1.0.tgz#5ad62f685a14edb421abebc2fff8db94df67b455" @@ -4625,6 +4976,13 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4698,11 +5056,6 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4735,20 +5088,6 @@ is-generator@^1.0.2: resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM= -is-glob@4.0.1, is-glob@^4.0.0, is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-glob@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4756,6 +5095,13 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -4764,13 +5110,6 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-invalid-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" - integrity sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ= - dependencies: - is-glob "^2.0.0" - is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -4783,6 +5122,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" @@ -4795,11 +5139,6 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -4817,20 +5156,19 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-reachable@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-reachable/-/is-reachable-3.1.0.tgz#d75288458fff25ab7befe13ba71ba11802300184" - integrity sha512-bpzlmReadJjGejiA3ITIrWNuBvSC6w8tlhorDjk58Ua8qgvd8CWpwk0am6oxKjhJ1RPpetpgcRSANZJFIC7SZA== +is-reachable@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-reachable/-/is-reachable-4.0.0.tgz#dcd6fe1d011eb1298030723979b785ce55186400" + integrity sha512-eCTBKm9K6nO3H1S3BrJBAqZJIVXKNdwDuGl6KHf1bnf/bn02BvEe+l+MypjsxbqZ7mt5oMhu+bS/mm7G2FRW3A== dependencies: arrify "^2.0.1" got "^9.6.0" is-port-reachable "^2.0.1" p-any "^2.1.0" - p-timeout "^3.1.0" - port-numbers "^4.0.7" - prepend-http "^2.0.0" + p-timeout "^3.2.0" + prepend-http "^3.0.1" router-ips "^1.0.0" - url-parse "^1.4.6" + url-parse "^1.4.7" is-redirect@^1.0.0: version "1.0.0" @@ -4868,6 +5206,11 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-symbol@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -4875,7 +5218,7 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.0" -is-typedarray@~1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -4888,22 +5231,15 @@ is-uri@~1.2.0: parse-uri "~1.0.0" punycode2 "~1.0.0" -is-valid-path@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" - integrity sha1-EQ+f90w39mPh7HkV60UfLbk6yd8= - dependencies: - is-invalid-path "^0.1.0" - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" + integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" @@ -4920,10 +5256,10 @@ iso-639-3@~1.1.0: resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.1.0.tgz#83722daf55490a707c318ae18a33ba3bab06c843" integrity sha512-l3BAnxNpyRIZA4mEzI2md/YVrxQ3hI8hiQe7TFyQknjyOh8vCzobZuAXTFHELco0FBkYRx4FkAlIqkKrHhnzgw== -iso-639-3@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.2.0.tgz#eee1f5e6ca2bbb33e3ecc910857c1c12e8b295be" - integrity sha512-jNvD2P4JHNckQH7pc0R0SQ4oPCpyEtgs0nTtjB+DZCUDdygz0cOAxlcnq5KgNjjsqMHbR4Sbgwz2+DflzAZvlQ== +iso-639-3@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-2.0.0.tgz#5844c6b885cbeac3571d407de5b5fdcb92f3505f" + integrity sha512-Pp+ctEs/pna6/rj05a5VR3qYxJHBZi95wp20C6Snf/WeghrkR/4G44LPJFqlbyo67XntkcUaxwrGmMeyY+F4mA== isobject@^2.0.0: version "2.1.0" @@ -4947,420 +5283,408 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== +istanbul-lib-instrument@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz#53321a7970f076262fd3292c8f9b2e4ac544aae1" + integrity sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ== dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" + "@babel/core" "^7.7.5" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" -istanbul-lib-report@^2.0.4: - version "2.0.8" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" -istanbul-lib-source-maps@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== dependencies: debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" + istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" - integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA== +istanbul-reports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.0.tgz#d4d16d035db99581b6194e119bbf36c963c5eb70" + integrity sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A== dependencies: - handlebars "^4.1.2" + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== -jest-changed-files@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" - integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== +jest-changed-files@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.1.0.tgz#73dae9a7d9949fdfa5c278438ce8f2ff3ec78131" + integrity sha512-bdL1aHjIVy3HaBO3eEQeemGttsq1BDlHgWcOjEOIAcga7OOEGWHD2WSu8HhL7I1F0mFFyci8VKU4tRNk+qtwDA== dependencies: - "@jest/types" "^24.9.0" - execa "^1.0.0" - throat "^4.0.0" + "@jest/types" "^25.1.0" + execa "^3.2.0" + throat "^5.0.0" -jest-cli@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" - integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== +jest-cli@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.1.0.tgz#75f0b09cf6c4f39360906bf78d580be1048e4372" + integrity sha512-p+aOfczzzKdo3AsLJlhs8J5EW6ffVidfSZZxXedJ0mHPBOln1DccqFmGCoO8JWd4xRycfmwy1eoQkMsF8oekPg== dependencies: - "@jest/core" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" + "@jest/core" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" exit "^0.1.2" - import-local "^2.0.0" + import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" + jest-config "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" prompts "^2.0.1" realpath-native "^1.1.0" - yargs "^13.3.0" + yargs "^15.0.0" -jest-config@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" - integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== +jest-config@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.1.0.tgz#d114e4778c045d3ef239452213b7ad3ec1cbea90" + integrity sha512-tLmsg4SZ5H7tuhBC5bOja0HEblM0coS3Wy5LTCb2C8ZV6eWLewHyK+3qSq9Bi29zmWQ7ojdCd3pxpx4l4d2uGw== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.9.0" - "@jest/types" "^24.9.0" - babel-jest "^24.9.0" - chalk "^2.0.1" + "@jest/test-sequencer" "^25.1.0" + "@jest/types" "^25.1.0" + babel-jest "^25.1.0" + chalk "^3.0.0" glob "^7.1.1" - jest-environment-jsdom "^24.9.0" - jest-environment-node "^24.9.0" - jest-get-type "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - micromatch "^3.1.10" - pretty-format "^24.9.0" + jest-environment-jsdom "^25.1.0" + jest-environment-node "^25.1.0" + jest-get-type "^25.1.0" + jest-jasmine2 "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + micromatch "^4.0.2" + pretty-format "^25.1.0" realpath-native "^1.1.0" -jest-diff@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" - integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== +jest-diff@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad" + integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw== dependencies: - chalk "^2.0.1" - diff-sequences "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" + chalk "^3.0.0" + diff-sequences "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" -jest-docblock@^24.3.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" - integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== +jest-docblock@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.1.0.tgz#0f44bea3d6ca6dfc38373d465b347c8818eccb64" + integrity sha512-370P/mh1wzoef6hUKiaMcsPtIapY25suP6JqM70V9RJvdKLrV4GaGbfUseUVk4FZJw4oTZ1qSCJNdrClKt5JQA== dependencies: - detect-newline "^2.1.0" + detect-newline "^3.0.0" -jest-each@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" - integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== +jest-each@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.1.0.tgz#a6b260992bdf451c2d64a0ccbb3ac25e9b44c26a" + integrity sha512-R9EL8xWzoPySJ5wa0DXFTj7NrzKpRD40Jy+zQDp3Qr/2QmevJgkN9GqioCGtAJ2bW9P/MQRznQHQQhoeAyra7A== dependencies: - "@jest/types" "^24.9.0" - chalk "^2.0.1" - jest-get-type "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + jest-get-type "^25.1.0" + jest-util "^25.1.0" + pretty-format "^25.1.0" -jest-environment-jsdom@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" - integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== +jest-environment-jsdom@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.1.0.tgz#6777ab8b3e90fd076801efd3bff8e98694ab43c3" + integrity sha512-ILb4wdrwPAOHX6W82GGDUiaXSSOE274ciuov0lztOIymTChKFtC02ddyicRRCdZlB5YSrv3vzr1Z5xjpEe1OHQ== dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" - jsdom "^11.5.1" + "@jest/environment" "^25.1.0" + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + jsdom "^15.1.1" -jest-environment-node@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" - integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== +jest-environment-node@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.1.0.tgz#797bd89b378cf0bd794dc8e3dca6ef21126776db" + integrity sha512-U9kFWTtAPvhgYY5upnH9rq8qZkj6mYLup5l1caAjjx9uNnkLHN2xgZy5mo4SyLdmrh/EtB9UPpKFShvfQHD0Iw== dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" + "@jest/environment" "^25.1.0" + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" -jest-get-type@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" - integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876" + integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw== -jest-haste-map@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" - integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== +jest-haste-map@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.1.0.tgz#ae12163d284f19906260aa51fd405b5b2e5a4ad3" + integrity sha512-/2oYINIdnQZAqyWSn1GTku571aAfs8NxzSErGek65Iu5o8JYb+113bZysRMcC/pjE5v9w0Yz+ldbj9NxrFyPyw== dependencies: - "@jest/types" "^24.9.0" - anymatch "^2.0.0" + "@jest/types" "^25.1.0" + anymatch "^3.0.3" fb-watchman "^2.0.0" - graceful-fs "^4.1.15" - invariant "^2.2.4" - jest-serializer "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.9.0" - micromatch "^3.1.10" + graceful-fs "^4.2.3" + jest-serializer "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" + micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" optionalDependencies: - fsevents "^1.2.7" + fsevents "^2.1.2" -jest-jasmine2@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" - integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== +jest-jasmine2@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.1.0.tgz#681b59158a430f08d5d0c1cce4f01353e4b48137" + integrity sha512-GdncRq7jJ7sNIQ+dnXvpKO2MyP6j3naNK41DTTjEAhLEdpImaDA9zSAZwDhijjSF/D7cf4O5fdyUApGBZleaEg== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" + "@jest/environment" "^25.1.0" + "@jest/source-map" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" co "^4.6.0" - expect "^24.9.0" + expect "^25.1.0" is-generator-fn "^2.0.0" - jest-each "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" - throat "^4.0.0" + jest-each "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-runtime "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + pretty-format "^25.1.0" + throat "^5.0.0" -jest-leak-detector@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" - integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== +jest-leak-detector@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.1.0.tgz#ed6872d15aa1c72c0732d01bd073dacc7c38b5c6" + integrity sha512-3xRI264dnhGaMHRvkFyEKpDeaRzcEBhyNrOG5oT8xPxOyUAblIAQnpiR3QXu4wDor47MDTiHbiFcbypdLcLW5w== dependencies: - jest-get-type "^24.9.0" - pretty-format "^24.9.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" -jest-matcher-utils@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" - integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== +jest-matcher-utils@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz#fa5996c45c7193a3c24e73066fc14acdee020220" + integrity sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ== dependencies: - chalk "^2.0.1" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" + chalk "^3.0.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" -jest-message-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" - integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== +jest-message-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.1.0.tgz#702a9a5cb05c144b9aa73f06e17faa219389845e" + integrity sha512-Nr/Iwar2COfN22aCqX0kCVbXgn8IBm9nWf4xwGr5Olv/KZh0CZ32RKgZWMVDXGdOahicM10/fgjdimGNX/ttCQ== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" "@types/stack-utils" "^1.0.1" - chalk "^2.0.1" - micromatch "^3.1.10" - slash "^2.0.0" + chalk "^3.0.0" + micromatch "^4.0.2" + slash "^3.0.0" stack-utils "^1.0.1" -jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== +jest-mock@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.1.0.tgz#411d549e1b326b7350b2e97303a64715c28615fd" + integrity sha512-28/u0sqS+42vIfcd1mlcg4ZVDmSUYuNvImP4X2lX5hRMLW+CN0BeiKVD4p+ujKKbSPKd3rg/zuhCF+QBLJ4vag== dependencies: - "@jest/types" "^24.9.0" + "@jest/types" "^25.1.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" integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== -jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" - integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== +jest-regex-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.1.0.tgz#efaf75914267741838e01de24da07b2192d16d87" + integrity sha512-9lShaDmDpqwg+xAd73zHydKrBbbrIi08Kk9YryBEBybQFg/lBWR/2BDjjiSE7KIppM9C5+c03XiDaZ+m4Pgs1w== -jest-resolve-dependencies@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" - integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== +jest-resolve-dependencies@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.1.0.tgz#8a1789ec64eb6aaa77fd579a1066a783437e70d2" + integrity sha512-Cu/Je38GSsccNy4I2vL12ZnBlD170x2Oh1devzuM9TLH5rrnLW1x51lN8kpZLYTvzx9j+77Y5pqBaTqfdzVzrw== dependencies: - "@jest/types" "^24.9.0" - jest-regex-util "^24.3.0" - jest-snapshot "^24.9.0" + "@jest/types" "^25.1.0" + jest-regex-util "^25.1.0" + jest-snapshot "^25.1.0" -jest-resolve@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" - integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== +jest-resolve@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.1.0.tgz#23d8b6a4892362baf2662877c66aa241fa2eaea3" + integrity sha512-XkBQaU1SRCHj2Evz2Lu4Czs+uIgJXWypfO57L7JYccmAXv4slXA6hzNblmcRmf7P3cQ1mE7fL3ABV6jAwk4foQ== dependencies: - "@jest/types" "^24.9.0" + "@jest/types" "^25.1.0" browser-resolve "^1.11.3" - chalk "^2.0.1" + chalk "^3.0.0" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" - integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== +jest-runner@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.1.0.tgz#fef433a4d42c89ab0a6b6b268e4a4fbe6b26e812" + integrity sha512-su3O5fy0ehwgt+e8Wy7A8CaxxAOCMzL4gUBftSs0Ip32S0epxyZPDov9Znvkl1nhVOJNf4UwAsnqfc3plfQH9w== dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.4.2" + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" exit "^0.1.2" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-docblock "^24.3.0" - jest-haste-map "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-leak-detector "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" + graceful-fs "^4.2.3" + jest-config "^25.1.0" + jest-docblock "^25.1.0" + jest-haste-map "^25.1.0" + jest-jasmine2 "^25.1.0" + jest-leak-detector "^25.1.0" + jest-message-util "^25.1.0" + jest-resolve "^25.1.0" + jest-runtime "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" source-map-support "^0.5.6" - throat "^4.0.0" + throat "^5.0.0" -jest-runtime@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" - integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== +jest-runtime@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.1.0.tgz#02683218f2f95aad0f2ec1c9cdb28c1dc0ec0314" + integrity sha512-mpPYYEdbExKBIBB16ryF6FLZTc1Rbk9Nx0ryIpIMiDDkOeGa0jQOKVI/QeGvVGlunKKm62ywcioeFVzIbK03bA== dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - chalk "^2.0.1" + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/source-map" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" + graceful-fs "^4.2.3" + jest-config "^25.1.0" + jest-haste-map "^25.1.0" + jest-message-util "^25.1.0" + jest-mock "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" realpath-native "^1.1.0" - slash "^2.0.0" - strip-bom "^3.0.0" - yargs "^13.3.0" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.0.0" -jest-serializer@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" - integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== +jest-serializer@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.1.0.tgz#73096ba90e07d19dec4a0c1dd89c355e2f129e5d" + integrity sha512-20Wkq5j7o84kssBwvyuJ7Xhn7hdPeTXndnwIblKDR2/sy1SUm6rWWiG9kSCgJPIfkDScJCIsTtOKdlzfIHOfKA== -jest-snapshot@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" - integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== +jest-snapshot@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.1.0.tgz#d5880bd4b31faea100454608e15f8d77b9d221d9" + integrity sha512-xZ73dFYN8b/+X2hKLXz4VpBZGIAn7muD/DAg+pXtDzDGw3iIV10jM7WiHqhCcpDZfGiKEj7/2HXAEPtHTj0P2A== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - expect "^24.9.0" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + expect "^25.1.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-resolve "^25.1.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^24.9.0" - semver "^6.2.0" + pretty-format "^25.1.0" + semver "^7.1.1" -jest-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== +jest-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.1.0.tgz#7bc56f7b2abd534910e9fa252692f50624c897d9" + integrity sha512-7did6pLQ++87Qsj26Fs/TIwZMUFBXQ+4XXSodRNy3luch2DnRXsSnmpVtxxQ0Yd6WTipGpbhh2IFP1mq6/fQGw== dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" + "@jest/types" "^25.1.0" + chalk "^3.0.0" is-ci "^2.0.0" mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" -jest-validate@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" - integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== +jest-validate@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.1.0.tgz#1469fa19f627bb0a9a98e289f3e9ab6a668c732a" + integrity sha512-kGbZq1f02/zVO2+t1KQGSVoCTERc5XeObLwITqC6BTRH3Adv7NZdYqCpKIZLUgpLXf2yISzQ465qOZpul8abXA== dependencies: - "@jest/types" "^24.9.0" + "@jest/types" "^25.1.0" camelcase "^5.3.1" - chalk "^2.0.1" - jest-get-type "^24.9.0" + chalk "^3.0.0" + jest-get-type "^25.1.0" leven "^3.1.0" - pretty-format "^24.9.0" + pretty-format "^25.1.0" -jest-watcher@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" - integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== +jest-watcher@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.1.0.tgz#97cb4a937f676f64c9fad2d07b824c56808e9806" + integrity sha512-Q9eZ7pyaIr6xfU24OeTg4z1fUqBF/4MP6J801lyQfg7CsnZ/TCzAPvCfckKdL5dlBBEKBeHV0AdyjFZ5eWj4ig== dependencies: - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" - jest-util "^24.9.0" - string-length "^2.0.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + jest-util "^25.1.0" + string-length "^3.1.0" -jest-worker@^24.6.0, jest-worker@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" - integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== +jest-worker@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" + integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== dependencies: merge-stream "^2.0.0" - supports-color "^6.1.0" + supports-color "^7.0.0" -jest@~24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" - integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== +jest@~25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-25.1.0.tgz#b85ef1ddba2fdb00d295deebbd13567106d35be9" + integrity sha512-FV6jEruneBhokkt9MQk0WUFoNTwnF76CLXtwNMfsc0um0TlB/LG2yxUd0KqaFjEJ9laQmVWQWS0sG/t2GsuI0w== dependencies: - import-local "^2.0.0" - jest-cli "^24.9.0" + "@jest/core" "^25.1.0" + import-local "^3.0.2" + jest-cli "^25.1.0" jquery@^3.3.1: version "3.4.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== -js-levenshtein@^1.1.3: - version "1.1.6" - resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" - integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5379,36 +5703,36 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^11.5.1: - version "11.12.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" - integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== +jsdom@^15.1.1: + version "15.2.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" + integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== dependencies: abab "^2.0.0" - acorn "^5.5.3" - acorn-globals "^4.1.0" + acorn "^7.1.0" + acorn-globals "^4.3.2" array-equal "^1.0.0" - cssom ">= 0.3.2 < 0.4.0" - cssstyle "^1.0.0" - data-urls "^1.0.0" + cssom "^0.4.1" + cssstyle "^2.0.0" + data-urls "^1.1.0" domexception "^1.0.1" - escodegen "^1.9.1" + escodegen "^1.11.1" html-encoding-sniffer "^1.0.2" - left-pad "^1.3.0" - nwsapi "^2.0.7" - parse5 "4.0.0" + nwsapi "^2.2.0" + parse5 "5.1.0" pn "^1.1.0" - request "^2.87.0" - request-promise-native "^1.0.5" - sax "^1.2.4" + request "^2.88.0" + request-promise-native "^1.0.7" + saxes "^3.1.9" symbol-tree "^3.2.2" - tough-cookie "^2.3.4" + tough-cookie "^3.0.1" w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.3" - whatwg-mimetype "^2.1.0" - whatwg-url "^6.4.1" - ws "^5.2.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^7.0.0" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -5458,38 +5782,6 @@ json5@^2.1.0: dependencies: minimist "^1.2.0" -jsonld-signatures@^1.1.5: - version "1.2.1" - resolved "https://registry.yarnpkg.com/jsonld-signatures/-/jsonld-signatures-1.2.1.tgz#493df5df9cd3a9f1b1cb296bbd3d081679f20ca8" - integrity sha1-ST3135zTqfGxyylrvT0IFnnyDKg= - dependencies: - async "^1.5.2" - bitcore-message "github:CoMakery/bitcore-message#dist" - commander "~2.9.0" - es6-promise "~4.0.5" - jsonld "0.4.3" - node-forge "~0.6.45" - -jsonld@0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.4.3.tgz#0bbc929190064d6650a5af5876e1bfdf0ed288f3" - integrity sha1-C7ySkZAGTWZQpa9YduG/3w7SiPM= - dependencies: - es6-promise "~2.0.1" - pkginfo "~0.3.0" - request "^2.61.0" - xmldom "0.1.19" - -jsonld@^0.4.11: - version "0.4.12" - resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.4.12.tgz#a02f205d5341414df1b6d8414f1b967a712073e8" - integrity sha1-oC8gXVNBQU3xtthBTxuWenEgc+g= - dependencies: - es6-promise "^2.0.0" - pkginfo "~0.4.0" - request "^2.61.0" - xmldom "0.1.19" - jsonwebtoken@^8.3.0, jsonwebtoken@~8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -5583,16 +5875,18 @@ latest-version@^3.0.0: dependencies: package-json "^4.0.0" -left-pad@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" - integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levenary@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.0.tgz#fc146fe75f32dc483a0a2c64aef720f602cd6210" + integrity sha512-VHcwhO0UTpUW7rLPN2/OiWJdgA1e9BqEDALhrgCe/F+uUJnep6CoUsTzMeP8Rh0NGr9uKquXxqe7lwLZo509nQ== + dependencies: + leven "^3.1.0" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -5609,11 +5903,6 @@ libphonenumber-js@^1.6.4: minimist "^1.2.0" xml2js "^0.4.17" -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" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.8.tgz#2bee2272674dc196cce3740b8436c43df2162f9c" @@ -5659,6 +5948,13 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -5719,15 +6015,17 @@ lodash.unescape@4.0.1: resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= -lodash@4.17.15, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.11, lodash@~4.17.14, lodash@~4.17.15: +lodash@4.17.15, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@~4.17.11, lodash@~4.17.14, lodash@~4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@=3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= +lolex@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" long@^4.0.0: version "4.0.0" @@ -5771,7 +6069,7 @@ lru-cache@^5.0.0: dependencies: yallist "^3.0.2" -lru_map@0.3.3: +lru_map@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= @@ -5791,6 +6089,13 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" + integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== + dependencies: + semver "^6.0.0" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -5798,13 +6103,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -5822,15 +6120,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/mem/-/mem-5.1.1.tgz#7059b67bf9ac2c924c9f1cff7155a064394adfb3" - integrity sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^2.1.0" - p-is-promise "^2.1.0" - memoize-one@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -5846,12 +6135,13 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge-graphql-schemas@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/merge-graphql-schemas/-/merge-graphql-schemas-1.7.0.tgz#bedf99b90096d4b324f8e81271e878e6b5cc930d" - integrity sha512-uxErpYVjlf91eTBdwHxVEwKtaosmmEHMJaQfe35XHwOEpUfhA9OFbYKRfZX5jUUS53xMnk203HDAl/u0EfjP7A== +merge-graphql-schemas@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/merge-graphql-schemas/-/merge-graphql-schemas-1.7.6.tgz#6fe0d6f35f14e3a9eb49de01277b43efec68596d" + integrity sha512-TSyBVPvyaKDKlqCqr5V/YoIa+DuqMEBb+ACZNPtadllNCc+LRr89vqNIqYSGiK2hhGgdla0qaJYGQ7FQrmiNzQ== dependencies: - graphql-toolkit "0.5.0" + "@graphql-toolkit/file-loading" "0.9.0" + "@graphql-toolkit/schema-merging" "0.9.0" tslib "1.10.0" merge-stream@^2.0.0: @@ -5859,19 +6149,24 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -metascraper-audio@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.6.6.tgz#1de5673e18fd9154fb0ead93a184d228514eaa26" - integrity sha512-wlpfJkZ2Bgc25Kf+XMFVSh9EwWKyJVUTfJ4nIkQ9XCYoUpEC2uwN2aHfF1TCZehCJKLioU6++zeuXEaq5APk9g== - dependencies: - "@metascraper/helpers" "^5.6.6" +merge2@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -metascraper-author@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.6.6.tgz#4ab41ac8f6281ecb7ec0689889cf0d49ceedf340" - integrity sha512-Qtvldjb53p21uF9QX3eC+abWjMnTY4KaHyiODGIOwn7Kz4A13uSUVG1WlObyxyANXcP8PfuO43JpePm53WBIFg== +metascraper-audio@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.10.6.tgz#095892445b90d40bc54e54f69536a80e36fd9e4c" + integrity sha512-wTVtYK8Ico82caIi6HlkyGgUaBC21X/vhT2aQ4LKcg+gHoOhJcmWNd5me9VhaRJ7gTV/7yKkL5A54fBcjcn8Kg== dependencies: - "@metascraper/helpers" "^5.6.6" + "@metascraper/helpers" "^5.10.6" + +metascraper-author@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-5.10.6.tgz#1ceaacec776d46629300db25e17657fe35a14a20" + integrity sha512-L2P/Fp0npaQcowbwi1vHKJbSYc99cio58/yYRm205xGfgMCRMpYOrYB+ecizXgeSSRiv8G8SXLrLXOLJ5K+SdA== + dependencies: + "@metascraper/helpers" "^5.10.6" lodash "~4.17.15" metascraper-clearbit-logo@^5.3.0: @@ -5881,28 +6176,28 @@ metascraper-clearbit-logo@^5.3.0: dependencies: got "~9.6.0" -metascraper-date@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.7.0.tgz#f993745f3fc72e43b782573adf87c4ebcc05a42b" - integrity sha512-y6qiroR0H7wyW6DMw3f4L/AOeA1OBm5En4qRYUiQHhwkl6TuuXnGjOU0nZK+oLB5wkO8hLE6lmpqxtfdnUdgkg== +metascraper-date@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.10.6.tgz#dbdc0ddb4f4220ad1ea412b4a686900c1b138cf6" + integrity sha512-WfZw7WhkMKrrq96ZcAxS01/YjiDBpAPt5e3ggnCfLi2ZzC370w9J0INUo7gAtujaNZvgTTSEcrDD7AbTVMSYKA== dependencies: - "@metascraper/helpers" "^5.7.0" + "@metascraper/helpers" "^5.10.6" -metascraper-description@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.7.4.tgz#f1fd88ee5b1c1fac5e7db3dcae8c69e342e6efd6" - integrity sha512-qncq4IjsFK+ZEdWjtjhokozoWu76qfVHG/RIRHjrDG19tTwWv+PXlgyvtFaetxezI4+lR3uQHssLVb7Nv9meJw== +metascraper-description@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.10.6.tgz#ebb4459a4e1acdc473534d1b898b7958b1769eb6" + integrity sha512-d3d6UMsNnD8Dy7gxA05iTOj5QmJrFQTw1+IrW9CiOfdNsYq0H+m265a9lRaXcyJdqkzmGnv/d52C0BtUDOrkRw== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.10.6" -metascraper-image@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.6.6.tgz#5d34bb6ac42f170e81df68fa64a3038ca5110bce" - integrity sha512-A/Pp7xCbv7w0fM0ez/vKI29PvIIdZ3qVnG6DVXVNVayKYm1bjHuTKbjvWcOZqZBe1PCr0gWwnz+w6axDQYxq5g== +metascraper-image@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.10.6.tgz#b23ec4bfab0467342b294f3d049c7b7e9a1dd071" + integrity sha512-/+m0VaaqnBgNREun/8jcq+clc4s9Z0FAuMO/TQf/mrz9SbcmpHeTD0WpiGJgEa9zYSbuEQYHdBkxhpw+SdJPCA== dependencies: - "@metascraper/helpers" "^5.6.6" + "@metascraper/helpers" "^5.10.6" -metascraper-lang-detector@^4.8.5: +metascraper-lang-detector@^4.10.2: version "4.10.2" resolved "https://registry.yarnpkg.com/metascraper-lang-detector/-/metascraper-lang-detector-4.10.2.tgz#45744bc331125c098e8b27716d76740161b121d2" integrity sha512-Lz1d5v/i1j08gQYz7sCdoxjOx94ArLV4UucUhGZeQpR4E6dK47V6aqfYwODRe2XAqhaU+3oLnbAipoHkOeZXiw== @@ -5911,81 +6206,78 @@ metascraper-lang-detector@^4.8.5: franc "~4.0.0" iso-639-3 "~1.1.0" -metascraper-lang@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.6.6.tgz#056e770e5a916d1291706a887a230d652f215165" - integrity sha512-bVvqfKK3WJhOFbTPl7s4ot2uXsM3Bp5lqNyeK2cjXvOfrSg/7l6iFZmEQ2Bm9hDqeGcxCltIlBaoZYZse4uGvQ== +metascraper-lang@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.10.6.tgz#d4130257f6604095e9af8d796a6bde5815f6c667" + integrity sha512-JDhNbP1iSnPV7d6PklIIdBSzlwqbtvH+n810Isa5/PGuvUkJzNkTAUN+eTM1i6YcTlMp1N2gYsQG9uwfpMwFog== dependencies: - "@metascraper/helpers" "^5.6.6" + "@metascraper/helpers" "^5.10.6" -metascraper-logo@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.7.4.tgz#a90136718b7f827ba41249442f48a0535245bf13" - integrity sha512-SIpKMWydmVHSFjV7/exPxDx7Ydgp5n5GG0dLBNKCEuv3fHiMulrtevDlV+yk4xIGPh1CnA0hCS6mL7N/2y9ltw== +metascraper-logo@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.6.tgz#22223ce79e4017f159b2a9ddc311d2eb636043b5" + integrity sha512-/uGW+X43T6Oj5DxWqAhANII9BdhQuM+e7O6/Vu116uLqW6cOJ/RDp5qp7ngKF41L0zCLd8Q2Xw2nduHi6tC4Uw== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.10.6" -metascraper-publisher@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.6.6.tgz#855515689e7d16870333e44af49ccb954bec45b3" - integrity sha512-micUEltrytzwKb1iJjnclJ/l5970xs0PWwoY/RFcIDviBVQ/EQNCW+DieK9P6gFJq3dSLPTJ9cv2QPHkDj+ORw== +metascraper-publisher@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.10.6.tgz#95dde6ecef3c7b890ac625893ab374096bebe1f4" + integrity sha512-4jTOpbIwXBADl6z39UzQ4DZLeVoj4Q+5dcHbEgGn9MQ9878FgxiJKyrHzYvqfe9fRNd0PcaFMuuwLyhz58haoA== dependencies: - "@metascraper/helpers" "^5.6.6" + "@metascraper/helpers" "^5.10.6" -metascraper-soundcloud@^5.6.7: - version "5.6.7" - resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.6.7.tgz#c06a5398c85ec2d8e139c7f2016451ebf54de039" - integrity sha512-pHfhrAej8adCwK5Vo0IIOvoCTSWM1gSXeUQAwSyPGySME7NRMfgMq/TJSFF24Qih0UrE7u/4ogtGR3k3/Sno6g== +metascraper-soundcloud@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.10.6.tgz#7aff2e17214b6939719ea726a0f5a5fe2a48c5a5" + integrity sha512-WO+B81e04Hng4/YOtq4dpNv9CrGWVemrNuZk3iIJU+B+gF3YpzdERxS4aIpM7KHcY/c8xu3xDM3LrSUWXWjM3g== dependencies: - "@metascraper/helpers" "^5.6.6" - memoize-one "~5.1.1" - tldts "~5.4.0" + "@metascraper/helpers" "^5.10.6" + tldts "~5.6.3" -metascraper-title@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.7.0.tgz#4e37ec7cb914b70b60c9867fc05b14779d2199dd" - integrity sha512-+GwhBfw7P+S44DRak6H2oe/ESbYMfYpmFzBny2vR6a3vc8KnRivVkK8vF6PLYuTj2bteEcpvZbbPZ2w/H32CMQ== +metascraper-title@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.10.6.tgz#0fd9a9bed7a0b990663086cdab45d11cd8cd3c7d" + integrity sha512-x4P8zr0x6Gh3gt26tf2xfjikG9xNS9MC3z4N2VP+OrYNuCc7Vz6TU+DR/DLAeZphsb1flgTd3P4iUfPUcWVTEQ== dependencies: - "@metascraper/helpers" "^5.7.0" + "@metascraper/helpers" "^5.10.6" lodash "~4.17.15" -metascraper-url@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.7.4.tgz#c2aa19d5ebd1e29d1d4154d350cc903fd1725d95" - integrity sha512-ApmaiKny0stNXoGABVDFaXpfK2J5cO/wTUuiaS/bsPWwnwn9TFfdAzatEdzDM6pq77pbKWI6CkdEpeNE5b10/g== +metascraper-url@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.10.6.tgz#45f0ea173fecfe56d60b3cddd3c018f9f4fd9b92" + integrity sha512-7F6uAsI27iVXxUMwwzXH0ret81CX1jgtoGCMz+TvZkyS0z4aUs0r8QpYRYEQuXrW+JawRVik0up54F/ScslObQ== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.10.6" -metascraper-video@^5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.6.6.tgz#c739e56c10843d2f5f4753726ebbeff09ad76ca8" - integrity sha512-PK8A+91HGNZJMuBI6h+TgkSkgStX9N3OKWU8x0GHSLvFHyd99pP4l7Y2/U4HMysr4RMzrPDNGgNx8vcxy2TjOA== +metascraper-video@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.10.6.tgz#8425d2dfc378b20612e8ef9324989a33bc0341b3" + integrity sha512-DzWBCe/z86QFv6mN9ZDmvk32FMWv+nPDSkyMEL7RCU6VeQOFFAOjwhDglp2qBMs8Xif358bQ4H/0akLZpDUfvw== dependencies: - "@metascraper/helpers" "^5.6.6" + "@metascraper/helpers" "^5.10.6" lodash "~4.17.15" -metascraper-youtube@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.7.4.tgz#3af23aaa90f36e906a739694b632ef014705b65f" - integrity sha512-9pXJoPsd3coX/d3mfQ/1T22osDmCyRPWkDUSW3ZqVjThuIU9yXWWxTSmadjDj1pSpr98nL8bU0/Y627GNPNYWg== +metascraper-youtube@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.10.6.tgz#6cacabb1791b06ed98a7da69aa00c1c6c50a2dfe" + integrity sha512-Yl5kEFawqpSGmVSG2yTVZj7mGfRSFGQ2A4cxpqSbaPIUCGJwG9BUJkMzyUG0m6jGrg0zI5CmeZGNBAXzgKGz4g== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.10.6" get-video-id "~3.1.4" - is-reachable "~3.1.0" - memoize-one "~5.1.1" + is-reachable "~4.0.0" p-locate "~4.1.0" -metascraper@^4.10.3: - version "4.10.3" - resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-4.10.3.tgz#8a97ed2e914e81d1dbc1f17a5b1e64f1b804493f" - integrity sha512-wNQm5A/PIxWcahaMwI+b3rOmmXRDNmjyF6Q15dHYXEqYoGl3dFaaT4lnTTm8yntvE+fOj8+o51ON2FBdstxbsA== +metascraper@^5.10.6: + version "5.10.6" + resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.10.6.tgz#d1249577a768566b86bd099cc2256df45cf05181" + integrity sha512-mPEDvoyHLWb0AlTE05W43vfKGgBJ99s5AYAhB4IiRgGR9uq8j7/ktTZaS/+pyFopYrEoe71L/k4KbYgNPQRASA== dependencies: - "@metascraper/helpers" "^4.10.2" - cheerio "~1.0.0-rc.2" + "@metascraper/helpers" "^5.10.6" + cheerio "~1.0.0-rc.3" cheerio-advanced-selectors "~2.0.1" - lodash "~4.17.11" - p-reduce "~2.0.0" - whoops "~4.0.2" + lodash "~4.17.15" + whoops "~4.1.0" methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" @@ -6011,11 +6303,38 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +migrate@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/migrate/-/migrate-1.6.2.tgz#8970d596780553fe9f545bdf83806df8473f025b" + integrity sha512-XAFab+ArPTo9BHzmihKjsZ5THKRryenA+lwob0R+ax0hLDs7YzJFJT5YZE3gtntZgzdgcuFLs82EJFB/Dssr+g== + dependencies: + chalk "^1.1.3" + commander "^2.9.0" + dateformat "^2.0.0" + dotenv "^4.0.0" + inherits "^2.0.3" + minimatch "^3.0.3" + mkdirp "^0.5.1" + slug "^0.9.2" + mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -6023,6 +6342,13 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24: dependencies: mime-db "1.40.0" +mime-types@~2.1.26: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -6033,22 +6359,17 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.0.0.tgz#0913ff0b121db44ef5848242c38bbb35d44cabde" - integrity sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA== +mimic-fn@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.0.0.tgz#76044cfa8818bbf6999c5c9acadf2d3649b14b4b" + integrity sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ== mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimatch@^3.0.4: +minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -6065,11 +6386,6 @@ minimist@^1.1.1, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass@^2.2.1, minipass@^2.3.5: version "2.3.5" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" @@ -6105,7 +6421,7 @@ moment@2.21.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== -moment@^2.17.1, moment@^2.22.2: +moment@^2.22.2: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -6125,6 +6441,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mustache@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.0.tgz#7f02465dbb5b435859d154831c032acdfbbefb31" + integrity sha512-FJgjyX/IVkbXBXYUwH+OYwQKqWpFPLaLVESd70yHjSDunwzV2hZOoTBvPf4KLoxesUzzyfTH6F784Uqd7Wm5yA== + mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -6144,11 +6465,6 @@ n-gram@^1.0.0: resolved "https://registry.yarnpkg.com/n-gram/-/n-gram-1.1.1.tgz#a374dc176a9063a2388d1be18ed7c35828be2a97" integrity sha512-qibRqvUghLIVsq+RTwVuwOzgOxf0l4DDZKVYAK0bMam5sG9ZzaJ6BUSJyG2Td8kTc7c/HcMUtjiN5ShobZA2bA== -n3@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/n3/-/n3-0.9.1.tgz#430b547d58dc7381408c45784dd8058171903932" - integrity sha1-QwtUfVjcc4FAjEV4TdgFgXGQOTI= - nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -6190,12 +6506,7 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - -neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6: +neo4j-driver@^1.7.6: version "1.7.6" resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49" integrity sha512-6c3ALO3vYDfUqNoCy8OFzq+fQ7q/ab3LCuJrmm8P04M7RmyRCCnUtJ8IzSTGbiZvyhcehGK+azNDAEJhxPV/hA== @@ -6204,26 +6515,37 @@ neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6: text-encoding-utf-8 "^1.0.2" uri-js "^4.2.2" -neo4j-graphql-js@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.7.2.tgz#6a56c63874bc41e678cb83580c6c7647e6f61ccf" - integrity sha512-nrhSmNAkiYgksNabNuHyMHYYaLloYZaVXRiYGrRVUcf84TLiwJdg5k9hIQVH9O6QQOvnK3lwBDlE7T0phpAIpg== +neo4j-driver@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-4.0.1.tgz#b25ffde0f16602e94c46d097e16a8bacbd773d5a" + integrity sha512-SqBhXyyyayVs5gV/6BrgdKbcmU5AsYQXkFAiYO74XAE8XPLJ1HVR/Hu4wjonAX7+70DsalkWEiFN1c6UaCVzlQ== + dependencies: + "@babel/runtime" "^7.5.5" + rxjs "^6.5.2" + text-encoding-utf-8 "^1.0.2" + uri-js "^4.2.2" + +neo4j-graphql-js@^2.11.5: + version "2.11.5" + resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.11.5.tgz#4e887d727ec05b2c57ab81fad373fa3fcb734e39" + integrity sha512-vex6PRqDT5wdxYgmw9p5oii9EUbflEkjzpjJ0tG1JfhWl5e7W/CLHfjT6wyl5wWRq8WYYvREAX3ADsdNapqUtw== dependencies: "@babel/runtime" "^7.5.5" "@babel/runtime-corejs2" "^7.5.5" + debug "^4.1.1" graphql "^14.2.1" graphql-auth-directives "^2.1.0" lodash "^4.17.15" - neo4j-driver "^1.7.3" + neo4j-driver "^4.0.1" -neode@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.3.tgz#a539830cce6f6e4825462f6cb03f2969a0003f1b" - integrity sha512-pArHG1hD2kVwrzLlz6B1+IgdOJRQj/BgR6KzH6DlVzSA6geoZRe68fbpvmOJtzyPU7iuUYxXVk87PpPM1A7dlg== +neode@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.7.tgz#766105307e138b1212957aceba538e89e3d784cb" + integrity sha512-XnRJyD6bZx4HyHBmnLHuVUKtSD3FhBPXYdh7/rqiFAwBDMOSySjMFjFCYmop+sF8IBZmliowDs8zkSHt27U1kw== dependencies: "@hapi/joi" "^15.1.0" dotenv "^4.0.0" - neo4j-driver "^1.7.5" + neo4j-driver "^1.7.6" uuid "^3.3.2" next-tick@^1.0.0: @@ -6256,21 +6578,11 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" - integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= - -node-fetch@2.6.0, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@~2.6.0: +node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@~2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-forge@~0.6.45: - version "0.6.49" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.49.tgz#f1ee95d5d74623938fe19d698aa5a26d54d2f60f" - integrity sha1-8e6V1ddGI5OP4Z1piqWibVTS9g8= - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6281,16 +6593,16 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^5.4.2: - version "5.4.3" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" - integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== +node-notifier@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" + integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== dependencies: growly "^1.3.0" - is-wsl "^1.1.0" - semver "^5.5.0" + is-wsl "^2.1.1" + semver "^6.3.0" shellwords "^0.1.1" - which "^1.3.0" + which "^1.3.1" node-pre-gyp@^0.12.0: version "0.12.0" @@ -6308,30 +6620,37 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.25: - version "1.1.28" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.28.tgz#503c3c70d0e4732b84e7aaa2925fbdde10482d4a" - integrity sha512-AQw4emh6iSXnCpDiFe0phYcThiccmkNWMZnFZ+lDJjAP8J0m2fVd59duvUUyuTirQOhIAajTFkzG6FHCLBO59g== +node-releases@^1.1.44: + version "1.1.45" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.45.tgz#4cf7e9175d71b1317f15ffd68ce63bce1d53e9f2" + integrity sha512-cXvGSfhITKI8qsV116u2FTzH5EWZJfgG7d4cpqwF8I8+1tWpD6AsvvGRKq2onR0DNj1jfqsjkXZsm14JMS7Cyg== dependencies: - semver "^5.3.0" + semver "^6.3.0" -nodemailer@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12" - integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw== - -nodemon@~1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.2.tgz#b0975147dc99b3761ceb595b3f9277084931dcc0" - integrity sha512-hRLYaw5Ihyw9zK7NF+9EUzVyS6Cvgc14yh8CAYr38tPxJa6UrOxwAQ351GwrgoanHCF0FalQFn6w5eoX/LGdJw== +nodemailer-html-to-text@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nodemailer-html-to-text/-/nodemailer-html-to-text-3.1.0.tgz#11e4e435eb03e4f3b439aaf294b1bd1377e7f789" + integrity sha512-AijyAZgcFb6b53g1oMwdCKyLYQVJzbgZKbs3Bma8zR5hPR1gkajQKGGZbwtuA5JhUqnyC8pjp+tiaS7CkQ8TRg== dependencies: - chokidar "^2.1.5" - debug "^3.1.0" + html-to-text "^5.1.1" + +nodemailer@^6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.2.tgz#7147550e32cdc37453380ab78d2074533966090a" + integrity sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ== + +nodemon@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0" + integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" ignore-by-default "^1.0.1" minimatch "^3.0.4" - pstree.remy "^1.1.6" - semver "^5.5.0" - supports-color "^5.2.0" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" touch "^3.1.0" undefsafe "^2.0.2" update-notifier "^2.5.0" @@ -6368,12 +6687,12 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0, normalize-url@~4.3.0: +normalize-url@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee" integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ== @@ -6383,6 +6702,11 @@ normalize-url@~4.2.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.2.0.tgz#e747f16b58e6d7f391495fd86415fa04ec7c9897" integrity sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg== +normalize-url@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + npm-bundled@^1.0.1: version "1.0.6" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" @@ -6418,6 +6742,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -6445,10 +6776,10 @@ numeral@^2.0.6: resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= -nwsapi@^2.0.7: - version "2.1.4" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" - integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw== +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== oauth-sign@~0.9.0: version "0.9.0" @@ -6469,12 +6800,17 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-hash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.0.tgz#7c4cc341eb8b53367312a7c546142f00c9e0ea20" + integrity sha512-I7zGBH0rDKwVGeGZpZoFaDhIwvJa3l1CZE+8VchylXbInNiCj7sxxea9P5dTM4ftKR5//nrqxrdeGSTWL2VpBA== -object-keys@^1.0.11, object-keys@^1.0.12: +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -6554,25 +6890,17 @@ optimism@^0.10.0: dependencies: "@wry/context" "^0.4.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - -optionator@^0.8.1, optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" - fast-levenshtein "~2.0.4" + fast-levenshtein "~2.0.6" levn "~0.3.0" prelude-ls "~1.1.2" type-check "~0.3.2" - wordwrap "~1.0.0" + word-wrap "~1.2.3" os-homedir@^1.0.0: version "1.0.2" @@ -6592,15 +6920,6 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -output-file-sync@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-2.0.1.tgz#f53118282f5f553c2799541792b723a4c71430c0" - integrity sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ== - dependencies: - graceful-fs "^4.1.11" - is-plain-obj "^1.1.0" - mkdirp "^0.5.1" - p-any@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-any/-/p-any-2.1.0.tgz#719489408e14f5f941a748f1e817f5c71cab35cb" @@ -6620,27 +6939,20 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - -p-each-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" - integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= - dependencies: - p-reduce "^1.0.0" +p-each-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" + integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== p-limit@^1.1.0: version "1.3.0" @@ -6670,23 +6982,13 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" -p-locate@~4.1.0: +p-locate@^4.1.0, p-locate@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= - -p-reduce@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.0.0.tgz#365a26916213650711124881a6bdc4e32c2bfe36" - integrity sha512-VcNNEqiYIkRCGeUHELY5dUrnQHCRwL6eIH/L9oSbl/PsvyHQXD1ws/MFwuEb+6dgH/URCfROVUqOYL37eHi2kQ== - p-some@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-some/-/p-some-4.1.0.tgz#28e73bc1e0d62db54c2ed513acd03acba30d5c04" @@ -6695,10 +6997,10 @@ p-some@^4.0.0: aggregate-error "^3.0.0" p-cancelable "^2.0.0" -p-timeout@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.1.0.tgz#198c1f503bb973e9b9727177a276c80afd6851f3" - integrity sha512-C27DYI+tCroT8J8cTEyySGydl2B7FlxrGNF5/wmMbl1V+jeehUCzEE/BVgzRebdm2K3ZitKOKx8YbdFumDyYmw== +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== dependencies: p-finally "^1.0.0" @@ -6761,10 +7063,10 @@ parse-uri@~1.0.0: resolved "https://registry.yarnpkg.com/parse-uri/-/parse-uri-1.0.0.tgz#2872dcc22f1a797acde1583d8a0ac29552ddac20" integrity sha1-KHLcwi8aeXrN4Vg9igrClVLdrCA= -parse5@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== parse5@^3.0.1: version "3.0.3" @@ -6793,6 +7095,11 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -6808,6 +7115,16 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" + integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -6832,6 +7149,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pathval@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" @@ -6842,6 +7164,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picomatch@^2.0.4, picomatch@^2.0.5: + version "2.1.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" + integrity sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA== + pidtree@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" @@ -6883,26 +7210,18 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkginfo@~0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" - integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE= - -pkginfo@~0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" - integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== -port-numbers@^4.0.7: - version "4.0.16" - resolved "https://registry.yarnpkg.com/port-numbers/-/port-numbers-4.0.16.tgz#fd47dd3eb6acd3a99d7ddea2caa3d53296286e6c" - integrity sha512-JV8PABRPyjyPBpvS4nbSv1Tmx58q7GLdSDpTC1rrKtFpKIHCAlLcAdEkIlIl+3AP2Nx2G5RbtNKX1QJHEkc2PQ== - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -6932,6 +7251,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prepend-http@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-3.0.1.tgz#3e724d58fd5867465b300bb9615009fa2f8ee3b6" + integrity sha512-BLxfZh+m6UiAiCPZFJ4+vYoL7NrRs5XgCTRrjseATAggXhdZKKxn+JUNmuVYWY23bDHgaEHodxw8mnmtVEDtHw== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -6939,20 +7263,20 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@~1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" - integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== +prettier@~1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-format@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" - integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== +pretty-format@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" + integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== dependencies: - "@jest/types" "^24.9.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" + "@jest/types" "^25.1.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" private@^0.1.6: version "0.1.8" @@ -6991,25 +7315,6 @@ property-expr@^1.5.0: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== -protobufjs@^6.8.6: - version "6.8.8" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" - integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.0" - "@types/node" "^10.1.0" - long "^4.0.0" - proxy-addr@~2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" @@ -7028,7 +7333,7 @@ psl@^1.1.24, psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd" integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag== -pstree.remy@^1.1.6: +pstree.remy@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== @@ -7111,7 +7416,12 @@ react-dom@^16.4.2: prop-types "^15.6.2" scheduler "^0.15.0" -react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + +react-is@^16.8.1: version "16.9.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== @@ -7133,14 +7443,6 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -7159,7 +7461,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.3, readable-stream@^2.3.5: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -7190,6 +7492,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -7197,18 +7506,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -reasoner@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/reasoner/-/reasoner-2.0.0.tgz#6ccf76cb9baf96b82c45ab0bd60211c2aa1b701b" - integrity sha1-bM92y5uvlrgsRasL1gIRwqobcBs= - dependencies: - n3 "^0.9.1" - rfc5646 "^2.0.0" - vocabs-asx "^0.11.1" - vocabs-rdf "^0.11.1" - vocabs-rdfs "^0.11.1" - vocabs-xsd "^0.11.1" - referrer-policy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" @@ -7231,7 +7528,7 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.2: +regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== @@ -7251,11 +7548,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp-tree@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f" - integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw== - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -7266,10 +7558,10 @@ regexpp@^3.0.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g== -regexpu-core@^4.5.4: - version "4.5.5" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.5.tgz#aaffe61c2af58269b3e516b61a73790376326411" - integrity sha512-FpI67+ky9J+cDizQUJlIlNZFKual/lUkFr1AG6zOCpwZ9cLrg8UUVakyUQJD7fCDIe9Z2nwTQJNPyonatNmDFQ== +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== dependencies: regenerate "^1.4.0" regenerate-unicode-properties "^8.1.0" @@ -7320,23 +7612,23 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -request-promise-core@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" - integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag== +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== dependencies: - lodash "^4.17.11" + lodash "^4.17.15" -request-promise-native@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" - integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== +request-promise-native@^1.0.7, request-promise-native@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== dependencies: - request-promise-core "1.1.2" + request-promise-core "1.1.3" stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.61.0, request@^2.87.0, request@^2.88.0, request@~2.88.0: +request@^2.88.0, request@~2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -7377,23 +7669,23 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: +resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -7404,10 +7696,10 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.1.tgz#9e018c540fcf0c427d678b9931cbf45e984bcaff" + integrity sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg== dependencies: path-parse "^1.0.6" @@ -7436,10 +7728,10 @@ retry@0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rfc5646@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/rfc5646/-/rfc5646-2.0.0.tgz#ac0c67b6cd04411ef7c80751ba159d9371ce116c" - integrity sha1-rAxnts0EQR73yAdRuhWdk3HOEWw= +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@2.6.3: version "2.6.3" @@ -7448,13 +7740,20 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@^2.6.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" + integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg== + dependencies: + glob "^7.1.3" + router-ips@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/router-ips/-/router-ips-1.0.0.tgz#44e00858ebebc0133d58e40b2cd8a1fbb04203f5" @@ -7472,15 +7771,15 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== -rxjs@^6.4.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" - integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== +rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" + integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== dependencies: tslib "^1.9.0" @@ -7521,10 +7820,10 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sanitize-html@~1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.1.tgz#f6effdf55dd398807171215a62bfc21811bacf85" - integrity sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA== +sanitize-html@~1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.21.1.tgz#1647d15c0c672901aa41eac1b86d0c38146d30ce" + integrity sha512-W6enXSVphVaVbmVbzVngBthR5f5sMmhq3EfPfBlzBzp2WnX8Rnk7NGpP7KmHUc0Y3MVk9tv/+CbpdHchX9ai7g== dependencies: chalk "^2.4.1" htmlparser2 "^3.10.0" @@ -7542,6 +7841,13 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + scheduler@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.15.0.tgz#6bfcf80ff850b280fed4aeecc6513bc0b4f17f8e" @@ -7562,21 +7868,26 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" + integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -7596,20 +7907,17 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-error@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-3.0.0.tgz#80100282b09be33c611536f50033481cb9cc87cf" - integrity sha512-+y3nkkG/go1Vdw+2f/+XUXM1DXX1XcxTl99FfiD/OEPUNw4uo0i6FKABfTAN5ZcgGtjTRZcEbxcE/jtXbEY19A== +serialize-error@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-4.1.0.tgz#63e1e33ede20bcd89d9f0528ea4c15fbf0f2b78a" + integrity sha512-5j9GgyGsP9vV9Uj1S0lDCvlsd+gc2LEPVK7HHHte7IyPwOD4lVQFeaX143gx3U5AnoCi+wbcb3mvaxVysjpxEw== + dependencies: + type-fest "^0.3.0" serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -7646,11 +7954,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shell-quote@^1.6.1: version "1.7.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.1.tgz#3161d969886fb14f9140c65245a5dd19b6f0b06b" @@ -7676,6 +7996,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -7685,10 +8010,17 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -slug@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/slug/-/slug-1.1.0.tgz#73eef5710416f515077bdf70c683bde4915913c9" - integrity sha512-NuIOjDQeTMPm+/AUIHJ5636mF3jOsYLFnoEErl9Tdpt4kpt4fOrAJxscH9mUgX1LtPaEqgPCawBg7A4yhoSWRg== +slug@^0.9.2: + version "0.9.4" + resolved "https://registry.yarnpkg.com/slug/-/slug-0.9.4.tgz#fad5f1ef33150830c7688cd8500514576eccabd8" + integrity sha512-3YHq0TeJ4+AIFbJm+4UWSQs5A1mmeWOTQqydW3OoPmQfNKxlO96NDRTIrp+TBkmvEsEFrd+Z/LXw8OD/6OlZ5g== + dependencies: + unicode ">= 0.3.1" + +slug@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/slug/-/slug-2.1.1.tgz#08df390d4b4d51bafb41ac0067c0c2dd70734ef2" + integrity sha512-yNGhDdS0DR0JyxnPC84qIx/Vd01RHVY4guJeBqBNdBoOLNWnzw5zkWJvxVSmsuUb92bikdnQFnw3PfGY8uZ82g== dependencies: unicode ">= 0.3.1" @@ -7738,10 +8070,10 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6, source-map-support@^0.5.9: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== +source-map-support@^0.5.16, source-map-support@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -7766,6 +8098,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -7889,18 +8226,18 @@ streamsearch@0.1.2: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= -string-argv@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.1.1.tgz#66bd5ae3823708eaa1916fa5412703150d4ddfaf" - integrity sha512-El1Va5ehZ0XTj3Ekw4WFidXvTmt9SrC0+eigdojgtJMVtPkF0qbBe9fyNSl9eQf+kUHnTSQxdQYzuHfZy8V+DQ== +string-argv@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-length@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" - integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== dependencies: astral-regex "^1.0.0" - strip-ansi "^4.0.0" + strip-ansi "^5.2.0" string-width@^1.0.1: version "1.0.2" @@ -7919,7 +8256,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: +string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== @@ -7937,6 +8274,15 @@ string-width@^4.1.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^5.2.0" +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + string.prototype.padend@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" @@ -7946,6 +8292,22 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" +string.prototype.trimleft@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" + integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" + integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -7974,23 +8336,40 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" @@ -8036,6 +8415,11 @@ supertest@~4.0.2: methods "^1.1.2" superagent "^3.8.3" +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + supports-color@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" @@ -8043,7 +8427,7 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -supports-color@^5.2.0, supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -8057,6 +8441,21 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.0.0.tgz#b1b94a159e9df00b0a554b2d5f0e0a89690334b0" + integrity sha512-bFhn0MQ8qefLyJ3K7PpHiPUTuTVPWw6RXfaMeV6xgJLXtBbszyboz1bvGTVv4R0YpQm2DqlXXn0fFHhxUHVE5w== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + symbol-observable@^1.0.2, symbol-observable@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -8102,15 +8501,22 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -test-exclude@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" - integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== dependencies: - glob "^7.1.3" + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" minimatch "^3.0.4" - read-pkg-up "^4.0.0" - require-main-filename "^2.0.0" text-encoding-utf-8@^1.0.2: version "1.0.2" @@ -8136,10 +8542,10 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -throat@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" - integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== through@^2.3.6: version "2.3.8" @@ -8179,17 +8585,17 @@ tlds@^1.187.0, tlds@^1.203.0: resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.203.1.tgz#4dc9b02f53de3315bc98b80665e13de3edfc1dfc" integrity sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw== -tldts-core@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.4.0.tgz#bd69ca3ad96a78ab675c74684c6d0717929777ec" - integrity sha512-kfDnB7fcNgNUcn5k21TPM/MbXqJNt2bBGQRfGyE39H334Qk+qNcSqw9It3YPxvrA7msl7DQ8wvcIsa0y55auug== +tldts-core@^5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.6.3.tgz#d80ce1e93b58ba0614701c28450360fc6986aaf1" + integrity sha512-E7Jtwgy5ZKXuKm3tb2Z73t0AgiGTnGnVrGfBAJj0nS2tENCclb/Ym5yt+wOdDW+8uJg0bI/BxHmbvUyLAYpcPQ== -tldts@~5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.4.0.tgz#ded769383615341660243acf547f197eb48f6795" - integrity sha512-S1CmstJxRb6KK+uLHhMUXXkI/zjA20RGM9QYkLgDEj42C9Zmr+OLjzEqf4Id/EWppuLi1z9FdNsz8/qi/pLCGA== +tldts@~5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.6.3.tgz#89159b2180bf18d807bcf38438ef6e35fafa8d9f" + integrity sha512-h17D3Q9iRTeEdqncCR5MfRvwPxRbGFwx/g51ky2s6+2i9BicZOPFikc5FE2jG0Se+0bAPaaoZLytQ1kGhH1U0g== dependencies: - tldts-core "^5.4.0" + tldts-core "^5.6.3" tmp@^0.0.33: version "0.0.33" @@ -8228,6 +8634,13 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -8255,7 +8668,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@^2.3.4: +tough-cookie@^2.3.3: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -8263,6 +8676,15 @@ tough-cookie@^2.3.3, tough-cookie@^2.3.4: psl "^1.1.28" punycode "^2.1.1" +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -8287,11 +8709,6 @@ trigram-utils@^1.0.0: n-gram "^1.0.0" trim "0.0.1" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" @@ -8328,7 +8745,7 @@ ts-invariant@^0.4.0: dependencies: tslib "^1.9.3" -tslib@1.10.0, tslib@^1.9.0, tslib@^1.9.3: +tslib@1.10.0, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== @@ -8352,7 +8769,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -8367,6 +8784,11 @@ type-fest@^0.5.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -8380,13 +8802,12 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179" integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg== -uglify-js@^3.1.4: - version "3.6.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" - integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: - commander "~2.20.0" - source-map "~0.6.1" + is-typedarray "^1.0.0" undefsafe@^2.0.2: version "2.0.2" @@ -8440,6 +8861,13 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unixify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" + integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA= + dependencies: + normalize-path "^2.1.1" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -8510,7 +8938,7 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.6: +url-parse@^1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== @@ -8569,16 +8997,25 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.1.0, uuid@^3.3.2, uuid@~3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" - integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== +uuid@^3.1.0, uuid@^3.3.2, uuid@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== +v8-to-istanbul@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.0.1.tgz#d6a2a3823b8ff49bdf2167ff2a45d82dff81d02f" + integrity sha512-x0yZvZAkjJwdD3fPiJzYP37aod0ati4LlmD2RmpKjqewjKAov/u/ytZ8ViIZb07cN4cePKzl9ijiUi7C1LQ8hQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + v8flags@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" @@ -8586,11 +9023,6 @@ v8flags@^3.1.1: dependencies: homedir-polyfill "^1.0.1" -valid-url@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" - integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -8599,6 +9031,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^12.2.0: + version "12.2.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-12.2.0.tgz#660d47e96267033fd070096c3b1a6f2db4380a0a" + integrity sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -8618,75 +9055,6 @@ video-extensions@~1.1.0: resolved "https://registry.yarnpkg.com/video-extensions/-/video-extensions-1.1.0.tgz#eaa86b45f29a853c2b873e9d8e23b513712997d6" integrity sha1-6qhrRfKahTwrhz6djiO1E3Epl9Y= -vocabs-as@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vocabs-as/-/vocabs-as-3.0.0.tgz#0dd0549cecb331ba4e917d2c5a4e83b146865c23" - integrity sha512-Dfze+B0CYZzhSK12jWvbxaL8/vXPnlzhhqhQTrEVxkGht+qzU4MmSLXSomQrdiSNKokVVtt16tyKoJWBW9TdNQ== - dependencies: - activitystreams-context ">=3.0.0" - vocabs ">=0.11.2" - -vocabs-asx@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-asx/-/vocabs-asx-0.11.1.tgz#6667e4e174dc4556722b6cb1b9619fb16491519a" - integrity sha1-Zmfk4XTcRVZyK2yxuWGfsWSRUZo= - dependencies: - vocabs ">=0.11.1" - -vocabs-interval@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-interval/-/vocabs-interval-0.11.1.tgz#1c009421f3e88a307aafbb75bfa670ff0f4f6d3c" - integrity sha1-HACUIfPoijB6r7t1v6Zw/w9PbTw= - dependencies: - vocabs ">=0.11.1" - -vocabs-ldp@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/vocabs-ldp/-/vocabs-ldp-0.1.0.tgz#da1728df560471750dfc7050e7e2df1bab901ce6" - integrity sha1-2hco31YEcXUN/HBQ5+LfG6uQHOY= - dependencies: - vocabs ">=0.11.1" - -vocabs-owl@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-owl/-/vocabs-owl-0.11.1.tgz#2355bbd27bfc19c5992d98079bbab3d7d65459e9" - integrity sha1-I1W70nv8GcWZLZgHm7qz19ZUWek= - dependencies: - vocabs ">=0.11.1" - -vocabs-rdf@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-rdf/-/vocabs-rdf-0.11.1.tgz#c7fa91d83b050ffb7b98ce2c72ab25c6fbcd1194" - integrity sha1-x/qR2DsFD/t7mM4scqslxvvNEZQ= - dependencies: - vocabs ">=0.11.1" - -vocabs-rdfs@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-rdfs/-/vocabs-rdfs-0.11.1.tgz#2e2df56ae0de008585b21057570386018da455bf" - integrity sha1-Li31auDeAIWFshBXVwOGAY2kVb8= - dependencies: - vocabs ">=0.11.1" - -vocabs-social@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-social/-/vocabs-social-0.11.1.tgz#d28545868cce325ba0c88e394f3de6e03fad85b1" - integrity sha1-0oVFhozOMlugyI45Tz3m4D+thbE= - dependencies: - vocabs ">=0.11.1" - -vocabs-xsd@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/vocabs-xsd/-/vocabs-xsd-0.11.1.tgz#20e201d8fd0fd330d6650d9061fda60baae6cd6c" - integrity sha1-IOIB2P0P0zDWZQ2QYf2mC6rmzWw= - dependencies: - vocabs ">=0.11.1" - -vocabs@>=0.11.1, vocabs@>=0.11.2: - version "0.11.2" - resolved "https://registry.yarnpkg.com/vocabs/-/vocabs-0.11.2.tgz#8944b40f11d415f07db6e259804024a1dbfaa4d4" - integrity sha512-OIon2MWA21ZO42UBsTa5DuMsk5zv72DxMdQNvLsPN1M9GrjVTovn3LgWUZdPVnKBpdWhqWV7Mfbq/Sh0vkHIBw== - w3c-hr-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" @@ -8694,16 +9062,26 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-on@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-3.3.0.tgz#9940981d047a72a9544a97b8b5fca45b2170a082" - integrity sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ== +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== dependencies: - "@hapi/joi" "^15.0.3" - core-js "^2.6.5" + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +wait-on@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-4.0.0.tgz#4d7e4485ca759968897fd3b0cc50720c0b4ca959" + integrity sha512-QrW3J8LzS5ADPfD9Rx5S6KJck66xkqyiFKQs9jmUTkIhiEOmkzU7WRZc+MjsnmkrgjitS2xQ4bb13hnlQnKBUQ== + dependencies: + "@hapi/joi" "^16.1.8" + lodash "^4.17.15" minimist "^1.2.0" request "^2.88.0" - rx "^4.1.0" + request-promise-native "^1.0.8" + rxjs "^6.5.4" walker@^1.0.7, walker@~1.0.5: version "1.0.7" @@ -8717,37 +9095,18 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" -whatwg-fetch@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-fetch@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^6.4.1: - version "6.5.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-url@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" @@ -8762,20 +9121,27 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.9, which@^1.3.0: +which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -whoops@~4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/whoops/-/whoops-4.0.2.tgz#60e1281d47a1600f5f5013059afaad369d83e9d4" - integrity sha512-b1ofth7xMOAkukgzMhAPKBrgieGJAgKVMyu54DXAOVLmkhpQEfNKe4wS0R7LbdxIsm6FD2CFUjBOdN7Sj+zLSg== +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: - clean-stack "~2.0.0" - mimic-fn "~2.0.0" + isexe "^2.0.0" + +whoops@~4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/whoops/-/whoops-4.1.0.tgz#f42e51514c7af19a9491a44cabf2712292c6a8e1" + integrity sha512-42soctqvFs9FaU1r4ZadCy2F6A9dUc4SN3ud+tbDEdmyZDTeYBgKKqtIdo6NiQlnZnJegWRCyKLk2edYH9DsHA== + dependencies: + clean-stack "~2.2.0" + mimic-fn "~3.0.0" wide-align@^1.1.0: version "1.1.3" @@ -8791,39 +9157,25 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" - integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - write-file-atomic@^2.0.0: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" @@ -8833,6 +9185,16 @@ write-file-atomic@^2.0.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write-file-atomic@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.1.tgz#558328352e673b5bb192cf86500d60b230667d4b" + integrity sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + write@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" @@ -8854,6 +9216,11 @@ ws@^6.0.0: dependencies: async-limiter "~1.0.0" +ws@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" + integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== + x-xss-protection@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.3.0.tgz#3e3a8dd638da80421b0e9fff11a2dbe168f6d52c" @@ -8882,10 +9249,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmldom@0.1.19: - version "0.1.19" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc" - integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw= +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xregexp@^4.2.4: version "4.2.4" @@ -8914,34 +9281,35 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== -yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yargs-parser@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1" + integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^13.3.0: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== +yargs@^15.0.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" + integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^3.0.0" + string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.1" + yargs-parser "^16.1.0" -yup@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7" - integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ== +yup@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.28.0.tgz#fdc04d1a495465c83d3757a80c47616884baeddc" + integrity sha512-9ZmsB/PT6/m+oUKF8rT9lWhMMGfx5s/aNCCf8pMu/GEQA0Ro2tLOc+aX12GjfL67Vif5a3c7eZVuxGFqFScnJQ== dependencies: "@babel/runtime" "^7.0.0" fn-name "~2.0.1" diff --git a/cypress.env.template.json b/cypress.env.template.json deleted file mode 100644 index 8eda47154..000000000 --- a/cypress.env.template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "BACKEND_HOST": "http://localhost:4000", - "NEO4J_URI": "bolt://localhost:7687", - "NEO4J_USERNAME": "neo4j", - "NEO4J_PASSWORD": "letmein" -} diff --git a/cypress/README.md b/cypress/README.md index 2adcff925..662d0b51c 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -16,12 +16,7 @@ First, you have to tell cypress how to connect to your local neo4j database among other things. You can copy our template configuration and change the new file according to your needs. -Make sure you are at the root level of the project. Then: -```bash -# in the top level folder Human-Connection/ -$ cp cypress.env.template.json cypress.env.json -``` -To start the services that are required for cypress testing, run this: +To start the services that are required for cypress testing, run: ```bash # in the top level folder Human-Connection/ diff --git a/cypress/constants/terms-and-conditions-version.js b/cypress/constants/terms-and-conditions-version.js new file mode 100644 index 000000000..87b00b8dc --- /dev/null +++ b/cypress/constants/terms-and-conditions-version.js @@ -0,0 +1,2 @@ +// please change also version in file "webapp/constants/terms-and-conditions-version.js" +export const VERSION = '0.0.3' \ No newline at end of file diff --git a/cypress/features.md b/cypress/features.md index 3adfd8771..60980703d 100644 --- a/cypress/features.md +++ b/cypress/features.md @@ -249,10 +249,12 @@ Shows automatically related actions for existing post. ### Administration +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/administration) + * Provide Admin-Interface to send Users Invite Code * Static Pages for Data Privacy Statement ... * Create, edit and delete Announcements -* Show Announcements on top of User Interface +* Pin a post to inform users ### Invitation diff --git a/cypress/integration/administration/PinPost.feature b/cypress/integration/administration/PinPost.feature new file mode 100644 index 000000000..40ff9cda5 --- /dev/null +++ b/cypress/integration/administration/PinPost.feature @@ -0,0 +1,36 @@ +Feature: Pin a post + As an admin + I want to pin a post so that it always appears at the top + In order to make sure all network users read it - e.g. notify people about security incidents, maintenance downtimes + + + Background: + Given we have the following posts in our database: + | id | title | pinned | createdAt | + | p1 | Some other post | | 2020-01-21 | + | p2 | Houston we have a problem | x | 2020-01-20 | + | p3 | Yet another post | | 2020-01-19 | + + Scenario: Pinned post always appears on the top of the newsfeed + Given I am logged in with a "user" role + Then the first post on the landing page has the title: + """ + Houston we have a problem + """ + And the post with title "Houston we have a problem" has a ribbon for pinned posts + + Scenario: Ordinary users cannot pin a post + Given I am logged in with a "user" role + When I open the content menu of post "Yet another post" + Then there is no button to pin a post + + Scenario: Admins are allowed to pin a post + Given I am logged in with a "admin" role + And I open the content menu of post "Yet another post" + When I click on 'Pin post' + Then I see a toaster with "Post pinned successfully" + And the first post on the landing page has the title: + """ + Yet another post + """ + And the post with title "Yet another post" has a ribbon for pinned posts diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index 814159a34..c4da93c7d 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -3,6 +3,14 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps"; const narratorAvatar = "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg"; +When("I type in a comment with {int} characters", size => { + var c=""; + for (var i = 0; i < size; i++) { + c += "c" + } + cy.get(".editor .ProseMirror").type(c); +}); + Then("I click on the {string} button", text => { cy.get("button") .contains(text) @@ -16,13 +24,51 @@ 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") + .get(".user-avatar img") .should("have.attr", "src") .and("contain", narratorAvatar) - .get("div p.ds-text span") + .get(".user-teaser > .info > .text") .should("contain", "today at"); }); +Then("I should see the entirety of my comment", () => { + cy.get("div.comment") + .should("not.contain", "show more") +}); + +Then("I should see an abreviated version of my comment", () => { + cy.get("div.comment") + .should("contain", "show more") +}); + Then("the editor should be cleared", () => { cy.get(".ProseMirror p").should("have.class", "is-empty"); }); + +When("I open the content menu of post {string}", (title)=> { + cy.contains('.post-card', title) + .find('.content-menu .base-button') + .click() +}) + +When("I click on 'Pin post'", (string)=> { + cy.get("a.ds-menu-item-link").contains("Pin post") + .click() +}) + +Then("there is no button to pin a post", () => { + cy.get("a.ds-menu-item-link") + .should('contain', "Report Post") // sanity check + .should('not.contain', "Pin post") +}) + +And("the post with title {string} has a ribbon for pinned posts", (title) => { + cy.get("article.post-card").contains(title) + .parent() + .find("div.ribbon.ribbon--pinned") + .should("contain", "Announcement") +}) + +Then("I see a toaster with {string}", (title) => { + cy.get(".iziToast-message").should("contain", title); +}) diff --git a/cypress/integration/common/profile.js b/cypress/integration/common/profile.js index b1bf9e4e0..c22c20392 100644 --- a/cypress/integration/common/profile.js +++ b/cypress/integration/common/profile.js @@ -32,5 +32,5 @@ Then("I cannot upload a picture", () => { cy.get(".ds-card-content") .children() .should("not.have.id", "customdropzone") - .should("have.class", "ds-avatar"); + .should("have.class", "user-avatar"); }); diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js index fb395d361..710928ff2 100644 --- a/cypress/integration/common/report.js +++ b/cypress/integration/common/report.js @@ -1,11 +1,13 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' +import { VERSION } from '../../constants/terms-and-conditions-version.js' +import { gql } from '../../../backend/src/helpers/jest' /* global cy */ let lastReportTitle let davidIrvingPostTitle = 'The Truth about the Holocaust' let davidIrvingPostSlug = 'the-truth-about-the-holocaust' -let davidIrvingName = 'David Irving' +let annoyingUserWhoMutedModeratorTitle = 'Fake news' const savePostTitle = $post => { return $post @@ -31,7 +33,7 @@ Given('I am logged in with a {string} role', role => { cy.factory().create('User', { email: `${role}@example.org`, password: '1234', - termsAndConditionsAgreedVersion: "0.0.2", + termsAndConditionsAgreedVersion: VERSION, role }) cy.login({ @@ -42,7 +44,7 @@ Given('I am logged in with a {string} role', role => { When('I click on "Report Post" from the content menu of the post', () => { cy.contains('.ds-card', davidIrvingPostTitle) - .find('.content-menu-trigger') + .find('.content-menu .base-button') .click({force: true}) cy.get('.popover .ds-menu-item-link') @@ -52,7 +54,7 @@ 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', davidIrvingPostTitle) - .get('.user-content-menu .content-menu-trigger') + .get('.user-content-menu .base-button') .click({ force: true }) cy.get('.popover .ds-menu-item-link') @@ -61,7 +63,7 @@ When('I click on "Report User" from the content menu in the user info box', () = }) When('I click on the author', () => { - cy.get('.username') + cy.get('.user-teaser') .click() .url().should('include', '/profile/') }) @@ -107,6 +109,11 @@ Then(`I can't see the moderation menu item`, () => { When(/^I confirm the reporting dialog .*:$/, message => { cy.contains(message) // wait for element to become visible cy.get('.ds-modal').within(() => { + cy.get('.ds-radio-option-label') + .first() + .click({ + force: true + }) cy.get('button') .contains('Report') .click() @@ -114,21 +121,22 @@ When(/^I confirm the reporting dialog .*:$/, message => { }) Given('somebody reported the following posts:', table => { - table.hashes().forEach(({ id }) => { + table.hashes().forEach(({ submitterEmail, resourceId, reasonCategory, reasonDescription }) => { const submitter = { - email: `submitter${id}@example.org`, + email: submitterEmail, password: '1234' } cy.factory() .create('User', submitter) .authenticateAs(submitter) - .mutate(`mutation($id: ID!, $description: String!) { - report(description: $description, id: $id) { + .mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { + fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) { id } }`, { - id, - description: 'Offensive content' + resourceId, + reasonCategory, + reasonDescription }) }) }) @@ -139,7 +147,27 @@ Then('I see all the reported posts including the one from above', () => { }) }) +Then('I see all the reported posts including from the user who muted me', () => { + cy.get('table tbody').within(() => { + cy.contains('tr', annoyingUserWhoMutedModeratorTitle) + }) +}) + Then('each list item links to the post page', () => { cy.contains(davidIrvingPostTitle).click() cy.location('pathname').should('contain', '/post') }) + +Then('I can visit the post page', () => { + cy.contains(annoyingUserWhoMutedModeratorTitle).click() + cy.location('pathname').should('contain', '/post') + .get('h3').should('contain', annoyingUserWhoMutedModeratorTitle) +}) + +When("they have a post someone has reported", () => { + cy.factory() + .create("Post", { + authorId: 'annnoying-user', + title, + }); +}) diff --git a/cypress/integration/common/search.js b/cypress/integration/common/search.js index 35b2d1346..f6589763b 100644 --- a/cypress/integration/common/search.js +++ b/cypress/integration/common/search.js @@ -1,21 +1,24 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps"; When("I search for {string}", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value); }); -Then("I should have one post in the select dropdown", () => { - cy.get(".input .ds-select-dropdown").should($li => { +Then("I should have one item in the select dropdown", () => { + cy.get(".searchable-input .ds-select-dropdown").should($li => { expect($li).to.have.length(1); }); }); Then("the search has no results", () => { - cy.get(".input .ds-select-dropdown").should($li => { + cy.get(".searchable-input .ds-select-dropdown").should($li => { expect($li).to.have.length(1); }); cy.get(".ds-select-dropdown").should("contain", 'Nothing found'); + cy.get(".searchable-input .ds-select-search") + .focus() + .type("{esc}"); }); Then("I should see the following posts in the select dropdown:", table => { @@ -24,26 +27,33 @@ Then("I should see the following posts in the select dropdown:", table => { }); }); +Then("I should see the following users in the select dropdown:", table => { + cy.get(".ds-heading").should("contain", "Users"); + table.hashes().forEach(({ slug }) => { + cy.get(".ds-select-dropdown").should("contain", slug); + }); +}); + When("I type {string} and press Enter", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value) .type("{enter}", { force: true }); }); When("I type {string} and press escape", value => { - cy.get("#nav-search") + cy.get(".searchable-input .ds-select-search") .focus() .type(value) .type("{esc}"); }); Then("the search field should clear", () => { - cy.get("#nav-search").should("have.text", ""); + cy.get(".searchable-input .ds-select-search").should("have.text", ""); }); -When("I select an entry", () => { - cy.get(".input .ds-select-dropdown ul li") +When("I select a post entry", () => { + cy.get(".searchable-input .search-post") .first() .trigger("click"); }); @@ -75,3 +85,13 @@ Then( ); } ); + +Then("I select a user entry", () => { + cy.get(".searchable-input .user-teaser") + .first() + .trigger("click"); +}) + +Then("I should be on the user's profile", () => { + cy.location("pathname").should("eq", "/profile/user-for-search/search-for-me") +}) diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js index b32924f6a..e8e968a5e 100644 --- a/cypress/integration/common/settings.js +++ b/cypress/integration/common/settings.js @@ -18,6 +18,8 @@ When('I save {string} as my new name', name => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') }) When('I save {string} as my location', location => { @@ -28,6 +30,8 @@ When('I save {string} as my location', location => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') myLocation = location }) @@ -38,6 +42,8 @@ When('I have the following self-description:', text => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') aboutMeText = text }) @@ -113,7 +119,7 @@ Then('they should be able to see my social media links', () => { }) When('I delete a social media link', () => { - cy.get("a[name='delete']") + cy.get(".base-button[title='Delete']") .click() }) @@ -123,7 +129,7 @@ Then('it gets deleted successfully', () => { }) When('I start editing a social media link', () => { - cy.get("a[name='edit']") + cy.get(".base-button[title='Edit']") .click() }) diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js index d712ee9b1..9a5c02d08 100644 --- a/cypress/integration/common/steps.js +++ b/cypress/integration/common/steps.js @@ -4,9 +4,13 @@ import { Then } from "cypress-cucumber-preprocessor/steps"; import helpers from "../../support/helpers"; +import { VERSION } from '../../constants/terms-and-conditions-version.js' +import locales from '../../../webapp/locales' +import orderBy from 'lodash/orderBy' /* global cy */ +const languages = orderBy(locales, 'name') let lastPost = {}; let loginCredentials = { @@ -14,7 +18,7 @@ let loginCredentials = { password: "1234" }; const termsAndConditionsAgreedVersion = { - termsAndConditionsAgreedVersion: "0.0.2" + termsAndConditionsAgreedVersion: VERSION }; const narratorParams = { id: 'id-of-peter-pan', @@ -25,10 +29,20 @@ const narratorParams = { ...termsAndConditionsAgreedVersion, }; +const annoyingParams = { + email: "spammy-spammer@example.org", + password: "1234", + ...termsAndConditionsAgreedVersion +}; + Given("I am logged in", () => { cy.login(loginCredentials); }); +Given("I am logged in as the muted user", () => { + cy.login({ email: annoyingParams.email, password: '1234' }); +}); + Given("we have a selection of categories", () => { cy.createCategories("cat0", "just-for-fun"); }); @@ -198,6 +212,7 @@ Given("we have the following posts in our database:", table => { ...postAttributes, deleted: Boolean(postAttributes.deleted), disabled: Boolean(postAttributes.disabled), + pinned: Boolean(postAttributes.pinned), categoryIds: ['cat-456'] } cy.factory().create("Post", postAttributes); @@ -223,7 +238,6 @@ Given("I previously created a post", () => { lastPost.authorId = narratorParams.id lastPost.title = "previously created post"; lastPost.content = "with some content"; - lastPost.categoryIds = ["cat0"]; cy.factory() .create("Post", lastPost); }); @@ -239,11 +253,17 @@ When("I type in the following text:", text => { }); Then("I select a category", () => { - cy.get("span") + cy.get(".base-button") .contains("Just for Fun") .click(); }); +When("I choose {string} as the language for the post", (languageCode) => { + cy.get('.ds-flex-item > .ds-form-item .ds-select ') + .click().get('.ds-select-option') + .eq(languages.findIndex(l => l.code === languageCode)).click() +}) + Then("the post shows up on the landing page at position {int}", index => { cy.openPage("landing"); const selector = `.post-card:nth-child(${index}) > .ds-card-content`; @@ -351,10 +371,12 @@ When("I log in with the following credentials:", table => { }); When("open the notification menu and click on the first item", () => { - cy.get(".notifications-menu").click(); + cy.get(".notifications-menu").invoke('show').click(); // "invoke('show')" because of the delay for show the menu cy.get(".notification-mention-post") .first() - .click(); + .click({ + force: true + }); }); Then("see {int} unread notifications in the top menu", count => { @@ -395,11 +417,6 @@ Then("there are no notifications in the top menu", () => { }); Given("there is an annoying user called {string}", name => { - const annoyingParams = { - email: "spammy-spammer@example.org", - password: "1234", - ...termsAndConditionsAgreedVersion - }; cy.factory().create("User", { ...annoyingParams, id: "annoying-user", @@ -408,6 +425,20 @@ Given("there is an annoying user called {string}", name => { }); }); +Given("there is an annoying user who has muted me", () => { + cy.neode() + .first("User", { + role: 'moderator' + }) + .then(mutedUser => { + cy.neode() + .first("User", { + id: 'annoying-user' + }) + .relateTo(mutedUser, "muted"); + }); +}); + Given("I am on the profile page of the annoying user", name => { cy.openPage("/profile/annoying-user/spammy-spammer"); }); @@ -423,7 +454,7 @@ When("I ", name => { When( "I click on {string} from the content menu in the user info box", button => { - cy.get(".user-content-menu .content-menu-trigger").click(); + cy.get(".user-content-menu .base-button").click(); cy.get(".popover .ds-menu-item-link") .contains(button) .click({ @@ -433,7 +464,7 @@ When( ); When("I navigate to my {string} settings page", settingsPage => { - cy.get(".avatar-menu").click(); + cy.get(".avatar-menu-trigger").click(); cy.get(".avatar-menu-popover") .find("a[href]") .contains("Settings") @@ -488,17 +519,17 @@ Given("I wrote a post {string}", title => { }); }); -When("I block the user {string}", name => { +When("I mute the user {string}", name => { cy.neode() .first("User", { name }) - .then(blocked => { + .then(mutedUser => { cy.neode() .first("User", { name: narratorParams.name }) - .relateTo(blocked, "blocked"); + .relateTo(mutedUser, "muted"); }); }); @@ -519,4 +550,4 @@ Then("I see only one post with the title {string}", title => { .find(".post-link") .should("have.length", 1); cy.get(".main-container").contains(".post-link", title); -}); \ No newline at end of file +}); diff --git a/cypress/integration/internationalization/Internationalization.feature b/cypress/integration/internationalization/Internationalization.feature index 0a5f90ff0..18070d888 100644 --- a/cypress/integration/internationalization/Internationalization.feature +++ b/cypress/integration/internationalization/Internationalization.feature @@ -14,7 +14,7 @@ Feature: Internationalization Examples: Login Button | language | buttonLabel | | Français | Connexion | - | Deutsch | Einloggen | + | Deutsch | Anmelden | | English | Login | Scenario: Keep preferred language after refresh diff --git a/cypress/integration/moderation/ReportContent.feature b/cypress/integration/moderation/ReportContent.feature index 0181dc7a6..105bad5e6 100644 --- a/cypress/integration/moderation/ReportContent.feature +++ b/cypress/integration/moderation/ReportContent.feature @@ -8,13 +8,15 @@ Feature: Report and Moderate So I can look into it and decide what to do Background: - Given we have this user in our database: - | id | name | - | u67 | David Irving| - Given we have the following posts in our database: - | authorId | id | title | content | - | u67 | p1 | The Truth about the Holocaust | It never existed! | + Given we have the following user accounts: + | id | name | + | u67 | David Irving | + | annoying-user | I'm gonna mute Moderators and Admins HA HA HA | + Given we have the following posts in our database: + | authorId | id | title | content | + | u67 | p1 | The Truth about the Holocaust | It never existed! | + | annoying-user | p2 | Fake news | This content is demonstratably infactual in some way | Scenario Outline: Report a post from various pages Given I am logged in with a "user" role When I see David Irving's post on the @@ -48,14 +50,26 @@ Feature: Report and Moderate Scenario: Review reported content Given somebody reported the following posts: - | id | - | p1 | + | submitterEmail | resourceId | reasonCategory | reasonDescription | + | p1.submitter@example.org | p1 | discrimination_etc | Offensive content | And I am logged in with a "moderator" role When I click on the avatar menu in the top right corner And I click on "Moderation" Then I see all the reported posts including the one from above And each list item links to the post page + Scenario: Review reported posts of a user who's muted a moderator + Given somebody reported the following posts: + | submitterEmail | resourceId | reasonCategory | reasonDescription | + | p2.submitter@example.org | p2 | other | Offensive content | + And my user account has the role "moderator" + And there is an annoying user who has muted me + And I am logged in + When I click on the avatar menu in the top right corner + And I click on "Moderation" + Then I see all the reported posts including from the user who muted me + And I can visit the post page + Scenario: Normal user can't see the moderation page Given I am logged in with a "user" role When I click on the avatar menu in the top right corner diff --git a/cypress/integration/notifications/Mentions.feature b/cypress/integration/notifications/Mentions.feature index d3c123863..ef2694abc 100644 --- a/cypress/integration/notifications/Mentions.feature +++ b/cypress/integration/notifications/Mentions.feature @@ -1,4 +1,4 @@ -Feature: Notifications for a mentions +Feature: Notification for a mention As a user I want to be notified if sb. mentions me in a post or comment In order join conversations about or related to me @@ -20,6 +20,7 @@ Feature: Notifications for a mentions """ And mention "@matt-rider" in the text And I select a category + And I choose "en" as the language for the post And I click on "Save" When I log out And I log in with the following credentials: diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index e7e462824..50284d6f5 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -20,3 +20,19 @@ Feature: Post Comment Then my comment should be successfully created And I should see my comment And the editor should be cleared + + Scenario: View medium length comments + Given I visit "post/bWBjpkTKZp/101-essays" + And I type in a comment with 305 characters + And I click on the "Comment" button + Then my comment should be successfully created + And I should see the entirety of my comment + And the editor should be cleared + + Scenario: View long comments + Given I visit "post/bWBjpkTKZp/101-essays" + And I type in a comment with 1205 characters + And I click on the "Comment" button + Then my comment should be successfully created + And I should see an abreviated version of my comment + And the editor should be cleared diff --git a/cypress/integration/post/WritePost.feature b/cypress/integration/post/WritePost.feature index 461766532..0d74606ad 100644 --- a/cypress/integration/post/WritePost.feature +++ b/cypress/integration/post/WritePost.feature @@ -17,7 +17,8 @@ Feature: Create a post Human Connection is a free and open-source social network for active citizenship. """ - Then I select a category + And I select a category + And I choose "en" as the language for the post And I click on "Save" Then I get redirected to ".../my-first-post" And the post was saved successfully diff --git a/cypress/integration/search/Search.feature b/cypress/integration/search/Search.feature index c1afc5b97..e83f58477 100644 --- a/cypress/integration/search/Search.feature +++ b/cypress/integration/search/Search.feature @@ -9,18 +9,23 @@ Feature: Search | id | title | content | | p1 | 101 Essays that will change the way you think | 101 Essays, of course! | | p2 | No searched for content | will be found in this post, I guarantee | + And we have the following user accounts: + | slug | name | id | + | search-for-me | Search for me | user-for-search | + | not-to-be-found | Not to be found | just-an-id | + Given I am logged in Scenario: Search for specific words When I search for "Essays" - Then I should have one post in the select dropdown + Then I should have one item in the select dropdown Then I should see the following posts in the select dropdown: | title | | 101 Essays that will change the way you think | Scenario: Press enter starts search - When I type "Essa" and press Enter - Then I should have one post in the select dropdown + When I type "Es" and press Enter + Then I should have one item in the select dropdown Then I should see the following posts in the select dropdown: | title | | 101 Essays that will change the way you think | @@ -31,11 +36,20 @@ Feature: Search Scenario: Select entry goes to post When I search for "Essays" - And I select an entry + And I select a post entry Then I should be on the post's page Scenario: Select dropdown content When I search for "Essays" - Then I should have one post in the select dropdown + Then I should have one item in the select dropdown Then I should see posts with the searched-for term in the select dropdown And I should not see posts without the searched-for term in the select dropdown + + Scenario: Search for users + Given I search for "Search" + Then I should have one item in the select dropdown + And I should see the following users in the select dropdown: + | slug | + | search-for-me | + And I select a user entry + Then I should be on the user's profile \ No newline at end of file diff --git a/cypress/integration/user_profile/blocked-users/Content.feature b/cypress/integration/user_profile/blocked-users/Content.feature deleted file mode 100644 index edc0d63b9..000000000 --- a/cypress/integration/user_profile/blocked-users/Content.feature +++ /dev/null @@ -1,22 +0,0 @@ -Feature: Block a User - As a user - I'd like to have a button to block another user - To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts - - Background: - Given I have a user account - And there is an annoying user called "Spammy Spammer" - - Scenario Outline: Blocked users cannot see each others posts - Given "Spammy Spammer" wrote a post "Spam Spam Spam" - And I wrote a post "I hate spammers" - And I block the user "Spammy Spammer" - When I log in with: - | Email | Password | - | | | - Then I see only one post with the title "" - Examples: - | email | password | expected_title | - | peterpan@example.org | 1234 | I hate spammers | - | spammy-spammer@example.org | 1234 | Spam Spam Spam | - diff --git a/cypress/integration/user_profile/blocked-users/Blocking.feature b/cypress/integration/user_profile/mute-users/Mute.feature similarity index 53% rename from cypress/integration/user_profile/blocked-users/Blocking.feature rename to cypress/integration/user_profile/mute-users/Mute.feature index 9b27f82a3..b52faeeaa 100644 --- a/cypress/integration/user_profile/blocked-users/Blocking.feature +++ b/cypress/integration/user_profile/mute-users/Mute.feature @@ -1,6 +1,6 @@ -Feature: Block a User +Feature: Mute a User As a user - I'd like to have a button to block another user + I'd like to have a button to mute another user To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts Background: @@ -8,36 +8,46 @@ Feature: Block a User And there is an annoying user called "Spammy Spammer" And I am logged in - Scenario: Block a user + Scenario: Mute a user Given I am on the profile page of the annoying user - When I click on "Block user" from the content menu in the user info box - And I navigate to my "Blocked users" settings page + When I click on "Mute user" from the content menu in the user info box + And I navigate to my "Muted users" settings page Then I can see the following table: | Avatar | Name | | | Spammy Spammer | - Scenario: Block a previously followed user + Scenario: Mute a previously followed user Given I follow the user "Spammy Spammer" And "Spammy Spammer" wrote a post "Spam Spam Spam" When I visit the profile page of the annoying user - And I click on "Block user" from the content menu in the user info box + And I click on "Mute user" from the content menu in the user info box Then the list of posts of this user is empty And nobody is following the user profile anymore - Scenario: Posts of blocked users are filtered from search results + Scenario: Posts of muted users are filtered from search results Given we have the following posts in our database: - | id | title | content | - | im-not-blocked | Post that should be seen | cause I'm not blocked | + | id | title | content | + | im-not-muted | Post that should be seen | cause I'm not muted | Given "Spammy Spammer" wrote a post "Spam Spam Spam" When I search for "Spam" Then I should see the following posts in the select dropdown: | title | | Spam Spam Spam | - When I block the user "Spammy Spammer" + When I mute the user "Spammy Spammer" And I refresh the page And I search for "Spam" Then the search has no results - But I search for "not blocked" + But I search for "not muted" Then I should see the following posts in the select dropdown: | title | | Post that should be seen | + + Scenario: Muted users can still see my posts + Given I previously created a post + And I mute the user "Spammy Spammer" + Given I log out + And I am logged in as the muted user + When I search for "previously created" + Then I should see the following posts in the select dropdown: + | title | + | previously created post | diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 4ec4addb3..893b99f4f 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -11,10 +11,19 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) + const cucumber = require('cypress-cucumber-preprocessor').default -module.exports = on => { +const dotenv = require('dotenv') + +module.exports = (on, config) => { // (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config + const { parsed } = dotenv.config({ path: require.resolve('../../backend/.env') }) + config.env.NEO4J_URI = parsed.NEO4J_URI + config.env.NEO4J_USERNAME = parsed.NEO4J_USERNAME + config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD + on('file:preprocessor', cucumber()) + return config } diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 51ba950a6..c9a2e213a 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -17,9 +17,9 @@ import "cypress-file-upload"; import helpers from "./helpers"; import users from "../fixtures/users.json"; import { GraphQLClient, request } from 'graphql-request' -import { gql } from '../../backend/src/jest/helpers' +import { gql } from '../../backend/src/helpers/jest' +import config from '../../backend/src/config' -const backendHost = Cypress.env('BACKEND_HOST') const switchLang = name => { cy.get(".locale-menu").click(); cy.contains(".locale-menu-popover a", name).click(); @@ -31,7 +31,7 @@ const authenticatedHeaders = async (variables) => { login(email: $email, password: $password) } ` - const response = await request(backendHost, mutation, variables) + const response = await request(config.GRAPHQL_URI, mutation, variables) return { authorization: `Bearer ${response.login}` } } @@ -60,7 +60,7 @@ Cypress.Commands.add("login", ({ email, password }) => { .as("submitButton") .click(); cy.get(".iziToast-message").should("contain", "You are logged in!"); - cy.get(".iziToast-close").click(); + cy.location("pathname").should("eq", "/"); }); Cypress.Commands.add("logout", (email, password) => { @@ -100,8 +100,7 @@ Cypress.Commands.add( 'authenticateAs', async ({email, password}) => { const headers = await authenticatedHeaders({ email, password }) - console.log(headers) - return new GraphQLClient(backendHost, { headers }) + return new GraphQLClient(config.GRAPHQL_URI, { headers }) } ) diff --git a/cypress/support/factories.js b/cypress/support/factories.js index da67debd5..1b76a1a01 100644 --- a/cypress/support/factories.js +++ b/cypress/support/factories.js @@ -1,16 +1,14 @@ -import Factory from '../../backend/src/seed/factories' -import { getDriver, neode as getNeode } from '../../backend/src/bootstrap/neo4j' -import setupNeode from '../../backend/src/bootstrap/neode' -import neode from 'neode' +import Factory from '../../backend/src/factories' +import { getDriver, getNeode } from '../../backend/src/db/neo4j' -const backendHost = Cypress.env('SEED_SERVER_HOST') const neo4jConfigs = { uri: Cypress.env('NEO4J_URI'), username: Cypress.env('NEO4J_USERNAME'), password: Cypress.env('NEO4J_PASSWORD') } const neo4jDriver = getDriver(neo4jConfigs) -const factoryOptions = { seedServerHost: backendHost, neo4jDriver, neodeInstance: setupNeode(neo4jConfigs)} +const neodeInstance = getNeode(neo4jConfigs) +const factoryOptions = { neo4jDriver, neodeInstance } const factory = Factory(factoryOptions) beforeEach(async () => { @@ -18,7 +16,7 @@ beforeEach(async () => { }) Cypress.Commands.add('neode', () => { - return setupNeode(neo4jConfigs) + return neodeInstance }) Cypress.Commands.add( 'first', diff --git a/deployment/digital-ocean/dashboard/README.md b/deployment/digital-ocean/dashboard/README.md index 3ae6378bf..5f66afe0b 100644 --- a/deployment/digital-ocean/dashboard/README.md +++ b/deployment/digital-ocean/dashboard/README.md @@ -5,7 +5,7 @@ The kubernetes dashboard is optional but very helpful for debugging. If you want ```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 +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml ``` ### Login to your dashboard @@ -18,7 +18,7 @@ $ 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/) +[http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/) You should see a login screen. diff --git a/deployment/digital-ocean/https/README.md b/deployment/digital-ocean/https/README.md index d100ba8dd..855a7facf 100644 --- a/deployment/digital-ocean/https/README.md +++ b/deployment/digital-ocean/https/README.md @@ -1,15 +1,16 @@ # 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: +[This resource was also helpful](https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html#installing-with-helm) -```text +```bash $ 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 add jetstack https://charts.jetstack.io $ 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 +$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml +$ helm install --name cert-manager --namespace cert-manager --version v0.11.0 jetstack/cert-manager ``` ## Create Letsencrypt Issuers and Ingress Services diff --git a/deployment/digital-ocean/https/templates/ingress.template.yaml b/deployment/digital-ocean/https/templates/ingress.template.yaml index a1af35bc7..d55c4d834 100644 --- a/deployment/digital-ocean/https/templates/ingress.template.yaml +++ b/deployment/digital-ocean/https/templates/ingress.template.yaml @@ -12,20 +12,20 @@ spec: tls: - hosts: # - nitro-mailserver.human-connection.org - - nitro-staging.human-connection.org + - develop.human-connection.org secretName: tls rules: - - host: nitro-staging.human-connection.org + - host: develop.human-connection.org http: paths: - path: / backend: - serviceName: nitro-web + serviceName: web servicePort: 3000 - # - host: nitro-mailserver.human-connection.org - # http: - # paths: - # - path: / - # backend: - # serviceName: mailserver - # servicePort: 80 + - host: mailserver.human-connection.org + http: + paths: + - path: / + backend: + serviceName: mailserver + servicePort: 80 diff --git a/deployment/human-connection/deployment-backend.yaml b/deployment/human-connection/deployment-backend.yaml index 7d93ebf73..0f75127e5 100644 --- a/deployment/human-connection/deployment-backend.yaml +++ b/deployment/human-connection/deployment-backend.yaml @@ -5,7 +5,7 @@ metadata: labels: human-connection.org/commit: COMMIT human-connection.org/selector: deployment-human-connection-backend - name: nitro-backend + name: backend namespace: human-connection spec: minReadySeconds: 15 @@ -28,7 +28,7 @@ spec: labels: human-connection.org/commit: COMMIT human-connection.org/selector: deployment-human-connection-backend - name: nitro-backend + name: backend spec: containers: - envFrom: diff --git a/deployment/human-connection/deployment-neo4j.yaml b/deployment/human-connection/deployment-neo4j.yaml index 3b381ce7b..5ff67b1a6 100644 --- a/deployment/human-connection/deployment-neo4j.yaml +++ b/deployment/human-connection/deployment-neo4j.yaml @@ -1,47 +1,61 @@ ---- - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: nitro-neo4j - namespace: human-connection - spec: - replicas: 1 - strategy: - rollingUpdate: - maxSurge: 0 - maxUnavailable: "100%" - selector: - matchLabels: +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + human-connection.org/selector: deployment-human-connection-neo4j + name: neo4j + namespace: human-connection +spec: + progressDeadlineSeconds: 2147483647 + replicas: 1 + revisionHistoryLimit: 2147483647 + selector: + matchLabels: + human-connection.org/selector: deployment-human-connection-neo4j + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 100% + type: RollingUpdate + template: + metadata: + annotations: + backup.velero.io/backup-volumes: neo4j-data + creationTimestamp: null + labels: human-connection.org/selector: deployment-human-connection-neo4j - template: - metadata: - annotations: - backup.velero.io/backup-volumes: neo4j-data - labels: - human-connection.org/selector: deployment-human-connection-neo4j - name: nitro-neo4j - spec: - containers: - - name: nitro-neo4j - image: humanconnection/neo4j:latest - imagePullPolicy: Always - resources: - requests: - memory: "2G" - limits: - memory: "8G" - envFrom: - - configMapRef: - name: configmap - ports: - - containerPort: 7687 - - containerPort: 7474 - volumeMounts: - - mountPath: /data/ - name: neo4j-data - volumes: - - name: neo4j-data - persistentVolumeClaim: - claimName: neo4j-data-claim - restartPolicy: Always - terminationGracePeriodSeconds: 30 + name: neo4j + spec: + containers: + - envFrom: + - configMapRef: + name: configmap + image: humanconnection/neo4j:latest + imagePullPolicy: Always + name: neo4j + ports: + - containerPort: 7687 + protocol: TCP + - containerPort: 7474 + protocol: TCP + resources: + limits: + memory: 2G + requests: + memory: 1G + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /data/ + name: neo4j-data + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: neo4j-data + persistentVolumeClaim: + claimName: neo4j-data-claim +status: {} diff --git a/deployment/human-connection/deployment-web.yaml b/deployment/human-connection/deployment-web.yaml index 885762e0a..db9c22a1f 100644 --- a/deployment/human-connection/deployment-web.yaml +++ b/deployment/human-connection/deployment-web.yaml @@ -1,37 +1,54 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: - name: nitro-web + creationTimestamp: null + labels: + human-connection.org/commit: COMMIT + human-connection.org/selector: deployment-human-connection-web + name: web namespace: human-connection spec: - replicas: 2 minReadySeconds: 15 progressDeadlineSeconds: 60 + replicas: 2 + revisionHistoryLimit: 2147483647 selector: matchLabels: human-connection.org/selector: deployment-human-connection-web + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate template: metadata: + creationTimestamp: null labels: human-connection.org/commit: COMMIT human-connection.org/selector: deployment-human-connection-web - name: nitro-web + name: web spec: containers: - - name: web + - env: + - name: HOST + value: 0.0.0.0 envFrom: - configMapRef: name: configmap - secretRef: name: human-connection - env: - - name: HOST - value: 0.0.0.0 image: humanconnection/nitro-web:latest + imagePullPolicy: Always + name: web ports: - containerPort: 3000 + protocol: TCP resources: {} - imagePullPolicy: Always + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} terminationGracePeriodSeconds: 30 status: {} diff --git a/deployment/human-connection/mailserver/deployment-mailserver.yaml b/deployment/human-connection/mailserver/deployment-mailserver.yaml index d97a66bc9..7037c54da 100644 --- a/deployment/human-connection/mailserver/deployment-mailserver.yaml +++ b/deployment/human-connection/mailserver/deployment-mailserver.yaml @@ -1,34 +1,51 @@ ---- - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: mailserver - namespace: human-connection - spec: - replicas: 1 - minReadySeconds: 15 - progressDeadlineSeconds: 60 - selector: - matchLabels: +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + human-connection.org/selector: deployment-human-connection-mailserver + name: mailserver + namespace: human-connection +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 60 + replicas: 1 + revisionHistoryLimit: 2147483647 + selector: + matchLabels: + human-connection.org/selector: deployment-human-connection-mailserver + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: human-connection.org/selector: deployment-human-connection-mailserver - template: - metadata: - labels: - human-connection.org/selector: deployment-human-connection-mailserver - name: "mailserver" - spec: - containers: - - name: mailserver - image: djfarrelly/maildev - imagePullPolicy: Always - ports: - - containerPort: 80 - - containerPort: 25 - envFrom: - - configMapRef: - name: configmap - - secretRef: - name: human-connection - restartPolicy: Always - terminationGracePeriodSeconds: 30 - status: {} + name: mailserver + spec: + containers: + - envFrom: + - configMapRef: + name: configmap + - secretRef: + name: human-connection + image: djfarrelly/maildev + imagePullPolicy: Always + name: mailserver + ports: + - containerPort: 80 + protocol: TCP + - containerPort: 25 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/deployment/human-connection/service-backend.yaml b/deployment/human-connection/service-backend.yaml index 52e4621b2..b36172ea7 100644 --- a/deployment/human-connection/service-backend.yaml +++ b/deployment/human-connection/service-backend.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: nitro-backend + name: backend namespace: human-connection labels: human-connection.org/selector: deployment-human-connection-backend diff --git a/deployment/human-connection/service-neo4j.yaml b/deployment/human-connection/service-neo4j.yaml index ebe7c5208..e5633884b 100644 --- a/deployment/human-connection/service-neo4j.yaml +++ b/deployment/human-connection/service-neo4j.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: nitro-neo4j + name: neo4j namespace: human-connection labels: human-connection.org/selector: deployment-human-connection-neo4j diff --git a/deployment/human-connection/service-web.yaml b/deployment/human-connection/service-web.yaml index 548b874c2..ec39442d5 100644 --- a/deployment/human-connection/service-web.yaml +++ b/deployment/human-connection/service-web.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: nitro-web + name: web namespace: human-connection labels: human-connection.org/selector: deployment-human-connection-web diff --git a/deployment/human-connection/templates/configmap.template.yaml b/deployment/human-connection/templates/configmap.template.yaml index 07c0bb53f..ae093e4bd 100644 --- a/deployment/human-connection/templates/configmap.template.yaml +++ b/deployment/human-connection/templates/configmap.template.yaml @@ -4,7 +4,6 @@ data: SMTP_HOST: "mailserver.human-connection" SMTP_PORT: "25" - GRAPHQL_PORT: "4000" GRAPHQL_URI: "http://nitro-backend.human-connection:4000" NEO4J_URI: "bolt://nitro-neo4j.human-connection:7687" NEO4J_AUTH: "none" diff --git a/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh b/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh index f9334ccd2..b56ace87a 100755 --- a/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh +++ b/deployment/legacy-migration/maintenance-worker/migration/mongo/export.sh @@ -44,7 +44,7 @@ export_collection_query "follows" '{"foreignService": "users"}' "users" # export_collection "settings" export_collection "shouts" # export_collection "status" -export_collection "users" +export_collection_query "users" '{"isVerified": true }' "verified" # export_collection "userscandos" # export_collection "usersettings" diff --git a/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh b/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh index cef2846a7..ccb22dafb 100755 --- a/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh +++ b/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh @@ -78,7 +78,7 @@ echo "DONE" echo "Start Importing Data" import_collection "badges" "badges/badges.cql" import_collection "categories" "categories/categories.cql" -import_collection "users" "users/users.cql" +import_collection "users_verified" "users/users.cql" import_collection "follows_users" "follows/follows.cql" #import_collection "follows_organizations" "follows/follows.cql" import_collection "contributions_post" "contributions/contributions.cql" diff --git a/deployment/minikube/README.md b/deployment/minikube/README.md index e77ddd667..342675b1b 100644 --- a/deployment/minikube/README.md +++ b/deployment/minikube/README.md @@ -9,7 +9,7 @@ open your minikube dashboard: $ 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. +This will give you an overview. Some of the steps below need some timing to make resources available to other dependent deployments. Keeping an eye on the dashboard is a great way to check that. Follow the installation instruction for [Human Connection](../human-connection/README.md). If all the pods and services have settled and everything looks green in your diff --git a/deployment/monitoring/README.md b/deployment/monitoring/README.md new file mode 100644 index 000000000..46dfb0301 --- /dev/null +++ b/deployment/monitoring/README.md @@ -0,0 +1,43 @@ +# Metrics + +You can optionally setup [prometheus](https://prometheus.io/) and +[grafana](https://grafana.com/) for metrics. + +We follow this tutorial [here](https://medium.com/@chris_linguine/how-to-monitor-your-kubernetes-cluster-with-prometheus-and-grafana-2d5704187fc8): + +```bash +kubectl proxy # proxy to your kubernetes dashboard + +helm repo list +# If using helm v3, the stable repository is not set, so you need to manually add it. +helm repo add stable https://kubernetes-charts.storage.googleapis.com +# Create a monitoring namespace for your cluster +kubectl create namespace monitoring +helm --namespace monitoring install prometheus stable/prometheus +kubectl -n monitoring get pods # look for 'server' +kubectl port-forward -n monitoring 9090 +# You can now see your prometheus server on: http://localhost:9090 + +# Make sure you are in folder `deployment/` +kubectl apply -f monitoring/grafana/config.yml +helm --namespace monitoring install grafana stable/grafana -f monitoring/grafana/values.yml +# Get the admin password for grafana from your kubernetes dashboard. +kubectl --namespace monitoring port-forward 3000 +# You can now see your grafana dashboard on: http://localhost:3000 +# Login with user 'admin' and the password you just looked up. +# In your dashboard import this dashboard: +# https://grafana.com/grafana/dashboards/1860 +# Enter ID 180 and choose "Prometheus" as datasource. +# You got metrics! +``` + +Now you should see something like this: + +![Grafana dashboard](./grafana/metrics.png) + +You can set up a grafana dashboard, by visiting https://grafana.com/dashboards, finding one that is suitable and copying it's id. +You then go to the left hand menu in localhost, choose `Dashboard` > `Manage` > `Import` +Paste in the id, click `Load`, select `Prometheus` for the data source, and click `Import` + +When you just installed prometheus and grafana, the data will not be available +immediately, so wait for a couple of minutes and reload. diff --git a/deployment/monitoring/grafana/config.yml b/deployment/monitoring/grafana/config.yml new file mode 100644 index 000000000..a338e3480 --- /dev/null +++ b/deployment/monitoring/grafana/config.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-grafana-datasource + namespace: monitoring + labels: + grafana_datasource: '1' +data: + datasource.yaml: |- + apiVersion: 1 + datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://prometheus-server.monitoring.svc.cluster.local diff --git a/deployment/monitoring/grafana/metrics.png b/deployment/monitoring/grafana/metrics.png new file mode 100644 index 000000000..cc68f1bad Binary files /dev/null and b/deployment/monitoring/grafana/metrics.png differ diff --git a/deployment/monitoring/grafana/values.yml b/deployment/monitoring/grafana/values.yml new file mode 100644 index 000000000..02004cc1c --- /dev/null +++ b/deployment/monitoring/grafana/values.yml @@ -0,0 +1,4 @@ +sidecar: + datasources: + enabled: true + label: grafana_datasource diff --git a/deployment/volumes/neo4j-online-backup/README.md b/deployment/volumes/neo4j-online-backup/README.md new file mode 100644 index 000000000..f096c769f --- /dev/null +++ b/deployment/volumes/neo4j-online-backup/README.md @@ -0,0 +1,57 @@ +# Backup (online) + +## Online backups are only avaible with a Neo4j Enterprise and a license, see https://neo4j.com/licensing/ for the different licenses available + +This tutorial explains how to carry out an online backup of your Neo4J +database in a kubernetes cluster. + +One of the benefits of doing an online backup is that the Neo4j database does not need to be stopped, so there is no downtime. Read [the docs](https://neo4j.com/docs/operations-manual/current/backup/performing/) + +To use Neo4j Enterprise you must add this line to your configmap, if using, or your deployment `nitro-neo4j` env. + +``` +NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes" +``` +## Create a Backup in Kubernetes + +```sh +# Backup the database with one command, this will get the nitro-neo4j pod, ssh into it, and run the backup command +kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }') -- neo4j-admin backup --backup-dir=/var/lib/neo4j --name=neo4j-backup +# Download the file from the pod to your computer. +kubectl cp human-connection/$(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }'):/var/lib/neo4j/neo4j-backup ./neo4j-backup/ +``` + +You should now have a backup of the database locally. If you want, you can simulate disaster recovery by sshing into the nitro-neo4j pod, deleting all data and restoring from backup + +## Disaster where database data is gone somehow + +```sh +kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j |awk '{ print $1 }') bash +# Enter cypher-shell +cypher-shell +# Delete all data +> MATCH (n) DETACH DELETE (n); + +exit +``` + +## Restore a backup in Kubernetes + +Restoration must be done while the database is not running, see [our docs](https://docs.human-connection.org/human-connection/deployment/volumes/neo4j-offline-backup#stop-and-restart-neo-4-j-database-in-kubernetes) for how to stop the database, but keep the container running + +After, you have stopped the database, and have the pod running, you can restore the database by running these commands: + +```sh +kubectl --namespace=human-connection get pods +# 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 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 restore --from=/root/neo4j-backup --force +exit +``` +Revert your changes to deployment `nitro-neo4j` which will restart the database. \ No newline at end of file diff --git a/docker-compose.build-and-test.yml b/docker-compose.build-and-test.yml index e8e79644f..27aa9fc6b 100644 --- a/docker-compose.build-and-test.yml +++ b/docker-compose.build-and-test.yml @@ -2,11 +2,15 @@ version: "3.4" services: webapp: + environment: + - "CI=${CI}" image: humanconnection/nitro-web:build-and-test build: context: webapp target: build-and-test backend: + environment: + - "CI=${CI}" image: humanconnection/nitro-backend:build-and-test build: context: backend diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index 52168c221..b6a869c57 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -15,7 +15,6 @@ services: environment: - NEO4J_dbms_security_auth__enabled=false - NEO4J_dbms_memory_heap_max__size=2G - - GRAPHQL_PORT=4000 - GRAPHQL_URI=http://localhost:4000 - CLIENT_URI=http://localhost:3000 - JWT_SECRET=b/&&7b78BF&fv/Vd diff --git a/docker-compose.override.yml b/docker-compose.override.yml index ba9b32c18..56f13939e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -15,6 +15,7 @@ services: - ./webapp:/nitro-web environment: - NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/` + - PUBLIC_REGISTRATION=false command: yarn run dev backend: build: @@ -28,6 +29,7 @@ services: - SMTP_PORT=25 - SMTP_IGNORE_TLS=true - "DEBUG=${DEBUG}" + - PUBLIC_REGISTRATION=false maintenance: image: humanconnection/maintenance:latest build: diff --git a/docker-compose.yml b/docker-compose.yml index 587ec718f..7b0c00163 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - "BUILD_COMMIT=${TRAVIS_COMMIT}" ports: - 3000:3000 + - 3002:3002 networks: - hc-network depends_on: @@ -38,7 +39,6 @@ services: - uploads:/nitro-backend/public/uploads environment: - NEO4J_URI=bolt://neo4j:7687 - - GRAPHQL_PORT=4000 - GRAPHQL_URI=http://backend:4000 - CLIENT_URI=http://localhost:3000 - JWT_SECRET=b/&&7b78BF&fv/Vd @@ -55,12 +55,13 @@ services: - hc-network environment: - NEO4J_AUTH=none + - NEO4J_dbms_security_procedures_unrestricted=algo.*,apoc.* + - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes ports: - 7687:7687 - 7474:7474 volumes: - neo4j_data:/data - networks: hc-network: volumes: diff --git a/features/support/steps.js b/features/support/steps.js new file mode 100644 index 000000000..71f493834 --- /dev/null +++ b/features/support/steps.js @@ -0,0 +1,48 @@ +// features/support/steps.js +import { Given, When, Then, After, AfterAll } from 'cucumber' +import Factory from '../../backend/src/factories' +import dotenv from 'dotenv' +import expect from 'expect' + +const debug = require('debug')('ea:test:steps') +const factory = Factory() + + +After(async () => { + await factory.cleanDatabase() +}) + +Given('our CLIENT_URI is {string}', function (string) { + expect(string).toEqual('http://localhost:3000') + // This is just for documentation. When you see URLs in the response of + // scenarios you, should be able to tell that it's coming from this + // environment variable. +}); + +Given('we have the following users in our database:', function (dataTable) { + return Promise.all(dataTable.hashes().map(({ slug, name }) => { + return factory.create('User', { + name, + slug, + }) + })) +}) + +When('I send a GET request to {string}', async function (pathname) { + const response = await this.get(pathname) + this.lastContentType = response.lastContentType + + this.lastResponses.push(response.lastResponse) + this.statusCode = response.statusCode +}) + +Then('the server responds with a HTTP Status {int} and the following json:', function (statusCode, docString) { + expect(this.statusCode).toEqual(statusCode) + const [ lastResponse ] = this.lastResponses + expect(JSON.parse(lastResponse)).toMatchObject(JSON.parse(docString)) +}) + +Then('the Content-Type is {string}', function (contentType) { + expect(this.lastContentType).toEqual(contentType) +}) + diff --git a/features/webfinger.feature b/features/webfinger.feature new file mode 100644 index 000000000..1a17e7ea3 --- /dev/null +++ b/features/webfinger.feature @@ -0,0 +1,36 @@ +Feature: Webfinger discovery + From an external server, e.g. Mastodon + I want to search for an actor alias + In order to follow the actor + + Background: + Given our CLIENT_URI is "http://localhost:3000" + And we have the following users in our database: + | name | slug | + | Peter Lustiger | peter-lustiger | + + Scenario: Search a user + When I send a GET request to "/.well-known/webfinger?resource=acct:peter-lustiger@localhost" + Then the server responds with a HTTP Status 200 and the following json: + """ + { + "subject": "acct:peter-lustiger@localhost:3000", + "links": [ + { + "rel": "self", + "type": "application/activity+json", + "href": "http://localhost:3000/activitypub/users/peter-lustiger" + } + ] + } + """ + And the Content-Type is "application/jrd+json; charset=utf-8" + + Scenario: Search without result + When I send a GET request to "/.well-known/webfinger?resource=acct:nonexisting@localhost" + Then the server responds with a HTTP Status 404 and the following json: + """ + { + "error": "No record found for \"nonexisting@localhost\"." + } + """ diff --git a/features/world.js b/features/world.js new file mode 100644 index 000000000..f46a63d4e --- /dev/null +++ b/features/world.js @@ -0,0 +1,38 @@ +import { setWorldConstructor } from 'cucumber' +import request from 'request' + +class CustomWorld { + constructor () { + // webFinger.feature + this.lastResponses = [] + this.lastContentType = null + this.lastInboxUrl = null + this.lastActivity = null + // object-article.feature + this.statusCode = null + } + get (pathname) { + return new Promise((resolve, reject) => { + request(`http://localhost:4000/${this.replaceSlashes(pathname)}`, { + headers: { + 'Accept': 'application/activity+json' + }}, (error, response, body) => { + if (!error) { + resolve({ + lastResponse: body, + lastContentType: response.headers['content-type'], + statusCode: response.statusCode + }) + } else { + reject(error) + } + }) + }) + } + + replaceSlashes (pathname) { + return pathname.replace(/^\/+/, '') + } +} + +setWorldConstructor(CustomWorld) diff --git a/neo4j/.archived/add_image_aspect_ratio.sh b/neo4j/.archived/add_image_aspect_ratio.sh new file mode 100644 index 000000000..8e2a16a01 --- /dev/null +++ b/neo4j/.archived/add_image_aspect_ratio.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then + echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables." + echo "Database manipulation is not possible without connecting to the database." + echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container" +fi + +until echo 'RETURN "Connection successful" as info;' | cypher-shell +do + echo "Connecting to neo4j failed, trying again..." + sleep 1 +done + +echo " + CALL apoc.periodic.iterate(' + CALL apoc.load.csv("out.csv") yield map as row return row + ',' + MATCH (post:Post) where post.image = row.image + set post.imageAspectRatio = row.aspectRatio + ', {batchSize:10000, iterateList:true, parallel:true}); +" | cypher-shell diff --git a/neo4j/.archived/change_disabled_relationship_to_report_node.sh b/neo4j/.archived/change_disabled_relationship_to_report_node.sh new file mode 100644 index 000000000..3227ec63a --- /dev/null +++ b/neo4j/.archived/change_disabled_relationship_to_report_node.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +ENV_FILE=$(dirname "$0")/.env +[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" + +if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then + echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables." + echo "Database manipulation is not possible without connecting to the database." + echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container" +fi + +until echo 'RETURN "Connection successful" as info;' | cypher-shell +do + echo "Connecting to neo4j failed, trying again..." + sleep 1 +done + +echo " +// convert old DISABLED to new REVIEWED-Report-BELONGS_TO structure +MATCH (moderator:User)-[disabled:DISABLED]->(disabledResource) +WHERE disabledResource:User OR disabledResource:Comment OR disabledResource:Post +DELETE disabled +CREATE (moderator)-[review:REVIEWED]->(report:Report)-[:BELONGS_TO]->(disabledResource) +SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true +SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false +// if disabledResource has no filed report, then create a moderators default filed report +WITH moderator, disabledResource, report +OPTIONAL MATCH (disabledResourceReporter:User)-[existingFiledReport:FILED]->(disabledResource) +FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NULL THEN [1] ELSE [] END | + CREATE (moderator)-[addModeratorReport:FILED]->(report) + SET addModeratorReport.createdAt = toString(datetime()), addModeratorReport.reasonCategory = 'other', addModeratorReport.reasonDescription = 'Old DISABLED relations didn't enforce mandatory reporting !!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.' +) +FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NOT NULL THEN [1] ELSE [] END | + CREATE (disabledResourceReporter)-[moveModeratorReport:FILED]->(report) + SET moveModeratorReport = existingFiledReport + DELETE existingFiledReport +) +RETURN disabledResource {.id}; +" | cypher-shell + +echo " +// for FILED resources without DISABLED relation which are handled above, create new FILED-Report-BELONGS_TO structure +MATCH (reporter:User)-[oldReport:REPORTED]->(notDisabledResource) +WHERE notDisabledResource:User OR notDisabledResource:Comment OR notDisabledResource:Post +MERGE (report:Report)-[:BELONGS_TO]->(notDisabledResource) +ON CREATE SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false +CREATE (reporter)-[filed:FILED]->(report) +SET report = oldReport +DELETE oldReport +RETURN notDisabledResource {.id}; +" | cypher-shell diff --git a/neo4j/Dockerfile b/neo4j/Dockerfile index fcd53fa04..b068b22b2 100644 --- a/neo4j/Dockerfile +++ b/neo4j/Dockerfile @@ -1,10 +1,8 @@ -FROM neo4j:3.5.9 +FROM neo4j:3.5.14-enterprise LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)" ARG BUILD_COMMIT ENV BUILD_COMMIT=$BUILD_COMMIT -COPY db_setup.sh /usr/local/bin/db_setup - RUN apt-get update && apt-get -y install wget htop RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.4/apoc-3.5.0.4-all.jar -P plugins/ diff --git a/neo4j/README.md b/neo4j/README.md index fe8825734..a4242b512 100644 --- a/neo4j/README.md +++ b/neo4j/README.md @@ -18,15 +18,6 @@ docker-compose up You can access Neo4J through [http://localhost:7474/](http://localhost:7474/) for an interactive cypher shell and a visualization of the graph. -### Database Indices and Constraints - -Database indices and constraints need to be created when the database is -running. So start the container with the command above and run: - -```bash -docker-compose exec neo4j db_setup -``` - ## Installation without Docker @@ -45,20 +36,6 @@ Then make sure to allow Apoc procedures by adding the following line to your Neo ``` dbms.security.procedures.unrestricted=apoc.* ``` -### Database Indices and Constraints - -If you have `cypher-shell` available with your local installation of neo4j you -can run: - -```bash -# in folder neo4j/ -$ cp .env.template .env -$ ./db_setup.sh -``` - -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). ### Alternatives diff --git a/neo4j/db_setup.sh b/neo4j/db_setup.sh deleted file mode 100755 index ba90ee5f4..000000000 --- a/neo4j/db_setup.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -ENV_FILE=$(dirname "$0")/.env -[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" - -if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then - echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables." - echo "Setting up database constraints and indexes will probably fail because of authentication errors." - echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container" -fi - -until echo 'RETURN "Connection successful" as info;' | cypher-shell -do - echo "Connecting to neo4j failed, trying again..." - sleep 1 -done - -echo ' -RETURN "Here is a list of indexes and constraints BEFORE THE SETUP:" as info; -CALL db.indexes(); -' | cypher-shell - -echo ' -CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]); -CREATE CONSTRAINT ON (p:Post) ASSERT p.id IS UNIQUE; -CREATE CONSTRAINT ON (c:Comment) ASSERT c.id IS UNIQUE; -CREATE CONSTRAINT ON (c:Category) ASSERT c.id IS UNIQUE; -CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE; -CREATE CONSTRAINT ON (t:Tag) ASSERT t.id IS UNIQUE; - -CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE; -CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE; -CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE; - -CREATE CONSTRAINT ON (e:EmailAddress) ASSERT e.email IS UNIQUE; -' | cypher-shell - -echo ' -RETURN "Setting up all the indexes and constraints seems to have been successful. Here is a list AFTER THE SETUP:" as info; -CALL db.indexes(); -' | cypher-shell diff --git a/package.json b/package.json index a1bf24f43..bd8f9312f 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,54 @@ { - "name": "nitro-cypress", - "version": "1.0.0", - "description": "Fullstack tests with cypress for Human Connection", + "name": "human-connection", + "version": "0.2.2", + "description": "Fullstack and API tests with cypress and cucumber for Human Connection", "author": "Human Connection gGmbh", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Human-Connection/Human-Connection.git" + }, "cypress-cucumber-preprocessor": { "nonGlobalStepDefinitions": true }, "scripts": { + "install:all": "yarn install && cd backend && yarn install && cd ../webapp && yarn install", "db:seed": "cd backend && yarn run db:seed", "db:reset": "cd backend && yarn run db:reset", - "cypress:backend:server": "cd backend && yarn run test:before:server", - "cypress:backend:seeder": "cd backend && yarn run test:before:seeder", - "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", - "test:jest": "cd webapp && yarn test && cd ../backend && yarn test:jest && codecov" + "cypress:backend": "cd backend && yarn run dev", + "cypress:webapp": "cd webapp && yarn run dev", + "cypress:setup": "run-p cypress:backend cypress:webapp", + "cypress:run": "cross-env cypress run", + "cypress:open": "cross-env cypress open", + "cucumber:setup": "cd backend && yarn run dev", + "cucumber": "wait-on tcp:4000 && cucumber-js --require-module @babel/register --exit", + "release": "standard-version" }, "devDependencies": { + "@babel/core": "^7.8.3", + "@babel/preset-env": "^7.8.3", + "@babel/register": "^7.8.3", + "auto-changelog": "^1.16.2", "bcryptjs": "^2.4.3", - "codecov": "^3.5.0", - "cross-env": "^5.2.1", - "cypress": "^3.4.1", - "cypress-cucumber-preprocessor": "^1.16.0", - "cypress-file-upload": "^3.3.3", - "cypress-plugin-retries": "^1.3.0", - "dotenv": "^8.1.0", + "codecov": "^3.6.2", + "cross-env": "^6.0.3", + "cucumber": "^6.0.5", + "cypress": "^3.8.3", + "cypress-cucumber-preprocessor": "^2.0.1", + "cypress-file-upload": "^3.5.3", + "cypress-plugin-retries": "^1.5.2", + "date-fns": "^2.9.0", + "dotenv": "^8.2.0", + "expect": "^25.1.0", "faker": "Marak/faker.js#master", "graphql-request": "^1.8.2", - "neo4j-driver": "^1.7.6", - "neode": "^0.3.3", + "neo4j-driver": "^4.0.1", + "neode": "^0.3.7", "npm-run-all": "^4.1.5", - "slug": "^1.1.0" + "slug": "^2.1.1", + "standard-version": "^7.1.0" + }, + "resolutions": { + "set-value": "^2.0.1" } } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index cfde8008d..146d342c9 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash sed -i "s//${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml sed -i "s//${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml -kubectl --namespace=human-connection patch configmap configmap -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml)" -kubectl --namespace=human-connection patch deployment nitro-backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)" -kubectl --namespace=human-connection patch deployment nitro-web -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)" +kubectl --namespace=human-connection patch configmap configmap -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml)" +kubectl --namespace=human-connection patch deployment backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)" +kubectl --namespace=human-connection patch deployment web -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)" diff --git a/scripts/docker_push.sh b/scripts/docker_push.sh index 8456e338e..3c746af92 100755 --- a/scripts/docker_push.sh +++ b/scripts/docker_push.sh @@ -1,30 +1,33 @@ #!/usr/bin/env bash ROOT_DIR=$(dirname "$0")/.. -DOCKER_CLI_EXPERIMENTAL=enabled # BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)} -IFS='.' read -r major minor patch < $ROOT_DIR/VERSION -apps=(nitro-web nitro-backend neo4j maintenance-worker maintenance) -tags=(latest $major $major.$minor $major.$minor.$patch) +VERSION=$(jq -r '.version' $ROOT_DIR/package.json) +IFS='.' read -r major minor patch <<< $VERSION +apps=(nitro-web nitro-backend neo4j maintenance) +tags=($major $major.$minor $major.$minor.$patch) # These three docker images have already been built by now: # docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT --target production -t humanconnection/nitro-backend:latest $ROOT_DIR/backend # docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT --target production -t humanconnection/nitro-web:latest $ROOT_DIR/webapp # docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT -t humanconnection/neo4j:latest $ROOT_DIR/neo4j -docker build -t humanconnection/maintenance-worker:latest $ROOT_DIR/deployment/legacy-migration/maintenance-worker docker build -t humanconnection/maintenance:latest $ROOT_DIR/webapp/ -f $ROOT_DIR/webapp/Dockerfile.maintenance echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin for app in "${apps[@]}" do + SOURCE="humanconnection/${app}:latest" + echo "docker push $SOURCE" + docker push $SOURCE + for tag in "${tags[@]}" do - SOURCE="humanconnection/${app}:latest" TARGET="humanconnection/${app}:${tag}" - if docker manifest inspect $TARGET &> /dev/null; then - echo "Docker image ${TARGET} already present, skipping ..." + if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $TARGET >/dev/null; then + echo "docker image ${TARGET} already present, skipping ..." else + echo -e "docker tag $SOURCE $TARGET\ndocker push $TARGET" docker tag $SOURCE $TARGET docker push $TARGET fi diff --git a/scripts/github_release.sh b/scripts/github_release.sh index 573333a38..93f50289d 100755 --- a/scripts/github_release.sh +++ b/scripts/github_release.sh @@ -2,9 +2,9 @@ ROOT_DIR=$(dirname "$0")/.. RELEASE_DIR="${ROOT_DIR}/release" -VERSION=$(<$ROOT_DIR/VERSION) +VERSION=$(jq -r ".version" $ROOT_DIR/package.json) -mkdir -p $RELEASE_DIR +# mkdir -p $RELEASE_DIR # The following command part produces 854M on my machine # apps=(nitro-web nitro-backend neo4j maintenance-worker maintenance) @@ -13,10 +13,4 @@ mkdir -p $RELEASE_DIR # docker image save "humanconnection/${app}:latest" | gzip > "${RELEASE_DIR}/${app}.${VERSION}.tar.gz" # done -# Use something smaller instead -git archive --format tar HEAD:backend | gzip > "${RELEASE_DIR}/backend.${VERSION}.tar.gz" -git archive --format tar HEAD:webapp | gzip > "${RELEASE_DIR}/webapp.${VERSION}.tar.gz" -git archive --format zip HEAD:backend > "${RELEASE_DIR}/backend.${VERSION}.zip" -git archive --format zip HEAD:webapp > "${RELEASE_DIR}/webapp.${VERSION}.zip" - -ghr -soft "${VERSION}" "${RELEASE_DIR}" +ghr -c "${VERSION}" "${VERSION}" diff --git a/scripts/setup_kubernetes.sh b/scripts/setup_kubernetes.sh index 2596a3e51..ea39312f6 100755 --- a/scripts/setup_kubernetes.sh +++ b/scripts/setup_kubernetes.sh @@ -13,6 +13,6 @@ tar xf doctl-1.14.0-linux-amd64.tar.gz chmod +x ./doctl sudo mv ./doctl /usr/local/bin/doctl -doctl auth init --access-token $DOCTL_ACCESS_TOKEN +doctl auth init --access-token $DIGITALOCEAN_ACCESS_TOKEN mkdir -p ~/.kube/ -doctl kubernetes cluster kubeconfig show nitro-staging > ~/.kube/config +doctl k8s cluster kubeconfig show develop > ~/.kube/config diff --git a/styleguide b/styleguide index 793556ad1..7ef834050 160000 --- a/styleguide +++ b/styleguide @@ -1 +1 @@ -Subproject commit 793556ad19f8cd10a82fd4b69d58bcf3559e3647 +Subproject commit 7ef83405006b016fe45b476ed6e34ec189d7d283 diff --git a/webapp/.babelrc b/webapp/.babelrc index b23873e12..4538e0ac0 100644 --- a/webapp/.babelrc +++ b/webapp/.babelrc @@ -12,6 +12,7 @@ ], "env": { "test": { + "plugins": ["require-context-hook"], "presets": [ [ "@babel/preset-env", @@ -24,4 +25,4 @@ ] } } -} \ No newline at end of file +} diff --git a/webapp/.env.template b/webapp/.env.template index bebdeaaaf..fdabcf003 100644 --- a/webapp/.env.template +++ b/webapp/.env.template @@ -1,3 +1,4 @@ MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" SENTRY_DSN_WEBAPP= COMMIT= +PUBLIC_REGISTRATION=false diff --git a/webapp/.eslintignore b/webapp/.eslintignore index d56900caf..be90fc8e3 100644 --- a/webapp/.eslintignore +++ b/webapp/.eslintignore @@ -3,3 +3,4 @@ build .nuxt styleguide/ **/*.min.js +static/sw.js diff --git a/webapp/.gitignore b/webapp/.gitignore index f8c980f7c..dca219bb5 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -84,3 +84,5 @@ cypress.env.json # Apple macOS folder attribute file .DS_Store + +sw.* diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 87793de92..c5025949b 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.10.0-alpine as base +FROM node:lts-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/Dockerfile.maintenance b/webapp/Dockerfile.maintenance index 116016480..ca4ba37bc 100644 --- a/webapp/Dockerfile.maintenance +++ b/webapp/Dockerfile.maintenance @@ -1,5 +1,5 @@ -FROM node:12.10.0-alpine as build -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)" +FROM node:lts-alpine as build +LABEL Description="Maintenance page 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 CMD ["yarn", "run", "start"] diff --git a/webapp/README.md b/webapp/README.md index 604c7e6ba..897bb56ca 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -6,14 +6,15 @@ ```bash # install all dependencies +$ cd webapp/ $ yarn install ``` Copy: ```text +# in webapp/ cp .env.template .env -cp cypress.env.template.json cypress.env.json ``` Configure the files according to your needs and your local setup. @@ -33,12 +34,74 @@ $ yarn build $ yarn start ``` -## Styleguide +### Run tests -All reusable Components \(for example avatar\) should be done inside the [Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) repository. +We ensure the quality of our frontend code by using +- [ESLint](https://eslint.org/) for checking our JavaScript code +- [Jest](https://jestjs.io/) and [Vue Test Utils](https://vue-test-utils.vuejs.org/) to unit test our components +- [Storybook](https://storybook.js.org/) to document and manually test our components in an isolated playground -![Styleguide Screenshot](../.gitbook/assets/screenshot-styleguide%20%281%29.png) +For more information see our [frontend testing guide](testing.md). Use these commands to run the tests: -More information can be found here: [https://github.com/Human-Connection/Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) +{% tabs %} +{% tab title="With Docker" %} -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! +After starting the application following the above guidelines, open new terminal windows for each of these commands: + +```bash +# run eslint +$ docker-compose exec webapp yarn lint +``` + +```bash +# run unit tests +$ docker-compose exec webapp yarn test +``` + +```bash +# start storybook +$ docker-compose exec webapp yarn storybook +``` + +You can then visit the Storybook playground on `http://localhost:3002` + +{% endtab %} + +{% tab title="Without Docker" %} + +After starting the application following the above guidelines, open new terminal windows and navigate to the `/webapp` directory for each of these commands: + +```bash +# run eslint in /webapp +$ yarn lint +``` + +```bash +# run unit tests in /webapp +$ yarn test +``` + +```bash +# start storybook in /webapp +$ yarn storybook +``` + +You can then visit the Storybook playground on `http://localhost:3002` + +{% endtab %} +{% endtabs %} + +## Styleguide Migration + +We are currently in the process of migrating our styleguide components and design tokens from the [Nitro Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) into the main [Human Connection repository](https://github.com/Human-Connection/Human-Connection) and refactoring our components in the process. During this migration, our new components will live in a `_new/` folder to separate them from the old, yet untouched components. + +### Folder Structure + +The folder structure we are following is [prescribed by Nuxt.js](https://nuxtjs.org/guide/directory-structure): + +- **assets** contains icons, images and logos in `svg` format and all shared SCSS files such as `tokens` +- **components** separated into two sub-folders: + - **generic** are the generic building blocks of the app – small, reusable and usually not coupled to state + - **features** are composed of components but tied to a particular function of the app (e.g. `comment` or `post`) +- **layouts** can use components to create layout templates for pages +- **pages** are the entry points for all `routes` in the app and are composed of layouts, features and components diff --git a/webapp/app/router.scrollBehavior.js b/webapp/app/router.scrollBehavior.js new file mode 100644 index 000000000..d34c4c38a --- /dev/null +++ b/webapp/app/router.scrollBehavior.js @@ -0,0 +1,10 @@ +export default function(to, from, savedPosition) { + if (savedPosition) return savedPosition + + // Edge case: If you click on a notification from a comment and then on the + // post page you click on 'comments', we avoid a "jumping" scroll behavior, + // ie. jump to the top and scroll back from there + if (to.path === from.path && to.hash !== from.hash) return false + + return { x: 0, y: 0 } +} diff --git a/webapp/assets.md b/webapp/assets.md deleted file mode 100644 index 6ac7dc388..000000000 --- a/webapp/assets.md +++ /dev/null @@ -1,5 +0,0 @@ -# ASSETS - -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/assets/_new/icons/index.js b/webapp/assets/_new/icons/index.js new file mode 100644 index 000000000..daa0714f2 --- /dev/null +++ b/webapp/assets/_new/icons/index.js @@ -0,0 +1,13 @@ +const svgFileList = require.context('./svgs', true, /\.svg/) +const icons = {} +const iconNames = [] + +svgFileList.keys().forEach(fileName => { + const svgCode = svgFileList(fileName) + const iconName = fileName.replace('./', '').replace('.svg', '') + icons[iconName] = svgCode + iconNames.push(iconName) +}) + +export { iconNames } +export default icons diff --git a/webapp/assets/_new/icons/svgs/angellist.svg b/webapp/assets/_new/icons/svgs/angellist.svg new file mode 100755 index 000000000..34123f5f0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/angellist.svg @@ -0,0 +1,5 @@ + + +angellist + + diff --git a/webapp/assets/_new/icons/svgs/angle-down.svg b/webapp/assets/_new/icons/svgs/angle-down.svg new file mode 100755 index 000000000..aa6f763c8 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/angle-down.svg @@ -0,0 +1,5 @@ + + +angle-down + + diff --git a/webapp/assets/_new/icons/svgs/arrow-down.svg b/webapp/assets/_new/icons/svgs/arrow-down.svg new file mode 100755 index 000000000..6cee34766 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/arrow-down.svg @@ -0,0 +1,5 @@ + + +arrow-down + + diff --git a/webapp/assets/_new/icons/svgs/arrow-left.svg b/webapp/assets/_new/icons/svgs/arrow-left.svg new file mode 100755 index 000000000..f7489c43a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/arrow-left.svg @@ -0,0 +1,5 @@ + + +arrow-left + + diff --git a/webapp/assets/_new/icons/svgs/arrow-right.svg b/webapp/assets/_new/icons/svgs/arrow-right.svg new file mode 100755 index 000000000..62d753057 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/arrow-right.svg @@ -0,0 +1,5 @@ + + +arrow-right + + diff --git a/webapp/assets/_new/icons/svgs/at.svg b/webapp/assets/_new/icons/svgs/at.svg new file mode 100755 index 000000000..e046ea75a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/at.svg @@ -0,0 +1,5 @@ + + +at + + diff --git a/webapp/assets/_new/icons/svgs/balance-scale.svg b/webapp/assets/_new/icons/svgs/balance-scale.svg new file mode 100755 index 000000000..f5e446fd2 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/balance-scale.svg @@ -0,0 +1,5 @@ + + +balance-scale + + diff --git a/webapp/assets/_new/icons/svgs/ban.svg b/webapp/assets/_new/icons/svgs/ban.svg new file mode 100755 index 000000000..7dc338541 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/ban.svg @@ -0,0 +1,5 @@ + + +ban + + diff --git a/webapp/assets/_new/icons/svgs/bars.svg b/webapp/assets/_new/icons/svgs/bars.svg new file mode 100755 index 000000000..5ea0f464d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/bars.svg @@ -0,0 +1,5 @@ + + +bars + + diff --git a/webapp/assets/_new/icons/svgs/bell.svg b/webapp/assets/_new/icons/svgs/bell.svg new file mode 100755 index 000000000..ee58c443c --- /dev/null +++ b/webapp/assets/_new/icons/svgs/bell.svg @@ -0,0 +1,5 @@ + + +bell + + diff --git a/webapp/assets/_new/icons/svgs/bold.svg b/webapp/assets/_new/icons/svgs/bold.svg new file mode 100755 index 000000000..c3516808d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/bold.svg @@ -0,0 +1,5 @@ + + +bold + + diff --git a/webapp/assets/_new/icons/svgs/bookmark.svg b/webapp/assets/_new/icons/svgs/bookmark.svg new file mode 100755 index 000000000..bd3ff4559 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/bookmark.svg @@ -0,0 +1,5 @@ + + +bookmark + + diff --git a/webapp/assets/_new/icons/svgs/bullhorn.svg b/webapp/assets/_new/icons/svgs/bullhorn.svg new file mode 100755 index 000000000..95e0d21d3 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/bullhorn.svg @@ -0,0 +1,5 @@ + + +bullhorn + + diff --git a/webapp/assets/_new/icons/svgs/check.svg b/webapp/assets/_new/icons/svgs/check.svg new file mode 100755 index 000000000..bf49fe01d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/check.svg @@ -0,0 +1,5 @@ + + +check + + diff --git a/webapp/assets/_new/icons/svgs/clock.svg b/webapp/assets/_new/icons/svgs/clock.svg new file mode 100755 index 000000000..2344dd890 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/clock.svg @@ -0,0 +1,5 @@ + + +clock-o + + diff --git a/webapp/assets/_new/icons/svgs/close.svg b/webapp/assets/_new/icons/svgs/close.svg new file mode 100755 index 000000000..48604d097 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/close.svg @@ -0,0 +1,5 @@ + + +close + + diff --git a/webapp/assets/_new/icons/svgs/cogs.svg b/webapp/assets/_new/icons/svgs/cogs.svg new file mode 100755 index 000000000..3d0104d19 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/cogs.svg @@ -0,0 +1,5 @@ + + +cogs + + diff --git a/webapp/assets/_new/icons/svgs/comment.svg b/webapp/assets/_new/icons/svgs/comment.svg new file mode 100755 index 000000000..fe848ec20 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/comment.svg @@ -0,0 +1,5 @@ + + +comment + + diff --git a/webapp/assets/_new/icons/svgs/comments.svg b/webapp/assets/_new/icons/svgs/comments.svg new file mode 100755 index 000000000..551f10e4e --- /dev/null +++ b/webapp/assets/_new/icons/svgs/comments.svg @@ -0,0 +1,5 @@ + + +comments + + diff --git a/webapp/assets/_new/icons/svgs/edit.svg b/webapp/assets/_new/icons/svgs/edit.svg new file mode 100755 index 000000000..a80101e6c --- /dev/null +++ b/webapp/assets/_new/icons/svgs/edit.svg @@ -0,0 +1,5 @@ + + +edit + + diff --git a/webapp/assets/_new/icons/svgs/ellipsis-v.svg b/webapp/assets/_new/icons/svgs/ellipsis-v.svg new file mode 100755 index 000000000..89313ddfd --- /dev/null +++ b/webapp/assets/_new/icons/svgs/ellipsis-v.svg @@ -0,0 +1,5 @@ + + +ellipsis-v + + diff --git a/webapp/assets/_new/icons/svgs/envelope.svg b/webapp/assets/_new/icons/svgs/envelope.svg new file mode 100755 index 000000000..92d5320c0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/envelope.svg @@ -0,0 +1,5 @@ + + +envelope + + diff --git a/webapp/assets/_new/icons/svgs/exclamation-circle.svg b/webapp/assets/_new/icons/svgs/exclamation-circle.svg new file mode 100755 index 000000000..cd658b997 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/exclamation-circle.svg @@ -0,0 +1,5 @@ + + +exclamation-circle + + diff --git a/webapp/assets/_new/icons/svgs/eye-slash.svg b/webapp/assets/_new/icons/svgs/eye-slash.svg new file mode 100755 index 000000000..4e434ac8d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/eye-slash.svg @@ -0,0 +1,5 @@ + + +eye-slash + + diff --git a/webapp/assets/_new/icons/svgs/eye.svg b/webapp/assets/_new/icons/svgs/eye.svg new file mode 100755 index 000000000..1e3e8fef3 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/eye.svg @@ -0,0 +1,5 @@ + + +eye + + diff --git a/webapp/assets/_new/icons/svgs/file.svg b/webapp/assets/_new/icons/svgs/file.svg new file mode 100755 index 000000000..82d36fe17 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/file.svg @@ -0,0 +1,5 @@ + + +file + + diff --git a/webapp/assets/_new/icons/svgs/filter.svg b/webapp/assets/_new/icons/svgs/filter.svg new file mode 100755 index 000000000..6e9f379b6 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/filter.svg @@ -0,0 +1,5 @@ + + +filter + + diff --git a/webapp/assets/_new/icons/svgs/flag.svg b/webapp/assets/_new/icons/svgs/flag.svg new file mode 100755 index 000000000..6d2769808 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/flag.svg @@ -0,0 +1,5 @@ + + +flag + + diff --git a/webapp/assets/_new/icons/svgs/flash.svg b/webapp/assets/_new/icons/svgs/flash.svg new file mode 100755 index 000000000..f3647d420 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/flash.svg @@ -0,0 +1,5 @@ + + +flash + + diff --git a/webapp/assets/_new/icons/svgs/globe.svg b/webapp/assets/_new/icons/svgs/globe.svg new file mode 100755 index 000000000..91bf9bf2b --- /dev/null +++ b/webapp/assets/_new/icons/svgs/globe.svg @@ -0,0 +1,5 @@ + + +globe + + diff --git a/webapp/assets/_new/icons/svgs/graduation-cap.svg b/webapp/assets/_new/icons/svgs/graduation-cap.svg new file mode 100755 index 000000000..5d35226d3 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/graduation-cap.svg @@ -0,0 +1,5 @@ + + +graduation-cap + + diff --git a/webapp/assets/_new/icons/svgs/heart-o.svg b/webapp/assets/_new/icons/svgs/heart-o.svg new file mode 100755 index 000000000..6605b96ac --- /dev/null +++ b/webapp/assets/_new/icons/svgs/heart-o.svg @@ -0,0 +1,5 @@ + + +heart-o + + diff --git a/webapp/assets/_new/icons/svgs/image.svg b/webapp/assets/_new/icons/svgs/image.svg new file mode 100755 index 000000000..efbd9131f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/image.svg @@ -0,0 +1,5 @@ + + +image + + diff --git a/webapp/assets/_new/icons/svgs/italic.svg b/webapp/assets/_new/icons/svgs/italic.svg new file mode 100755 index 000000000..81d29483c --- /dev/null +++ b/webapp/assets/_new/icons/svgs/italic.svg @@ -0,0 +1,5 @@ + + +italic + + diff --git a/webapp/assets/_new/icons/svgs/link.svg b/webapp/assets/_new/icons/svgs/link.svg new file mode 100755 index 000000000..624a255b1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/link.svg @@ -0,0 +1,5 @@ + + +link + + diff --git a/webapp/assets/_new/icons/svgs/list-ol.svg b/webapp/assets/_new/icons/svgs/list-ol.svg new file mode 100755 index 000000000..f3fb101ac --- /dev/null +++ b/webapp/assets/_new/icons/svgs/list-ol.svg @@ -0,0 +1,5 @@ + + +list-ol + + diff --git a/webapp/assets/_new/icons/svgs/list-ul.svg b/webapp/assets/_new/icons/svgs/list-ul.svg new file mode 100755 index 000000000..d565a8064 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/list-ul.svg @@ -0,0 +1,5 @@ + + +list-ul + + diff --git a/webapp/assets/_new/icons/svgs/lock.svg b/webapp/assets/_new/icons/svgs/lock.svg new file mode 100755 index 000000000..31813d729 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/lock.svg @@ -0,0 +1,5 @@ + + +lock + + diff --git a/webapp/assets/_new/icons/svgs/map-marker.svg b/webapp/assets/_new/icons/svgs/map-marker.svg new file mode 100755 index 000000000..af7f96727 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/map-marker.svg @@ -0,0 +1,5 @@ + + +map-marker + + diff --git a/webapp/assets/_new/icons/svgs/medkit.svg b/webapp/assets/_new/icons/svgs/medkit.svg new file mode 100755 index 000000000..43076dc9c --- /dev/null +++ b/webapp/assets/_new/icons/svgs/medkit.svg @@ -0,0 +1,5 @@ + + +medkit + + diff --git a/webapp/assets/_new/icons/svgs/minus.svg b/webapp/assets/_new/icons/svgs/minus.svg new file mode 100755 index 000000000..b7a94beb1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/minus.svg @@ -0,0 +1,5 @@ + + +minus + + diff --git a/webapp/assets/_new/icons/svgs/money.svg b/webapp/assets/_new/icons/svgs/money.svg new file mode 100755 index 000000000..04a116d2a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/money.svg @@ -0,0 +1,5 @@ + + +money + + diff --git a/webapp/assets/_new/icons/svgs/mouse-pointer.svg b/webapp/assets/_new/icons/svgs/mouse-pointer.svg new file mode 100755 index 000000000..2917ef518 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/mouse-pointer.svg @@ -0,0 +1,5 @@ + + +mouse-pointer + + diff --git a/webapp/assets/_new/icons/svgs/paint-brush.svg b/webapp/assets/_new/icons/svgs/paint-brush.svg new file mode 100755 index 000000000..03b06aac6 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/paint-brush.svg @@ -0,0 +1,5 @@ + + +paint-brush + + diff --git a/webapp/assets/_new/icons/svgs/paragraph.svg b/webapp/assets/_new/icons/svgs/paragraph.svg new file mode 100755 index 000000000..26365f984 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/paragraph.svg @@ -0,0 +1,5 @@ + + +paragraph + + diff --git a/webapp/assets/_new/icons/svgs/paw.svg b/webapp/assets/_new/icons/svgs/paw.svg new file mode 100755 index 000000000..364ff1918 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/paw.svg @@ -0,0 +1,5 @@ + + +paw + + diff --git a/webapp/assets/_new/icons/svgs/plus.svg b/webapp/assets/_new/icons/svgs/plus.svg new file mode 100755 index 000000000..66da52005 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/plus.svg @@ -0,0 +1,5 @@ + + +plus + + diff --git a/webapp/assets/_new/icons/svgs/question-circle.svg b/webapp/assets/_new/icons/svgs/question-circle.svg new file mode 100755 index 000000000..1ae2bbf6b --- /dev/null +++ b/webapp/assets/_new/icons/svgs/question-circle.svg @@ -0,0 +1,5 @@ + + +question-circle + + diff --git a/webapp/assets/_new/icons/svgs/quote-right.svg b/webapp/assets/_new/icons/svgs/quote-right.svg new file mode 100755 index 000000000..4e6469624 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/quote-right.svg @@ -0,0 +1,5 @@ + + +quote-right + + diff --git a/webapp/assets/_new/icons/svgs/search.svg b/webapp/assets/_new/icons/svgs/search.svg new file mode 100755 index 000000000..ddbb4da44 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/search.svg @@ -0,0 +1,5 @@ + + +search + + diff --git a/webapp/assets/_new/icons/svgs/shield.svg b/webapp/assets/_new/icons/svgs/shield.svg new file mode 100755 index 000000000..d897203ad --- /dev/null +++ b/webapp/assets/_new/icons/svgs/shield.svg @@ -0,0 +1,5 @@ + + +shield + + diff --git a/webapp/assets/_new/icons/svgs/shopping-cart.svg b/webapp/assets/_new/icons/svgs/shopping-cart.svg new file mode 100755 index 000000000..9ca3c5e13 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/shopping-cart.svg @@ -0,0 +1,5 @@ + + +shopping-cart + + diff --git a/webapp/assets/_new/icons/svgs/sign-in.svg b/webapp/assets/_new/icons/svgs/sign-in.svg new file mode 100755 index 000000000..bb300f950 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/sign-in.svg @@ -0,0 +1,5 @@ + + +sign-in + + diff --git a/webapp/assets/_new/icons/svgs/sign-out.svg b/webapp/assets/_new/icons/svgs/sign-out.svg new file mode 100755 index 000000000..d185fbe6f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/sign-out.svg @@ -0,0 +1,5 @@ + + +sign-out + + diff --git a/webapp/assets/_new/icons/svgs/smile.svg b/webapp/assets/_new/icons/svgs/smile.svg new file mode 100755 index 000000000..7de0ac746 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/smile.svg @@ -0,0 +1,5 @@ + + +smile-o + + diff --git a/webapp/assets/_new/icons/svgs/sort-amount-asc.svg b/webapp/assets/_new/icons/svgs/sort-amount-asc.svg new file mode 100755 index 000000000..6344da1a9 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/sort-amount-asc.svg @@ -0,0 +1,5 @@ + + +sort-amount-asc + + diff --git a/webapp/assets/_new/icons/svgs/sort-amount-desc.svg b/webapp/assets/_new/icons/svgs/sort-amount-desc.svg new file mode 100755 index 000000000..7239c1d72 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/sort-amount-desc.svg @@ -0,0 +1,5 @@ + + +sort-amount-desc + + diff --git a/webapp/assets/_new/icons/svgs/trash.svg b/webapp/assets/_new/icons/svgs/trash.svg new file mode 100755 index 000000000..6475fd236 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/trash.svg @@ -0,0 +1,5 @@ + + +trash + + diff --git a/webapp/assets/_new/icons/svgs/tree.svg b/webapp/assets/_new/icons/svgs/tree.svg new file mode 100755 index 000000000..e0534af45 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/tree.svg @@ -0,0 +1,5 @@ + + +tree + + diff --git a/webapp/assets/_new/icons/svgs/university.svg b/webapp/assets/_new/icons/svgs/university.svg new file mode 100755 index 000000000..cddb5775a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/university.svg @@ -0,0 +1,5 @@ + + +university + + diff --git a/webapp/assets/_new/icons/svgs/unlink.svg b/webapp/assets/_new/icons/svgs/unlink.svg new file mode 100755 index 000000000..f63b36e9b --- /dev/null +++ b/webapp/assets/_new/icons/svgs/unlink.svg @@ -0,0 +1,5 @@ + + +unlink + + diff --git a/webapp/assets/_new/icons/svgs/user-plus.svg b/webapp/assets/_new/icons/svgs/user-plus.svg new file mode 100755 index 000000000..dea6ab228 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/user-plus.svg @@ -0,0 +1,5 @@ + + +user-plus + + diff --git a/webapp/assets/_new/icons/svgs/user-times.svg b/webapp/assets/_new/icons/svgs/user-times.svg new file mode 100755 index 000000000..8f4cb905a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/user-times.svg @@ -0,0 +1,5 @@ + + +user-times + + diff --git a/webapp/assets/_new/icons/svgs/user.svg b/webapp/assets/_new/icons/svgs/user.svg new file mode 100755 index 000000000..04cc45b41 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/user.svg @@ -0,0 +1,5 @@ + + +user + + diff --git a/webapp/assets/_new/icons/svgs/users.svg b/webapp/assets/_new/icons/svgs/users.svg new file mode 100755 index 000000000..7572021ba --- /dev/null +++ b/webapp/assets/_new/icons/svgs/users.svg @@ -0,0 +1,5 @@ + + +users + + diff --git a/webapp/assets/_new/icons/svgs/warning.svg b/webapp/assets/_new/icons/svgs/warning.svg new file mode 100755 index 000000000..cd0a7baed --- /dev/null +++ b/webapp/assets/_new/icons/svgs/warning.svg @@ -0,0 +1,5 @@ + + +warning + + diff --git a/webapp/assets/_new/styles/mixins/buttonStates.scss b/webapp/assets/_new/styles/mixins/buttonStates.scss new file mode 100644 index 000000000..b8e1509a6 --- /dev/null +++ b/webapp/assets/_new/styles/mixins/buttonStates.scss @@ -0,0 +1,65 @@ +@mixin buttonStates($color-scheme: primary, $filled: false) { + $main-color: $color-primary; + $active-color: $color-primary-dark; + $hover-color: $color-primary-light; + + @if $color-scheme == danger { + $main-color: $color-danger; + $active-color: $color-danger-dark; + $hover-color: $color-danger-light; + } + + color: $main-color; + border-color: $main-color; + background-color: transparent; + transition: background-color $duration-short; + + &:focus { + outline: $border-size-base dashed $main-color; + } + + &:enabled { + &:hover { + color: $color-neutral-100; + border-color: $main-color; + background-color: $main-color; + } + + &:active { + color: $color-neutral-100; + border-color: $active-color; + background-color: $active-color; + } + } + + &:disabled { + color: $color-neutral-60; + border-color: $color-neutral-60; + cursor: default; + } + + @if $filled { + color: $color-neutral-100; + border-color: $main-color; + background-color: $main-color; + + &:enabled { + &:hover { + border-color: $hover-color; + background-color: $hover-color; + } + + &:active { + color: $color-neutral-100; + border-color: $active-color; + background-color: $active-color; + } + } + + &:disabled { + color: $color-neutral-100; + background-color: $color-neutral-60; + border-color: $color-neutral-60; + } + } +} diff --git a/webapp/assets/_new/styles/resets.scss b/webapp/assets/_new/styles/resets.scss new file mode 100644 index 000000000..2784add5f --- /dev/null +++ b/webapp/assets/_new/styles/resets.scss @@ -0,0 +1,11 @@ +* { + box-sizing: border-box; +} + +button { + padding: 0; + background: transparent; + border: none; + font-family: inherit; + font-size: inherit; +} diff --git a/webapp/assets/_new/styles/tokens.scss b/webapp/assets/_new/styles/tokens.scss new file mode 100644 index 000000000..90ec527ea --- /dev/null +++ b/webapp/assets/_new/styles/tokens.scss @@ -0,0 +1,329 @@ +/** + * @tokens Color Brand + * @presenter Color + */ + + $color-primary: rgb(23, 181, 63); + $color-primary-light: rgb(96, 214, 98); + $color-primary-dark: rgb(25, 122, 49); + $color-primary-active: rgb(25, 194, 67); + $color-primary-inverse: rgb(241, 253, 244); + $color-secondary: rgb(0, 142, 230); + $color-secondary-active: rgb(10, 161, 255); + $color-secondary-inverse: rgb(240, 249, 255); + $color-success: rgb(23, 181, 63); + $color-success-active: rgb(26, 203, 71); + $color-success-inverse: rgb(241, 253, 244); + $color-danger: rgb(219, 57, 36); + $color-danger-light: rgb(242, 97, 65); + $color-danger-dark: rgb(158, 43, 28); + $color-danger-active: rgb(224, 81, 62); + $color-danger-inverse: rgb(253, 243, 242); + $color-warning: rgb(230, 121, 25); + $color-warning-active: rgb(233, 137, 53); + $color-warning-inverse: rgb(253, 247, 241); + $color-yellow: rgb(245, 196, 0); + $color-yellow-active: rgb(255, 206, 10); + $color-yellow-inverse: rgb(255, 252, 240); + +/** + * @tokens Color Neutral + * @presenter Color + */ + +$color-neutral-0: rgb(25, 23, 28); +$color-neutral-10: rgb(40, 37, 45); +$color-neutral-20: rgb(75, 69, 84); +$color-neutral-30: rgb(100, 92, 112); +$color-neutral-40: rgb(112, 103, 126); +$color-neutral-50: rgb(151, 143, 163); +$color-neutral-60: rgb(177, 171, 186); +$color-neutral-70: rgb(203, 199, 209); +$color-neutral-80: rgb(229, 227, 232); +$color-neutral-85: rgb(239, 238, 241); +$color-neutral-90: rgb(245, 244, 246); +$color-neutral-95: rgb(250, 249, 250); +$color-neutral-100: rgb(255, 255, 255); + +/** + * @tokens Color Text + * @presenter Color + */ + +$text-color-base: rgb(75, 69, 84); +$text-color-soft: rgb(112, 103, 126); +$text-color-softer: rgb(177, 171, 186); +$text-color-disabled: rgb(177, 171, 186); +$text-color-inverse: rgb(250, 249, 250); +$text-color-link: rgb(23, 181, 63); +$text-color-link-active: rgb(25, 194, 67); +$text-color-primary: rgb(23, 181, 63); +$text-color-primary-inverse: rgb(241, 253, 244); +$text-color-secondary: rgb(0, 142, 230); +$text-color-secondary-inverse: rgb(240, 249, 255); +$text-color-success: rgb(23, 181, 63); +$text-color-success-inverse: rgb(241, 253, 244); +$text-color-warning: rgb(230, 121, 25); +$text-color-warning-inverse: rgb(253, 247, 241); +$text-color-danger: rgb(219, 57, 36); +$text-color-danger-inverse: rgb(253, 243, 242); + +/** + * @tokens Color Background + * @presenter Color + */ + +$background-color-base: rgb(255, 255, 255); +$background-color-soft: rgb(250, 249, 250); +$background-color-softer: rgb(245, 244, 246); +$background-color-softer-active: rgb(250, 249, 250); +$background-color-softest: rgb(239, 238, 241); +$background-color-softest-active: rgb(245, 244, 246); +$background-color-inverse: rgb(25, 23, 28); +$background-color-inverse-soft: rgb(40, 37, 45); +$background-color-inverse-softer: rgb(75, 69, 84); +$background-color-inverse-softer-active: rgb(100, 92, 112); +$background-color-disabled: rgb(245, 244, 246); +$background-color-primary: rgb(23, 181, 63); +$background-color-primary-active: rgb(25, 194, 67); +$background-color-primary-inverse: rgb(241, 253, 244); +$background-color-secondary: rgb(0, 142, 230); +$background-color-secondary-active: rgb(10, 161, 255); +$background-color-secondary-inverse: rgb(240, 249, 255); +$background-color-success: rgb(23, 181, 63); +$background-color-success-active: rgb(26, 203, 71); +$background-color-success-inverse: rgb(241, 253, 244); +$background-color-warning: rgb(230, 121, 25); +$background-color-warning-active: rgb(233, 137, 53); +$background-color-warning-inverse: rgb(253, 247, 241); +$background-color-danger: rgb(219, 57, 36); +$background-color-danger-active: rgb(224, 81, 62); +$background-color-danger-inverse: rgb(253, 243, 242); + +/** + * @tokens Color Border + * @presenter Color + */ + +$border-color-base: rgb(177, 171, 186); +$border-color-soft: rgb(203, 199, 209); +$border-color-softer: rgb(229, 227, 232); +$border-color-softest: rgb(245, 244, 246); +$border-color-active: rgb(23, 181, 63); +$border-color-primary: rgb(23, 181, 63); +$border-color-success: rgb(23, 181, 63); +$border-color-warning: rgb(230, 121, 25); +$border-color-danger: rgb(219, 57, 36); + +/** + * @tokens Border Size + * @presenter Border + */ + +$border-size-base: 1px; +$border-size-large: 3px; +$border-size-x-large: 6px; + +/** + * @tokens Border Radius + * @presenter BorderRadius + */ + +$border-radius-x-large: 5px; +$border-radius-large: 4px; +$border-radius-base: 4px; +$border-radius-rounded: 2em; +$border-radius-circle: 50%; + +/** + * @tokens Font Size + * @presenter FontSize + */ + +$font-size-xxxx-large: 3rem; +$font-size-xxx-large: 2.5rem; +$font-size-xx-large: 2rem; +$font-size-x-large: 1.5rem; +$font-size-large: 1.25rem; +$font-size-base: 1rem; +$font-size-body: 15px; +$font-size-small: 0.8rem; +$font-size-x-small: 0.7rem; +$font-size-xx-small: 0.6rem; + +/** + * @tokens Font Space + * @presenter Spacing + */ + +$font-space-xxxx-large: 2em; +$font-space-xxx-large: 1.5em; +$font-space-xx-large: 1.2em; +$font-space-x-large: 1em; +$font-space-large: 0.6em; +$font-space-base: 0.5em; +$font-space-small: 0.4em; +$font-space-x-small: 0.3em; +$font-space-xx-small: 0.2em; +$font-space-xxx-small: 0.1em; + +/** + * @tokens Font Family + * @presenter FontFamily + */ + +$font-family-heading: 'LatoWeb', sans-serif; +$font-family-text: 'LatoWeb', sans-serif; +$font-family-serif: 'Gentium Basic', serif; +$font-family-code: inconsolata, monospace; + +/** + * @tokens Font Weight + * @presenter FontWeight + */ + +$font-weight-regular: normal; +$font-weight-bold: 600; + +/** + * @tokens Line Height + * @presenter LineHeight + */ + +$line-height-large: 1.5; +$line-height-base: 1.3; +$line-height-small: 1.1; +$line-height-smaller: 1.0; + +/** + * @tokens Letter Spacing + * @presenter Spacing + */ + +$letter-spacing-x-large: 0.1em; +$letter-spacing-large: 0.05em; +$letter-spacing-base: 0; +$letter-spacing-small: -0.01em; +$letter-spacing-x-small: -0.015em; + +/** + * @tokens Opacity + * @presenter Opacity + */ + +$opacity-soft: 0.65; +$opacity-disabled: 0.5; + +/** + * @tokens Space + * @presenter Spacing + */ + +$space-xxx-large: 128px; +$space-xx-large: 64px; +$space-x-large: 48px; +$space-large: 32px; +$space-base: 24px; +$space-small: 16px; +$space-x-small: 8px; +$space-xx-small: 4px; +$space-xxx-small: 2px; + +/** + * @tokens Size Height + * @presenter Spacing + */ + +$size-height-base: 42px; +$size-height-large: 50px; +$size-height-xlarge: 60px; +$size-height-footer: 64px; +$size-tappable-square: 44px; + +/** + * @tokens Size Width + * @presenter Spacing + */ + +$size-width-paginate: 100px; + +/** + * @tokens Size Avatar + * @presenter Spacing + */ + +$size-avatar-small: 34px; +$size-avatar-base: 44px; +$size-avatar-large: 114px; + +/** + * @tokens Size Buttons + * @presenter Spacing + */ + + $size-button-base: 36px; + $size-button-small: 26px; + +/** + * @tokens Size Icons + * @presenter Spacing + */ + + $size-icon-base: 16px; + +/** + * @tokens Shadow + * @presenter Shadow + */ + +$box-shadow-x-large: 0 15px 30px 0 rgba(0,0,0,.11),0 5px 15px 0 rgba(0,0,0,.08); +$box-shadow-large: 0 10px 20px 0 rgba(0,0,0,.11),0 3px 10px 0 rgba(0,0,0,.08); +$box-shadow-base: 0px 12px 26px -4px rgba(0, 0, 0, .1); +$box-shadow-small: 0px 8px 18px -2px rgba(0, 0, 0, .1); +$box-shadow-x-small: 0px 0px 3px 0px rgba(0, 0, 0, .1); +$box-shadow-active: 0 0 6px 1px rgba(20, 100, 160, 0.5); +$box-shadow-inset: inset 0 0 20px 1px rgba(0,0,0,.15); +$box-shadow-small-inset: inset 0 0 0 1px rgba(0,0,0,.05); + +/** + * @tokens Animation Duration + */ + +$duration-short: 0.08s; +$duration-base: 0.5s; +$duration-long: 0.75s; +$duration-x-long: 1s; +$duration-xx-long: 2s; + +/** + * @tokens Animation Ease + * @presenter Easing + */ + +$ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); +$ease-out-sharp: cubic-bezier(0.165, 0.84, 0.44, 1); +$ease-out-bounce: cubic-bezier(.87,-.41,.19,1.44); +$ease-in: cubic-bezier(0.55, 0.085, 0.68, 0.53); +$ease-in-sharp: cubic-bezier(0.895, 0.03, 0.685, 0.22); + +/** + * @tokens Z-Index + */ + +$z-index-modal: 9999; +$z-index-dropdown: 8888; +$z-index-page-submenu: 2500; +$z-index-page-header: 2000; +$z-index-page-sidebar: 1500; +$z-index-sticky: 100; +$z-index-post-card-link: 5; + +/** + * @tokens Media Query + */ + +$media-query-x-small: (min-width: 480px); +$media-query-small: (min-width: 600px); +$media-query-medium: (min-width: 768px); +$media-query-large: (min-width: 1024px); +$media-query-x-large: (min-width: 1200px); diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss index 5c4964688..fbab1d78f 100644 --- a/webapp/assets/styles/main.scss +++ b/webapp/assets/styles/main.scss @@ -160,7 +160,7 @@ hr { align-content: center; align-items: center; - .ds-icon { + .base-icon { padding-right: $space-xx-small; } } @@ -175,3 +175,7 @@ hr { overflow-wrap: break-word; word-wrap: break-word; } + +.dropdown-arrow { + font-size: $font-size-xx-small; +} diff --git a/webapp/components.md b/webapp/components.md index 92b3dd1fb..ea99214ec 100644 --- a/webapp/components.md +++ b/webapp/components.md @@ -1,5 +1,38 @@ -# COMPONENTS +# Components – Code Guidelines -The components directory contains your Vue.js Components. +## We adhere to the [single responsibility principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) -_Nuxt.js doesn't supercharge these components._ +Each component does _exactly one job_. The goal is to end up with many small components that are: +- easy to understand +- easy to maintain +- easy to reuse + +**How do you decide what is a separate component?** Try to describe what it does in _one sentence_! When you find yourself using `and` and `or` the code you are talking about should probably be split up into two or more components. + +On the other hand, when something is easily expressed in a few lines of HTML and SCSS and not likely to be reused this is a good indicator that it should _not_ go into a separate component. + +## We compose with components + +Usually `pages` use `layouts` as templates and will be composed of `features`. `features` are composed of `components`, the smallest building blocks of the app. The further down we go in this hierarchy the simpler and more generic the components become. Here is an example: + +- The `index` page is responsible for displaying a list of posts. It uses the `default` layout and the `PostList` feature. +- The `PostList` feature uses a `List` component to render `PostTeaser` features. +- The `PostTeaser` feature consists of a `LayoutCard` wrapped around a `CardImage`, `CardTitle` and `CardContent` component. + +The `index` page is unique in the app and will never be reused. The `PostList` knows it is handling post data and can therefore not be used for anything else – but it can display posts on the `index` as well as the `user` page. + +The `Card` on the other hand does not care about the type of data it needs to handle. It just takes whatever it receives and renders it in a certain way, so it can be reused throughout the app for many different features. + +## We use two-word names + +We follow the W3C rules for naming custom elements as suggested in the [Vue.js docs](https://vuejs.org/v2/guide/components-registration.html#Component-Names) to differentiate our own components from regular HTML elements in our templates. + +Names should also be meaningful and unique to avoid confusion and code duplication, and also not too long to make them readable. Therefore: aim for two-word names, such as `layout-card`, `post-list` or `post-teaser`. + +## Recommended reads + +For a deeper dive into the WHY and HOW have a look at the following resources which the above guidelines are based on: + +- [Atomic design](https://bradfrost.com/blog/post/atomic-web-design/) +- [CDD – component based design](https://medium.com/@wereheavyweight/how-were-using-component-based-design-5f9e3176babb) +- [Vue.js component styleguide](https://pablohpsilva.github.io/vuejs-component-style-guide/#/) diff --git a/webapp/components/Avatar/Avatar.spec.js b/webapp/components/Avatar/Avatar.spec.js deleted file mode 100644 index 626e584c9..000000000 --- a/webapp/components/Avatar/Avatar.spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils' -import Styleguide from '@human-connection/styleguide' -import Avatar from './Avatar.vue' -import Filters from '~/plugins/vue-filters' - -const localVue = createLocalVue() -localVue.use(Styleguide) -localVue.use(Filters) - -describe('Avatar.vue', () => { - let propsData = {} - - const Wrapper = () => { - return mount(Avatar, { propsData, localVue }) - } - - it('renders no image', () => { - expect( - Wrapper() - .find('img') - .exists(), - ).toBe(false) - }) - - it('renders an icon', () => { - expect( - Wrapper() - .find('.ds-icon') - .exists(), - ).toBe(true) - }) - - describe('given a user', () => { - describe('with a relative avatar url', () => { - beforeEach(() => { - propsData = { - user: { - avatar: '/avatar.jpg', - }, - } - }) - - it('adds a prefix to load the image from the uploads service', () => { - expect( - Wrapper() - .find('img') - .attributes('src'), - ).toBe('/api/avatar.jpg') - }) - }) - - describe('with an absolute avatar url', () => { - beforeEach(() => { - propsData = { - user: { - avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg', - }, - } - }) - - it('keeps the avatar URL as is', () => { - // e.g. our seeds have absolute image URLs - expect( - Wrapper() - .find('img') - .attributes('src'), - ).toBe('https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg') - }) - }) - }) -}) diff --git a/webapp/components/Avatar/Avatar.vue b/webapp/components/Avatar/Avatar.vue deleted file mode 100644 index ec2f9b28b..000000000 --- a/webapp/components/Avatar/Avatar.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/webapp/components/AvatarMenu/AvatarMenu.spec.js b/webapp/components/AvatarMenu/AvatarMenu.spec.js new file mode 100644 index 000000000..c432a5ad8 --- /dev/null +++ b/webapp/components/AvatarMenu/AvatarMenu.spec.js @@ -0,0 +1,161 @@ +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' +import AvatarMenu from './AvatarMenu.vue' + +const localVue = global.localVue + +config.stubs['nuxt-link'] = '' +config.stubs['router-link'] = '' + +describe('AvatarMenu.vue', () => { + let propsData, getters, wrapper, mocks + + beforeEach(() => { + propsData = {} + mocks = { + $route: { + path: '', + }, + $router: { + resolve: jest.fn(() => { + return { href: '/profile/u343/matt' } + }), + }, + $t: jest.fn(a => a), + } + getters = { + 'auth/user': () => { + return { id: 'u343', name: 'Matt' } + }, + } + }) + + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(AvatarMenu, { propsData, localVue, store, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the UserAvatar component', () => { + wrapper.find('.avatar-menu-trigger').trigger('click') + expect(wrapper.find('.user-avatar').exists()).toBe(true) + }) + + describe('given a userName', () => { + it('displays the userName', () => { + expect(wrapper.find('b').text()).toEqual('Matt') + }) + }) + + describe('no userName', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { id: 'u343' } + }, + } + wrapper = Wrapper() + wrapper.find('.avatar-menu-trigger').trigger('click') + }) + + it('displays anonymous user', () => { + expect(wrapper.find('b').text()).toEqual('profile.userAnonym') + }) + }) + + describe('menu items', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { id: 'u343', slug: 'matt' } + }, + 'auth/isModerator': () => false, + 'auth/isAdmin': () => false, + } + wrapper = Wrapper() + wrapper.find('.avatar-menu-trigger').trigger('click') + }) + + describe('role user', () => { + it('displays a link to user profile', () => { + const profileLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex(route => route.path === '/profile/u343/matt')) + expect(profileLink.exists()).toBe(true) + }) + + it('displays a link to the notifications page', () => { + const notificationsLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex(route => route.path === '/notifications')) + expect(notificationsLink.exists()).toBe(true) + }) + + it('displays a link to the settings page', () => { + const settingsLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex(route => route.path === '/settings')) + expect(settingsLink.exists()).toBe(true) + }) + }) + + describe('role moderator', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { id: 'u343', slug: 'matt' } + }, + 'auth/isModerator': () => true, + 'auth/isAdmin': () => false, + } + wrapper = Wrapper() + wrapper.find('.avatar-menu-trigger').trigger('click') + }) + + it('displays a link to moderation page', () => { + const moderationLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex(route => route.path === '/moderation')) + expect(moderationLink.exists()).toBe(true) + }) + + it('displays a total of 4 links', () => { + const allLinks = wrapper.findAll('.ds-menu-item') + expect(allLinks).toHaveLength(4) + }) + }) + + describe('role admin', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { id: 'u343', slug: 'matt' } + }, + 'auth/isModerator': () => true, + 'auth/isAdmin': () => true, + } + wrapper = Wrapper() + wrapper.find('.avatar-menu-trigger').trigger('click') + }) + + it('displays a link to admin page', () => { + const adminLink = wrapper + .findAll('.ds-menu-item span') + .at(wrapper.vm.routes.findIndex(route => route.path === '/admin')) + expect(adminLink.exists()).toBe(true) + }) + + it('displays a total of 5 links', () => { + const allLinks = wrapper.findAll('.ds-menu-item') + expect(allLinks).toHaveLength(5) + }) + }) + }) + }) +}) diff --git a/webapp/components/AvatarMenu/AvatarMenu.story.js b/webapp/components/AvatarMenu/AvatarMenu.story.js new file mode 100644 index 000000000..9146075cd --- /dev/null +++ b/webapp/components/AvatarMenu/AvatarMenu.story.js @@ -0,0 +1,17 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import StoryRouter from 'storybook-vue-router' +import AvatarMenu from '~/components/AvatarMenu/AvatarMenu' +import helpers from '~/storybook/helpers' + +helpers.init() + +storiesOf('AvatarMenu', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .addDecorator(StoryRouter()) + .add('dropdown', () => ({ + components: { AvatarMenu }, + store: helpers.store, + template: '', + })) diff --git a/webapp/components/AvatarMenu/AvatarMenu.vue b/webapp/components/AvatarMenu/AvatarMenu.vue new file mode 100644 index 000000000..f65c6f6cf --- /dev/null +++ b/webapp/components/AvatarMenu/AvatarMenu.vue @@ -0,0 +1,150 @@ + + + diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.spec.js b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js index 199dacb74..82f5e61eb 100644 --- a/webapp/components/CategoriesSelect/CategoriesSelect.spec.js +++ b/webapp/components/CategoriesSelect/CategoriesSelect.spec.js @@ -1,17 +1,18 @@ -import { mount, createLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import CategoriesSelect from './CategoriesSelect' -import Styleguide from '@human-connection/styleguide' +import Vue from 'vue' -const localVue = createLocalVue() -localVue.use(Styleguide) +const localVue = global.localVue describe('CategoriesSelect.vue', () => { let wrapper let mocks + let provide let democracyAndPolitics let environmentAndNature let consumptionAndSustainablity + const propsData = { model: 'categoryIds' } const categories = [ { id: 'cat9', @@ -35,6 +36,11 @@ describe('CategoriesSelect.vue', () => { }, ] beforeEach(() => { + provide = { + $parentForm: { + update: jest.fn(), + }, + } mocks = { $t: jest.fn(), } @@ -42,7 +48,7 @@ describe('CategoriesSelect.vue', () => { describe('shallowMount', () => { const Wrapper = () => { - return mount(CategoriesSelect, { mocks, localVue }) + return mount(CategoriesSelect, { propsData, mocks, localVue, provide }) } beforeEach(() => { @@ -50,8 +56,9 @@ describe('CategoriesSelect.vue', () => { }) describe('toggleCategory', () => { - beforeEach(() => { + beforeEach(async () => { wrapper.vm.categories = categories + await Vue.nextTick() democracyAndPolitics = wrapper.findAll('button').at(0) democracyAndPolitics.trigger('click') }) @@ -60,8 +67,8 @@ describe('CategoriesSelect.vue', () => { expect(wrapper.vm.selectedCategoryIds).toEqual([categories[0].id]) }) - it('emits an updateCategories event when the selectedCategoryIds changes', () => { - expect(wrapper.emitted().updateCategories[0][0]).toEqual([categories[0].id]) + it('calls $parent.update with selected category ids', () => { + expect(provide.$parentForm.update).toHaveBeenCalledWith('categoryIds', ['cat9']) }) it('removes categories when clicked a second time', () => { diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.vue b/webapp/components/CategoriesSelect/CategoriesSelect.vue index cbe46b890..3e240e435 100644 --- a/webapp/components/CategoriesSelect/CategoriesSelect.vue +++ b/webapp/components/CategoriesSelect/CategoriesSelect.vue @@ -3,15 +3,16 @@
- - {{ $t(`contribution.category.name.${category.slug}`) }} - +
@@ -28,16 +29,23 @@ + + diff --git a/webapp/components/Comment.spec.js b/webapp/components/Comment/Comment.spec.js similarity index 77% rename from webapp/components/Comment.spec.js rename to webapp/components/Comment/Comment.spec.js index 81972e501..b307700d9 100644 --- a/webapp/components/Comment.spec.js +++ b/webapp/components/Comment/Comment.spec.js @@ -1,12 +1,8 @@ -import { config, shallowMount, createLocalVue } from '@vue/test-utils' +import { config, shallowMount } from '@vue/test-utils' import Comment from './Comment.vue' import Vuex from 'vuex' -import Styleguide from '@human-connection/styleguide' -const localVue = createLocalVue() - -localVue.use(Vuex) -localVue.use(Styleguide) +const localVue = global.localVue config.stubs['client-only'] = '' @@ -30,7 +26,9 @@ describe('Comment.vue', () => { }, $filters: { truncate: a => a, + removeHtml: a => a, }, + $scrollTo: jest.fn(), $apollo: { mutate: jest.fn().mockResolvedValue({ data: { @@ -50,6 +48,8 @@ describe('Comment.vue', () => { }) describe('shallowMount', () => { + beforeEach(jest.useFakeTimers) + Wrapper = () => { const store = new Vuex.Store({ getters, @@ -71,7 +71,8 @@ describe('Comment.vue', () => { } }) - it('renders content', () => { + // skipped for now because of the immense difficulty in testing tiptap editor + it.skip('renders content', () => { wrapper = Wrapper() expect(wrapper.text()).toMatch('Hello I am a comment content') }) @@ -103,7 +104,7 @@ describe('Comment.vue', () => { getters['auth/isModerator'] = () => true }) - it('renders comment data', () => { + it.skip('renders comment data', () => { wrapper = Wrapper() expect(wrapper.text()).toMatch('comment content') }) @@ -115,7 +116,35 @@ describe('Comment.vue', () => { }) }) - beforeEach(jest.useFakeTimers) + describe('scrollToAnchor mixin', () => { + describe('$route.hash !== comment.id', () => { + beforeEach(() => { + mocks.$route = { + hash: '', + } + }) + + it('skips $scrollTo', () => { + wrapper = Wrapper() + jest.runAllTimers() + expect(mocks.$scrollTo).not.toHaveBeenCalled() + }) + }) + + describe('$route.hash === comment.id', () => { + beforeEach(() => { + mocks.$route = { + hash: '#commentId-2', + } + }) + + it('calls $scrollTo', () => { + wrapper = Wrapper() + jest.runAllTimers() + expect(mocks.$scrollTo).toHaveBeenCalledWith('#commentId-2') + }) + }) + }) describe('test callbacks', () => { beforeEach(() => { diff --git a/webapp/components/Comment/Comment.story.js b/webapp/components/Comment/Comment.story.js new file mode 100644 index 000000000..291b6cb11 --- /dev/null +++ b/webapp/components/Comment/Comment.story.js @@ -0,0 +1,54 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import Comment from './Comment' +import helpers from '~/storybook/helpers' + +helpers.init() + +const comment = { + id: '5d42a2277f2725002a449cb3', + content: + '

Thank you all!

@wolfgang-huss @robert-schafer @greg @human-connection

watch my video

I think we can all learn a lot from Alex\'s video :)

It\'s really great stuff!!

Please give him a big smiley face emoticon :D

', + contentExcerpt: + '

Thank you all!

@wolfgang-huss @robert-schafer @greg @human-connection

watch my video

I think we can all learn a lot from Alex\'s video :)

It\'s really great stuff!!

Please give him a …

', + createdAt: '2019-08-01T08:26:15.839Z', + updatedAt: '2019-08-01T08:26:15.839Z', + deleted: false, + disabled: false, + author: { + id: '1', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + slug: 'jenny-rostock', + name: 'Rainer Unsinn', + disabled: false, + deleted: false, + contributionsCount: 25, + shoutedCount: 5, + commentedCount: 39, + followedByCount: 2, + followedByCurrentUser: true, + location: null, + badges: [ + { + id: 'indiegogo_en_bear', + icon: '/img/badges/indiegogo_en_bear.svg', + __typename: 'Badge', + }, + ], + __typename: 'User', + }, + __typename: 'Comment', +} + +storiesOf('Comment', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('Basic comment', () => ({ + components: { Comment }, + store: helpers.store, + data: () => ({ + comment, + }), + template: ``, + })) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment/Comment.vue similarity index 58% rename from webapp/components/Comment.vue rename to webapp/components/Comment/Comment.vue index e54919cbd..b413e03f6 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment/Comment.vue @@ -3,17 +3,22 @@ - + {{ this.$t('comment.content.unavailable-placeholder') }}
- - - - + + + + + - -
-
-
- - - + {{ isCollapsed ? $t('comment.show.more') : $t('comment.show.less') }} +
@@ -66,33 +60,37 @@ diff --git a/webapp/components/CommentForm/CommentForm.spec.js b/webapp/components/CommentForm/CommentForm.spec.js index 94ce5aa66..420ab26fb 100644 --- a/webapp/components/CommentForm/CommentForm.spec.js +++ b/webapp/components/CommentForm/CommentForm.spec.js @@ -1,12 +1,11 @@ -import { mount, createLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import CommentForm from './CommentForm' -import Styleguide from '@human-connection/styleguide' +import Vue from 'vue' import MutationObserver from 'mutation-observer' global.MutationObserver = MutationObserver -const localVue = createLocalVue() -localVue.use(Styleguide) +const localVue = global.localVue describe('CommentForm.vue', () => { let mocks @@ -75,7 +74,8 @@ describe('CommentForm.vue', () => { it('calls `clear` method when the cancel button is clicked', async () => { wrapper.vm.updateEditorContent('ok') - await wrapper.find('.cancelBtn').trigger('submit') + await Vue.nextTick() + await wrapper.find('[data-test="cancel-button"]').trigger('submit') expect(cancelMethodSpy).toHaveBeenCalledTimes(1) }) @@ -163,13 +163,13 @@ describe('CommentForm.vue', () => { describe('cancel button is clicked', () => { it('calls `closeEditWindow` method', async () => { wrapper.vm.updateEditorContent('ok') - await wrapper.find('.cancelBtn').trigger('submit') + await wrapper.find('[data-test="cancel-button"]').trigger('submit') expect(closeMethodSpy).toHaveBeenCalledTimes(1) }) it('emits `showEditCommentMenu` event', async () => { wrapper.vm.updateEditorContent('ok') - await wrapper.find('.cancelBtn').trigger('submit') + await wrapper.find('[data-test="cancel-button"]').trigger('submit') expect(wrapper.emitted('showEditCommentMenu')).toEqual([[false]]) }) }) diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue index 6f1c7d931..063a3d599 100644 --- a/webapp/components/CommentForm/CommentForm.vue +++ b/webapp/components/CommentForm/CommentForm.vue @@ -1,28 +1,22 @@ - + + + + + + -
- -
diff --git a/webapp/pages/login.vue b/webapp/pages/login.vue index 46168f1ab..d7548706c 100644 --- a/webapp/pages/login.vue +++ b/webapp/pages/login.vue @@ -1,138 +1,34 @@ - - diff --git a/webapp/pages/moderation/index.vue b/webapp/pages/moderation/index.vue index 9988ebc7a..abf957cac 100644 --- a/webapp/pages/moderation/index.vue +++ b/webapp/pages/moderation/index.vue @@ -1,142 +1,13 @@ diff --git a/webapp/pages/notifications/index.spec.js b/webapp/pages/notifications/index.spec.js new file mode 100644 index 000000000..70592b467 --- /dev/null +++ b/webapp/pages/notifications/index.spec.js @@ -0,0 +1,146 @@ +import { config, shallowMount, mount } from '@vue/test-utils' +import NotificationsPage from './index.vue' + +import DropdownFilter from '~/components/DropdownFilter/DropdownFilter' +import NotificationsTable from '~/components/NotificationsTable/NotificationsTable' +import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons' + +const localVue = global.localVue + +config.stubs['client-only'] = '' + +describe('PostIndex', () => { + let wrapper, Wrapper, mocks, propsData + + beforeEach(() => { + propsData = {} + mocks = { + $t: string => string, + $toast: { + error: jest.fn(string => string), + }, + $i18n: { + locale: () => 'en', + }, + $apollo: { + mutate: jest.fn().mockResolvedValueOnce({ + data: { markAsRead: { id: 'notificationSourceId', read: true } }, + }), + queries: { + notifications: { + refresh: jest.fn().mockResolvedValueOnce(), + }, + }, + }, + } + }) + + describe('shallowMount', () => { + beforeEach(() => { + Wrapper = () => { + return shallowMount(NotificationsPage, { + mocks, + localVue, + propsData, + }) + } + wrapper = Wrapper() + }) + + it('renders a Notications header', () => { + expect(wrapper.find('ds-heading-stub').exists()).toBe(true) + }) + + it('renders a `dropdown-filter` component', () => { + expect(wrapper.find('dropdown-filter-stub').exists()).toBe(true) + }) + + it('renders a `notifications-table` component', () => { + expect(wrapper.find('notifications-table-stub').exists()).toBe(true) + }) + }) + + describe('mount', () => { + beforeEach(() => { + Wrapper = () => { + return mount(NotificationsPage, { + mocks, + localVue, + propsData, + }) + } + }) + + describe('filter', () => { + beforeEach(() => { + propsData.filterOptions = [ + { label: 'All', value: null }, + { label: 'Read', value: true }, + { label: 'Unread', value: false }, + ] + wrapper = Wrapper() + wrapper.find(DropdownFilter).vm.$emit('filter', propsData.filterOptions[1]) + }) + + it('sets `notificationRead` to value of received option', () => { + expect(wrapper.vm.notificationRead).toEqual(propsData.filterOptions[1].value) + }) + + it('set label to the label of the received option', () => { + expect(wrapper.vm.selected).toEqual(propsData.filterOptions[1].label) + }) + + it('refreshes the notificaitons', () => { + expect(mocks.$apollo.queries.notifications.refresh).toHaveBeenCalledTimes(1) + }) + }) + + describe('markNotificationAsRead', () => { + beforeEach(() => { + wrapper = Wrapper() + wrapper.find(NotificationsTable).vm.$emit('markNotificationAsRead', 'notificationSourceId') + }) + + it('calls markNotificationAsRead mutation', () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith( + expect.objectContaining({ variables: { id: 'notificationSourceId' } }), + ) + }) + + describe('error handling', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockRejectedValueOnce({ message: 'Some error message' }) + wrapper = Wrapper() + wrapper + .find(NotificationsTable) + .vm.$emit('markNotificationAsRead', 'notificationSourceId') + }) + + it('shows an error message if there is an error', () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('Some error message') + }) + }) + }) + + describe('PaginationButtons', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('next: given a user is on the first page', () => { + it('adds offset to pageSize to skip first x notifications and display next page', () => { + wrapper.find(PaginationButtons).vm.$emit('next') + expect(wrapper.vm.offset).toEqual(12) + }) + }) + + describe('back: given a user is on the third page', () => { + it('sets offset when back is emitted', () => { + wrapper.setData({ offset: 24 }) + wrapper.find(PaginationButtons).vm.$emit('back') + expect(wrapper.vm.offset).toEqual(12) + }) + }) + }) + }) +}) diff --git a/webapp/pages/notifications/index.vue b/webapp/pages/notifications/index.vue new file mode 100644 index 000000000..c49d208be --- /dev/null +++ b/webapp/pages/notifications/index.vue @@ -0,0 +1,115 @@ + + + + diff --git a/webapp/pages/password-reset.vue b/webapp/pages/password-reset.vue index e8f6ab4d6..a4d0f1e56 100644 --- a/webapp/pages/password-reset.vue +++ b/webapp/pages/password-reset.vue @@ -1,17 +1,30 @@ + diff --git a/webapp/pages/password-reset/change-password.vue b/webapp/pages/password-reset/change-password.vue index 7ab124782..3efdd001b 100644 --- a/webapp/pages/password-reset/change-password.vue +++ b/webapp/pages/password-reset/change-password.vue @@ -3,7 +3,11 @@ :email="email" :nonce="nonce" @passwordResetResponse="handlePasswordResetResponse" - /> + > + + {{ $t('site.back-to-login') }} + + diff --git a/webapp/pages/password-reset/request.vue b/webapp/pages/password-reset/request.vue index aa1b1ef05..29f8fde9d 100644 --- a/webapp/pages/password-reset/request.vue +++ b/webapp/pages/password-reset/request.vue @@ -1,5 +1,9 @@ diff --git a/webapp/pages/post/_id.vue b/webapp/pages/post/_id.vue index 3f8d93868..a02afd3b9 100644 --- a/webapp/pages/post/_id.vue +++ b/webapp/pages/post/_id.vue @@ -77,17 +77,6 @@ export default { ] }, }, - watch: { - $route(to, from) { - if (to.hash === '#comments') { - window.scroll({ - top: document.getElementById('comments').offsetTop, - left: 0, - behavior: 'smooth', - }) - } - }, - }, } diff --git a/webapp/pages/post/_id/_slug/index.spec.js b/webapp/pages/post/_id/_slug/index.spec.js index e0fa6556a..db960bb67 100644 --- a/webapp/pages/post/_id/_slug/index.spec.js +++ b/webapp/pages/post/_id/_slug/index.spec.js @@ -1,14 +1,8 @@ -import { config, shallowMount, createLocalVue } from '@vue/test-utils' +import { config, shallowMount } from '@vue/test-utils' import PostSlug from './index.vue' import Vuex from 'vuex' -import Styleguide from '@human-connection/styleguide' -import Filters from '~/plugins/vue-filters' -const localVue = createLocalVue() - -localVue.use(Vuex) -localVue.use(Styleguide) -localVue.use(Filters) +const localVue = global.localVue config.stubs['client-only'] = '' @@ -31,6 +25,9 @@ describe('PostSlug', () => { $filters: { truncate: a => a, }, + $route: { + hash: '', + }, // If you are mocking the router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html $router: { history: { diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index dd84ee3d7..7c524973c 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -1,13 +1,29 @@ diff --git a/webapp/pages/profile/_id/_slug.spec.js b/webapp/pages/profile/_id/_slug.spec.js index 6470bb69a..8d95834a8 100644 --- a/webapp/pages/profile/_id/_slug.spec.js +++ b/webapp/pages/profile/_id/_slug.spec.js @@ -1,21 +1,14 @@ -import { config, mount, createLocalVue } from '@vue/test-utils' +import { config, mount } from '@vue/test-utils' import ProfileSlug from './_slug.vue' -import Vuex from 'vuex' -import Styleguide from '@human-connection/styleguide' -import Filters from '~/plugins/vue-filters' -import InfiniteScroll from '~/plugins/vue-infinite-scroll' -const localVue = createLocalVue() +const localVue = global.localVue -localVue.use(Vuex) -localVue.use(Styleguide) -localVue.use(Filters) -localVue.use(InfiniteScroll) localVue.filter('date', d => d) config.stubs['client-only'] = '' config.stubs['v-popover'] = '' config.stubs['nuxt-link'] = '' +config.stubs['infinite-loading'] = '' describe('ProfileSlug', () => { let wrapper @@ -95,86 +88,6 @@ describe('ProfileSlug', () => { it('displays name of the user', () => { expect(wrapper.text()).toContain('Bob the builder') }) - - describe('load more button', () => { - const aPost = { - title: 'I am a post', - content: 'This is my content', - contentExcerpt: 'This is my content', - } - - describe('currently no posts available (e.g. after tab switching)', () => { - beforeEach(() => { - wrapper.setData({ posts: [], hasMore: false }) - }) - - it('displays no "load more" button', () => { - expect(wrapper.find('.load-more').exists()).toBe(false) - }) - - describe('apollo client in `loading` state', () => { - beforeEach(() => { - wrapper.vm.$apollo.loading = true - }) - - it('never displays more than one loading spinner', () => { - expect(wrapper.findAll('.ds-spinner')).toHaveLength(1) - }) - - it('displays a loading spinner below the posts list', () => { - expect(wrapper.find('.user-profile-posts-list .ds-spinner').exists()).toBe(true) - }) - }) - }) - - describe('pagination returned less posts than available', () => { - beforeEach(() => { - const posts = [1, 2, 3, 4, 5].map(id => { - return { - ...aPost, - id, - } - }) - - wrapper.setData({ posts, hasMore: true }) - }) - - it('displays a "load more" button', () => { - expect(wrapper.find('.load-more').exists()).toBe(true) - }) - - describe('apollo client in `loading` state', () => { - beforeEach(() => { - wrapper.vm.$apollo.loading = true - }) - - it('never displays more than one loading spinner', () => { - expect(wrapper.findAll('.ds-spinner')).toHaveLength(1) - }) - - it('displays a loading spinner below the posts list', () => { - expect(wrapper.find('.load-more .ds-spinner').exists()).toBe(true) - }) - }) - }) - - describe('pagination returned as many posts as available', () => { - beforeEach(() => { - const posts = [1, 2, 3, 4, 5, 6].map(id => { - return { - ...aPost, - id, - } - }) - - wrapper.setData({ posts, hasMore: false }) - }) - - it('displays no "load more" button', () => { - expect(wrapper.find('.load-more').exists()).toBe(false) - }) - }) - }) }) }) }) diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index d7f4e2f39..ce220b66b 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -11,9 +11,9 @@ style="position: relative; height: auto;" > - + - + - {{ userName }} + + {{ userName }} + + + {{ userSlug }} + - + {{ user.location.name }} @@ -43,7 +48,11 @@ - + @@ -58,15 +67,15 @@ diff --git a/webapp/pages/settings/index.spec.js b/webapp/pages/settings/index.spec.js index f0eff0641..ff2781073 100644 --- a/webapp/pages/settings/index.spec.js +++ b/webapp/pages/settings/index.spec.js @@ -1,12 +1,8 @@ -import { mount, createLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import index from './index.vue' import Vuex from 'vuex' -import Styleguide from '@human-connection/styleguide' -const localVue = createLocalVue() - -localVue.use(Vuex) -localVue.use(Styleguide) +const localVue = global.localVue describe('index.vue', () => { let store @@ -24,6 +20,7 @@ describe('index.vue', () => { data: { UpdateUser: { id: 'u1', + slug: 'peter', name: 'Peter', locationName: 'Berlin', about: 'Smth', @@ -37,34 +34,67 @@ describe('index.vue', () => { }, } getters = { - 'auth/user': () => { - return {} - }, + 'auth/user': () => ({}), } }) describe('mount', () => { + let options const Wrapper = () => { store = new Vuex.Store({ getters, }) - return mount(index, { store, mocks, localVue }) + return mount(index, { store, mocks, localVue, ...options }) } + beforeEach(() => { + options = {} + }) + it('renders', () => { expect(Wrapper().contains('div')).toBe(true) }) - describe('given a new username and hitting submit', () => { - it('calls updateUser mutation', () => { + describe('given form validation errors', () => { + beforeEach(() => { + options = { + ...options, + computed: { + formSchema: () => ({ + slug: [ + (_rule, _value, callback) => { + callback(new Error('Ouch!')) + }, + ], + }), + }, + } + }) + + it('cannot call updateUser mutation', () => { const wrapper = Wrapper() - const input = wrapper.find('#name') - const submitForm = wrapper.find('.ds-form') - input.setValue('Peter') - submitForm.trigger('submit') + wrapper.find('#name').setValue('Peter') + wrapper.find('.ds-form').trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalled() + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + }) + + describe('no form validation errors', () => { + beforeEach(() => { + options = { ...options, computed: { formSchema: () => ({}) } } + }) + + describe('given a new username and hitting submit', () => { + it('calls updateUser mutation', () => { + const wrapper = Wrapper() + + wrapper.find('#name').setValue('Peter') + wrapper.find('.ds-form').trigger('submit') + + expect(mocks.$apollo.mutate).toHaveBeenCalled() + }) }) }) }) diff --git a/webapp/pages/settings/index.vue b/webapp/pages/settings/index.vue index 5c99f4b8b..20034d5af 100644 --- a/webapp/pages/settings/index.vue +++ b/webapp/pages/settings/index.vue @@ -1,75 +1,54 @@ diff --git a/webapp/pages/settings/my-email-address/enter-nonce.spec.js b/webapp/pages/settings/my-email-address/enter-nonce.spec.js new file mode 100644 index 000000000..3e0ed5a4c --- /dev/null +++ b/webapp/pages/settings/my-email-address/enter-nonce.spec.js @@ -0,0 +1,50 @@ +import { mount } from '@vue/test-utils' +import EnterNoncePage from './enter-nonce.vue' + +const localVue = global.localVue + +describe('EnterNoncePage', () => { + let mocks + let wrapper + + beforeEach(() => { + wrapper = null + mocks = { + $t: jest.fn(t => t), + $route: { + query: {}, + }, + $router: { + replace: jest.fn(), + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(EnterNoncePage, { + mocks, + localVue, + }) + } + + describe('form', () => { + describe('submit', () => { + it('renders form errors', () => { + wrapper = Wrapper() + wrapper.find('form').trigger('submit') + expect(mocks.$router.replace).not.toHaveBeenCalled() + }) + + describe('entering a nonce', () => { + it('redirects to my-email-address/verify', () => { + wrapper = Wrapper() + wrapper.find('#nonce').setValue('foobar') + wrapper.find('form').trigger('submit') + expect(mocks.$router.replace).toHaveBeenCalled() + }) + }) + }) + }) + }) +}) diff --git a/webapp/pages/settings/my-email-address/enter-nonce.vue b/webapp/pages/settings/my-email-address/enter-nonce.vue new file mode 100644 index 000000000..91b2e269f --- /dev/null +++ b/webapp/pages/settings/my-email-address/enter-nonce.vue @@ -0,0 +1,59 @@ + + + diff --git a/webapp/pages/settings/my-email-address/index.spec.js b/webapp/pages/settings/my-email-address/index.spec.js new file mode 100644 index 000000000..dd64c1733 --- /dev/null +++ b/webapp/pages/settings/my-email-address/index.spec.js @@ -0,0 +1,117 @@ +import { config, mount } from '@vue/test-utils' +import EmailSettingsIndexPage from './index.vue' +import Vuex from 'vuex' + +const localVue = global.localVue + +config.stubs['sweetalert-icon'] = '' + +describe('EmailSettingsIndexPage', () => { + let store + let mocks + let wrapper + + beforeEach(() => { + wrapper = null + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { id: 'u23', email: 'some-mail@example.org' } + }, + }, + }) + mocks = { + $t: jest.fn(t => t), + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + $apollo: { + mutate: jest.fn().mockResolvedValue({ + data: { AddEmailAddress: { email: 'yet-another-email@example.org' } }, + }), + }, + $router: { + push: jest.fn(), + }, + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(EmailSettingsIndexPage, { + store, + mocks, + localVue, + }) + } + + describe('form', () => { + describe('submit', () => { + beforeEach(jest.useFakeTimers) + + describe('email unchanged', () => { + beforeEach(() => { + wrapper = Wrapper() + wrapper.find('form').trigger('submit') + }) + + it('displays form errors', () => { + expect(wrapper.text()).not.toContain('settings.email.submitted') + expect(wrapper.text()).toContain('settings.email.validation.same-email') + }) + + it('does not call $apollo.mutate', () => { + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + }) + + describe('enter another email', () => { + beforeEach(() => { + wrapper = Wrapper() + wrapper.find('#email').setValue('yet-ANOTHER-email@example.org') + wrapper.find('form').trigger('submit') + }) + + it('delivers email to backend', () => { + const expected = expect.objectContaining({ + variables: { email: 'yet-ANOTHER-email@example.org' }, + }) + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) + }) + + it('no form errors', () => { + expect(wrapper.text()).not.toContain('settings.email.validation.same-email') + expect(wrapper.text()).toContain('settings.email.submitted') + }) + + describe('after timeout', () => { + beforeEach(jest.runAllTimers) + + it('redirects with response from backend', () => { + expect(mocks.$router.push).toHaveBeenCalledWith({ + path: 'my-email-address/enter-nonce', + query: { email: 'yet-another-email@example.org' }, + }) + }) + }) + }) + + describe('if backend responds with unique constraint violation', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockRejectedValue({ + message: 'User account already exists', + }) + wrapper = Wrapper() + wrapper.find('#email').setValue('already-taken@example.org') + wrapper.find('form').trigger('submit') + }) + + it('translates error message', () => { + expect(wrapper.text()).toContain('registration.signup.form.errors.email-exists') + }) + }) + }) + }) + }) +}) diff --git a/webapp/pages/settings/my-email-address/index.vue b/webapp/pages/settings/my-email-address/index.vue new file mode 100644 index 000000000..4e01bbb44 --- /dev/null +++ b/webapp/pages/settings/my-email-address/index.vue @@ -0,0 +1,116 @@ + + + diff --git a/webapp/pages/settings/my-email-address/verify.spec.js b/webapp/pages/settings/my-email-address/verify.spec.js new file mode 100644 index 000000000..e9c2e5a34 --- /dev/null +++ b/webapp/pages/settings/my-email-address/verify.spec.js @@ -0,0 +1,160 @@ +import { config, mount } from '@vue/test-utils' +import EmailVerifyPage from './verify.vue' +import Vuex from 'vuex' + +const localVue = global.localVue + +config.stubs['client-only'] = '' +config.stubs['sweetalert-icon'] = '' + +describe('EmailVerifyPage', () => { + let store + let mocks + let wrapper + let setUser + + beforeEach(() => { + setUser = jest.fn() + wrapper = null + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return { id: 'u23', email: 'some-mail@example.org' } + }, + }, + mutations: { + 'auth/SET_USER': setUser, + }, + }) + mocks = { + $t: jest.fn(t => t), + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + $router: { + replace: jest.fn(), + }, + store, + } + }) + + describe('asyncData', () => { + const asyncDataAction = () => { + const context = { + store: mocks.store, + query: {}, + app: { + apolloProvider: { + defaultClient: mocks.$apollo, + }, + }, + } + return EmailVerifyPage.asyncData(context) + } + + describe('backend sends successful response', () => { + beforeEach(() => { + mocks = { + ...mocks, + $apollo: { + mutate: jest.fn().mockResolvedValue({ + data: { + VerifyEmailAddress: { + email: 'verified-email@example.org', + }, + }, + }), + }, + } + }) + + it('sets `success` to true', async () => { + await expect(asyncDataAction()).resolves.toEqual({ + success: true, + }) + }) + + it("updates current user's email", async () => { + await asyncDataAction() + expect(setUser).toHaveBeenCalledWith({}, { id: 'u23', email: 'verified-email@example.org' }) + }) + }) + + describe('backend sends unsuccessful response', () => { + beforeEach(() => { + mocks = { + ...mocks, + $apollo: { + mutate: jest.fn().mockRejectedValue({ + data: { VerifyEmailAddress: null }, + errors: [{ message: 'User account already exists with that email' }], + }), + }, + } + }) + + it('sets `success` to false', async () => { + await expect(asyncDataAction()).resolves.toEqual({ + success: false, + }) + }) + + it('does not updates current user', async () => { + await asyncDataAction() + expect(setUser).not.toHaveBeenCalled() + }) + }) + }) + + describe('mount', () => { + beforeEach(jest.useFakeTimers) + const Wrapper = () => { + return mount(EmailVerifyPage, { + store, + mocks, + localVue, + }) + } + + describe('given successful verification', () => { + beforeEach(() => { + mocks = { ...mocks, success: true } + wrapper = Wrapper() + }) + + it('shows success message', () => { + expect(wrapper.text()).toContain('settings.email.change-successful') + }) + + describe('after timeout', () => { + beforeEach(jest.runAllTimers) + + it('redirects to email settings page', () => { + expect(mocks.$router.replace).toHaveBeenCalledWith({ + name: 'settings-my-email-address', + }) + }) + }) + }) + + describe('given unsuccessful verification', () => { + beforeEach(() => { + mocks = { ...mocks, success: false } + wrapper = Wrapper() + }) + + it('shows success message', () => { + expect(wrapper.text()).toContain('settings.email.verification-error') + }) + + describe('after timeout', () => { + beforeEach(jest.runAllTimers) + + it('does not redirect', () => { + expect(mocks.$router.replace).not.toHaveBeenCalledWith() + }) + }) + }) + }) +}) diff --git a/webapp/pages/settings/my-email-address/verify.vue b/webapp/pages/settings/my-email-address/verify.vue new file mode 100644 index 000000000..21e4fd775 --- /dev/null +++ b/webapp/pages/settings/my-email-address/verify.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/webapp/pages/settings/my-organizations.vue b/webapp/pages/settings/my-organizations.vue index e2a5f3373..aefd602b7 100644 --- a/webapp/pages/settings/my-organizations.vue +++ b/webapp/pages/settings/my-organizations.vue @@ -5,7 +5,7 @@ diff --git a/webapp/pages/terms-and-conditions-confirm.vue b/webapp/pages/terms-and-conditions-confirm.vue index 09e40ac7c..0716048b5 100644 --- a/webapp/pages/terms-and-conditions-confirm.vue +++ b/webapp/pages/terms-and-conditions-confirm.vue @@ -2,11 +2,11 @@

- - + + {{ $t(`termsAndConditions.termsAndConditionsNewConfirmText`) }} - - + +

@@ -17,24 +17,19 @@