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/.travis.yml b/.travis.yml index 70451cd79..f8170d1e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ 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 @@ -37,7 +37,6 @@ script: # 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f41d1062..cd785773b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,133 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -#### [0.1.8](https://github.com/Human-Connection/Human-Connection/compare/0.1.7...0.1.8) +#### [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) +- 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) +- add design token addon to storybook [`fc387f6`](https://github.com/Human-Connection/Human-Connection/commit/fc387f63e2cd4aef0964c81a13b892bdba952e12) + +#### [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) +- 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) +- 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) +- add migration plan to webapp readme [`8816f7b`](https://github.com/Human-Connection/Human-Connection/commit/8816f7be2a9662bc1333e37b306dee6b964fc2e0) + +#### [v0.1.8](https://github.com/Human-Connection/Human-Connection/compare/0.1.7...v0.1.8) > 25 October 2019 @@ -23,8 +149,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - 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) - Update to version 0.1.8 [`d45264b`](https://github.com/Human-Connection/Human-Connection/commit/d45264b3afa1557c2205e7ca1b77c778ee37ab5a) +- build(deps): bump @nuxtjs/apollo in /webapp [`26c21b5`](https://github.com/Human-Connection/Human-Connection/commit/26c21b5b76c96206d98ff6bbfdbd1ca973ffcd4f) - build(deps-dev): bump @storybook/addon-actions in /webapp [`7e95d37`](https://github.com/Human-Connection/Human-Connection/commit/7e95d376a311a5ede6351d577d30e25aea9cb65d) -- new terms and conditions fixed [`ff8680f`](https://github.com/Human-Connection/Human-Connection/commit/ff8680ff862846cf619423007809ea3139cada96) #### [0.1.7](https://github.com/Human-Connection/Human-Connection/compare/0.1.6...0.1.7) @@ -131,8 +257,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - 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) -- Refactored backend database to a single `REPORTED` relation [`82228c6`](https://github.com/Human-Connection/Human-Connection/commit/82228c6c99c4b33ab20ddfbc13cce6ac6f95792c) #### [0.1.4](https://github.com/Human-Connection/Human-Connection/compare/0.1.3...0.1.4) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1475df0de..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 existing 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, Wolfgang, Matt, Alina, Alex) - - once there is a review, and presuming no requested changes, PR opener can merge - -* CI/tests - - the CI needs to pass - - linting (yarn lint --fix) - - 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/SUMMARY.md b/SUMMARY.md index 8d8d81534..11c4b0293 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -7,15 +7,10 @@ * [Backend](backend/README.md) * [GraphQL](backend/graphql.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) diff --git a/VERSION b/VERSION index 699c6c6d4..9767cc98e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.8 +0.1.10 diff --git a/backend/package.json b/backend/package.json index 352bf3dce..699dc9972 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,16 +9,9 @@ "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", "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": "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" }, @@ -42,7 +35,7 @@ }, "dependencies": { "@hapi/joi": "^16.1.7", - "@sentry/node": "^5.7.1", + "@sentry/node": "^5.8.0", "apollo-cache-inmemory": "~1.6.3", "apollo-client": "~2.6.4", "apollo-link-context": "~1.0.19", @@ -54,7 +47,7 @@ "cheerio": "~1.0.0-rc.3", "cors": "~2.8.5", "cross-env": "~6.0.3", - "date-fns": "2.6.0", + "date-fns": "2.7.0", "debug": "~4.1.1", "dotenv": "~8.2.0", "express": "^4.17.1", @@ -62,29 +55,29 @@ "graphql": "^14.5.8", "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", - "graphql-middleware": "~4.0.1", + "graphql-middleware": "~4.0.2", "graphql-middleware-sentry": "^3.2.1", - "graphql-shield": "~7.0.0", + "graphql-shield": "~7.0.2", "graphql-tag": "~2.10.1", "helmet": "~3.21.2", "jsonwebtoken": "~8.5.1", "linkifyjs": "~2.1.8", "lodash": "~4.17.14", - "merge-graphql-schemas": "^1.7.0", + "merge-graphql-schemas": "^1.7.3", "metascraper": "^4.10.3", - "metascraper-audio": "^5.7.14", + "metascraper-audio": "^5.7.17", "metascraper-author": "^5.7.17", "metascraper-clearbit-logo": "^5.3.0", "metascraper-date": "^5.7.17", - "metascraper-description": "^5.7.14", - "metascraper-image": "^5.7.6", + "metascraper-description": "^5.7.17", + "metascraper-image": "^5.7.17", "metascraper-lang": "^5.7.17", "metascraper-lang-detector": "^4.8.5", "metascraper-logo": "^5.7.17", - "metascraper-publisher": "^5.7.14", + "metascraper-publisher": "^5.7.17", "metascraper-soundcloud": "^5.7.17", - "metascraper-title": "^5.7.14", - "metascraper-url": "^5.7.14", + "metascraper-title": "^5.7.17", + "metascraper-url": "^5.7.17", "metascraper-video": "^5.7.17", "metascraper-youtube": "^5.7.17", "minimatch": "^3.0.4", @@ -101,16 +94,17 @@ "slug": "~1.1.0", "trunc-html": "~1.1.2", "uuid": "~3.3.3", + "validator": "^12.0.0", "wait-on": "~3.3.0", "xregexp": "^4.2.4" }, "devDependencies": { - "@babel/cli": "~7.6.4", - "@babel/core": "~7.6.4", - "@babel/node": "~7.6.3", + "@babel/cli": "~7.7.0", + "@babel/core": "~7.7.2", + "@babel/node": "~7.7.0", "@babel/plugin-proposal-throw-expressions": "^7.2.0", - "@babel/preset-env": "~7.6.3", - "@babel/register": "~7.6.2", + "@babel/preset-env": "~7.7.1", + "@babel/register": "~7.7.0", "apollo-server-testing": "~2.9.7", "babel-core": "~7.0.0-0", "babel-eslint": "~10.0.3", @@ -121,12 +115,11 @@ "eslint-config-prettier": "~6.5.0", "eslint-config-standard": "~14.1.0", "eslint-plugin-import": "~2.18.2", - "eslint-plugin-jest": "~23.0.2", + "eslint-plugin-jest": "~23.0.3", "eslint-plugin-node": "~10.0.0", "eslint-plugin-prettier": "~3.1.1", "eslint-plugin-promise": "~4.2.1", "eslint-plugin-standard": "~4.0.1", - "graphql-request": "~1.8.2", "jest": "~24.9.0", "nodemon": "~1.19.4", "prettier": "~1.18.2", diff --git a/backend/src/jest/helpers.js b/backend/src/jest/helpers.js index 380aedd16..201d68c14 100644 --- a/backend/src/jest/helpers.js +++ b/backend/src/jest/helpers.js @@ -1,21 +1,3 @@ -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) { diff --git a/backend/src/middleware/nodes/locations.js b/backend/src/middleware/nodes/locations.js index a90d8c0d7..3e0ca6855 100644 --- a/backend/src/middleware/nodes/locations.js +++ b/backend/src/middleware/nodes/locations.js @@ -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,6 +32,7 @@ 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, @@ -48,6 +49,7 @@ 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) { @@ -56,6 +58,7 @@ const createLocation = async (session, mapboxData) => { query += ' RETURN l.id' await session.run(query, data) + session.close() } const createOrUpdateLocations = async (userId, locationName, driver) => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index a494783cf..718f0b1e4 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -1,5 +1,21 @@ import extractMentionedUsers from './mentions/extractMentionedUsers' +const postAuthorOfComment = async (comment, { context }) => { + 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, + }) + session.close() + const [postAuthor] = await result.records.map(record => { + return record.get('user') + }) + return postAuthor +} + const notifyUsers = async (label, id, idsOfUsers, reason, context) => { if (!idsOfUsers.length) return @@ -90,11 +106,13 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo } const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => { - const idsOfUsers = extractMentionedUsers(args.content) - + let idsOfUsers = extractMentionedUsers(args.content) const comment = await resolve(root, args, context, resolveInfo) if (comment) { + const postAuthor = await postAuthorOfComment(comment, { context }) + idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id) + await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js index 88f91d688..18ee998db 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js @@ -105,6 +105,7 @@ describe('notifications', () => { let title let postContent let postAuthor + const createPostAction = async () => { authenticatedUser = await postAuthor.toJson() await mutate({ @@ -239,6 +240,7 @@ describe('notifications', () => { describe('mentions me in a post', () => { beforeEach(async () => { title = 'Mentioning Al Capone' + postContent = 'Hey @al-capone how do you do?' }) @@ -439,7 +441,15 @@ describe('notifications', () => { }) }) - it('sends a notification', async () => { + it('sends only one notification with reason mentioned_in_comment', async () => { + postAuthor = await instance.create('User', { + id: 'MrPostAuthor', + name: 'Mr Author', + slug: 'mr-author', + email: 'post-author@example.org', + password: '1234', + }) + await createCommentOnPostAction() const expected = expect.objectContaining({ data: { @@ -467,6 +477,40 @@ describe('notifications', () => { }), ).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, + }, + }, + ], + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) }) describe('but the author of the post blocked me', () => { diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index a0116a439..d312bc112 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -135,6 +135,7 @@ const permissions = shield( blockedUsers: isAuthenticated, notifications: isAuthenticated, profilePagePosts: or(onlyEnabledContent, isModerator), + Donations: isAuthenticated, }, Mutation: { '*': deny, @@ -177,6 +178,7 @@ const permissions = shield( VerifyEmailAddress: isAuthenticated, pinPost: isAdmin, unpinPost: isAdmin, + UpdateDonations: isAdmin, }, User: { email: or(isMyOwn, isAdmin), diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.js b/backend/src/middleware/softDelete/softDeleteMiddleware.js index d3fd13cfc..3360d4085 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.js +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.js @@ -32,6 +32,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/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js index 1dbd36b72..bd4805ed8 100644 --- a/backend/src/middleware/validation/validationMiddleware.js +++ b/backend/src/middleware/validation/validationMiddleware.js @@ -72,6 +72,7 @@ const validateReport = async (resolve, root, args, context, info) => { submitterId: user.id, }, ) + session.close() const [existingReportedResource] = reportQueryRes.records.map(record => { return { label: record.get('label'), diff --git a/backend/src/models/Donations.js b/backend/src/models/Donations.js new file mode 100644 index 000000000..45e06e1d4 --- /dev/null +++ b/backend/src/models/Donations.js @@ -0,0 +1,14 @@ +import uuid from 'uuid/v4' + +module.exports = { + 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/Location.js b/backend/src/models/Location.js index bd6e0b5d9..2c62877f7 100644 --- a/backend/src/models/Location.js +++ b/backend/src/models/Location.js @@ -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/index.js b/backend/src/models/index.js index 08362b69f..bd89ddc51 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -12,4 +12,5 @@ export default { Category: require('./Category.js'), Tag: require('./Tag.js'), Location: require('./Location.js'), + Donations: require('./Donations.js'), } diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js index 95fa9ef61..b1bd36451 100644 --- a/backend/src/schema/index.js +++ b/backend/src/schema/index.js @@ -24,6 +24,7 @@ export default applyScalars( 'SocialMedia', 'NOTIFIED', 'REPORTED', + 'Donations', ], // add 'User' here as soon as possible }, @@ -44,6 +45,7 @@ export default applyScalars( 'EMOTED', 'NOTIFIED', 'REPORTED', + 'Donations', ], // add 'User' here as soon as possible }, diff --git a/backend/src/schema/resolvers/comments.js b/backend/src/schema/resolvers/comments.js index 7378238bb..e0b69b153 100644 --- a/backend/src/schema/resolvers/comments.js +++ b/backend/src/schema/resolvers/comments.js @@ -59,6 +59,7 @@ export default { `, { commentId: args.id }, ) + session.close() const [comment] = transactionRes.records.map(record => record.get('comment').properties) return comment }, diff --git a/backend/src/schema/resolvers/donations.js b/backend/src/schema/resolvers/donations.js new file mode 100644 index 000000000..88149077d --- /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 + const session = driver.session() + let donations + 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..327688d3a --- /dev/null +++ b/backend/src/schema/resolvers/donations.spec.js @@ -0,0 +1,174 @@ +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 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 index ce93a28e9..06c0dbd1a 100644 --- a/backend/src/schema/resolvers/emails.js +++ b/backend/src/schema/resolvers/emails.js @@ -3,11 +3,14 @@ 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 'validator' 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) @@ -16,13 +19,13 @@ export default { } // check email does not belong to anybody - await existingEmailAddress(_parent, args, context) + await existingEmailAddress({ args, context }) const nonce = generateNonce() const { user: { id: userId }, } = context - const { email } = args + const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async txc => { const result = await txc.run( @@ -32,7 +35,7 @@ export default { SET email.createdAt = toString(datetime()) RETURN email, user `, - { userId, email, nonce }, + { userId, email: args.email, nonce }, ) return result.records.map(record => ({ name: record.get('user').properties.name, diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.js b/backend/src/schema/resolvers/helpers/createPasswordReset.js new file mode 100644 index 000000000..8d575abfc --- /dev/null +++ b/backend/src/schema/resolvers/helpers/createPasswordReset.js @@ -0,0 +1,31 @@ +import { normalizeEmail } from 'validator' + +export default async function createPasswordReset(options) { + const { driver, nonce, email, issuedAt = new Date() } = options + const normalizedEmail = normalizeEmail(email) + 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: normalizedEmail, + }) + 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 +} diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js b/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js new file mode 100644 index 000000000..a566e225a --- /dev/null +++ b/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js @@ -0,0 +1,35 @@ +import createPasswordReset from './createPasswordReset' + +describe('createPasswordReset', () => { + const issuedAt = new Date() + const nonce = 'abcdef' + + describe('email lookup', () => { + let driver + let mockSession + beforeEach(() => { + mockSession = { + close() {}, + run: jest.fn().mockReturnValue({ + records: { + map: jest.fn(() => []), + }, + }), + } + driver = { session: () => mockSession } + }) + + it('lowercases email address', async () => { + const email = 'stRaNGeCaSiNG@ExAmplE.ORG' + await createPasswordReset({ driver, email, issuedAt, nonce }) + expect(mockSession.run.mock.calls).toEqual([ + [ + expect.any(String), + expect.objectContaining({ + email: 'strangecasing@example.org', + }), + ], + ]) + }) + }) +}) diff --git a/backend/src/schema/resolvers/helpers/existingEmailAddress.js b/backend/src/schema/resolvers/helpers/existingEmailAddress.js index 007d2de6b..ee1a6af82 100644 --- a/backend/src/schema/resolvers/helpers/existingEmailAddress.js +++ b/backend/src/schema/resolvers/helpers/existingEmailAddress.js @@ -1,7 +1,6 @@ import { UserInputError } from 'apollo-server' -export default async function alreadyExistingMail(_parent, args, context) { - let { email } = args - email = email.toLowerCase() + +export default async function alreadyExistingMail({ args, context }) { const cypher = ` MATCH (email:EmailAddress {email: $email}) OPTIONAL MATCH (email)-[:BELONGS_TO]-(user) @@ -10,7 +9,7 @@ export default async function alreadyExistingMail(_parent, args, context) { let transactionRes const session = context.driver.session() try { - transactionRes = await session.run(cypher, { email }) + transactionRes = await session.run(cypher, { email: args.email }) } finally { session.close() } 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..aed2419e0 --- /dev/null +++ b/backend/src/schema/resolvers/locations.spec.js @@ -0,0 +1,85 @@ +import Factory from '../../seed/factories' +import { gql } from '../../jest/helpers' +import { neode as getNeode, getDriver } from '../../bootstrap/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/notifications.js b/backend/src/schema/resolvers/notifications.js index 4cab1ffc4..9e6f5c91a 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -18,9 +18,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 notifications, whereClause, orderByClause + switch (args.read) { case true: whereClause = 'WHERE notification.read = TRUE' @@ -41,13 +40,15 @@ 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}` : '' try { const cypher = ` MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) ${whereClause} RETURN resource, notification, user ${orderByClause} + ${offset} ${limit} ` const result = await session.run(cypher, { id: currentUser.id }) notifications = await result.records.map(transformReturnType) @@ -77,4 +78,10 @@ export default { 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/passwordReset.js b/backend/src/schema/resolvers/passwordReset.js index 3c5f4636c..7c0d9e747 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: { diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js index fabee1c7e..b8633e9c3 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 createPasswordReset from './helpers/createPasswordReset' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -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/posts.js b/backend/src/schema/resolvers/posts.js index 3b3065277..c6b36fe9d 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -182,6 +182,7 @@ export default { `, { postId: args.id }, ) + session.close() const [post] = transactionRes.records.map(record => record.get('post').properties) return post }, diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index bd62b32c3..d425357c3 100644 --- a/backend/src/schema/resolvers/registration.js +++ b/backend/src/schema/resolvers/registration.js @@ -4,6 +4,7 @@ import fileUpload from './fileUpload' import encryptPassword from '../../helpers/encryptPassword' import generateNonce from './helpers/generateNonce' import existingEmailAddress from './helpers/existingEmailAddress' +import { normalizeEmail } from 'validator' const instance = neode() @@ -29,9 +30,9 @@ export default { return response }, Signup: async (_parent, args, context) => { - const nonce = generateNonce() - args.nonce = nonce - let emailAddress = await existingEmailAddress(_parent, args, context) + args.nonce = generateNonce() + args.email = normalizeEmail(args.email) + let emailAddress = await existingEmailAddress({ args, context }) if (emailAddress) return emailAddress try { emailAddress = await instance.create('EmailAddress', args) @@ -42,9 +43,9 @@ export default { }, SignupByInvitation: async (_parent, args, context) => { const { token } = args - const nonce = generateNonce() - args.nonce = nonce - let emailAddress = await existingEmailAddress(_parent, args, context) + args.nonce = generateNonce() + args.email = normalizeEmail(args.email) + let emailAddress = await existingEmailAddress({ args, context }) if (emailAddress) return emailAddress try { const result = await instance.cypher( @@ -78,7 +79,7 @@ export default { args.termsAndConditionsAgreedAt = new Date().toISOString() let { nonce, email } = args - email = email.toLowerCase() + email = normalizeEmail(email) const result = await instance.cypher( ` MATCH(email:EmailAddress {nonce: {nonce}, email: {email}}) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 1a4acf0fc..c2929a612 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -178,6 +178,7 @@ export default { 'termsAndConditionsAgreedAt', 'allowEmbedIframes', 'showShoutsPublicly', + 'locale', ], boolean: { followedByCurrentUser: 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/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/NOTIFIED.gql b/backend/src/schema/types/type/NOTIFIED.gql index 5082b5f7f..5557cbd54 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,7 +24,7 @@ enum NotificationReason { } type Query { - notifications(read: Boolean, orderBy: NotificationOrdering): [NOTIFIED] + notifications(read: Boolean, orderBy: NotificationOrdering, first: Int, offset: Int): [NOTIFIED] } type Mutation { diff --git a/backend/src/seed/factories/donations.js b/backend/src/seed/factories/donations.js new file mode 100644 index 000000000..e22cdb6d7 --- /dev/null +++ b/backend/src/seed/factories/donations.js @@ -0,0 +1,18 @@ +import uuid from 'uuid/v4' + +export default function create() { + return { + factory: async ({ args, neodeInstance }) => { + const defaults = { + id: uuid(), + goal: 15000, + progress: 0, + } + args = { + ...defaults, + ...args, + } + return neodeInstance.create('Donations', args) + }, + } +} diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index 5fd6f2d82..5054155fc 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -1,4 +1,3 @@ -import { GraphQLClient, request } from 'graphql-request' import { getDriver, neode } from '../../bootstrap/neo4j' import createBadge from './badges.js' import createUser from './users.js' @@ -9,20 +8,9 @@ import createTag from './tags.js' import createSocialMedia from './socialMedia.js' import createLocation from './locations.js' import createEmailAddress from './emailAddresses.js' +import createDonations from './donations.js' import createUnverifiedEmailAddresss from './unverifiedEmailAddresses.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, @@ -34,6 +22,7 @@ const factories = { Location: createLocation, EmailAddress: createEmailAddress, UnverifiedEmailAddress: createUnverifiedEmailAddresss, + Donations: createDonations, } export const cleanDatabase = async (options = {}) => { @@ -48,127 +37,31 @@ export const cleanDatabase = async (options = {}) => { } export default function Factory(options = {}) { - const { - seedServerHost = 'http://127.0.0.1:4001', - neo4jDriver = getDriver(), - neodeInstance = neode(), - } = options - - const graphQLClient = new GraphQLClient(seedServerHost) + const { neo4jDriver = getDriver(), neodeInstance = neode() } = options 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 followUser(properties) { - const { id } = properties - const mutation = ` - mutation { - followUser( - id: "${id}" - ) - } - ` - 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, + const { factory } = this.factories[node](args) + this.lastResponse = await factory({ + args, + neodeInstance, + factoryInstance: this, }) - return this + return this.lastResponse }, + 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.followUser.bind(result) - result.invite.bind(result) result.cleanDatabase.bind(result) return result } diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 76fbb4875..c99f348cf 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -1,10 +1,13 @@ 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' +const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] + /* eslint-disable no-multi-spaces */ ;(async function() { let authenticatedUser = null @@ -39,6 +42,7 @@ import { gql } from '../jest/helpers' nameDE: 'Hamburg', nameNL: 'Hamburg', namePL: 'Hamburg', + nameRU: 'Гамбург', }), factory.create('Location', { id: 'region.14880313158564380', @@ -54,6 +58,7 @@ import { gql } from '../jest/helpers' nameDE: 'Berlin', nameNL: 'Berlijn', namePL: 'Berlin', + nameRU: 'Берлин', }), factory.create('Location', { id: 'country.10743216036480410', @@ -67,6 +72,7 @@ import { gql } from '../jest/helpers' nameFR: 'Allemagne', nameIT: 'Germania', nameEN: 'Germany', + nameRU: 'Германия', }), factory.create('Location', { id: 'region.9397217726497330', @@ -82,6 +88,7 @@ import { gql } from '../jest/helpers' nameDE: 'Paris', nameNL: 'Parijs', namePL: 'Paryż', + nameRU: 'Париж', }), factory.create('Location', { id: 'country.9759535382641660', @@ -95,6 +102,7 @@ import { gql } from '../jest/helpers' nameFR: 'France', nameIT: 'Francia', nameEN: 'France', + nameRU: 'Франция', }), ]) await Promise.all([ @@ -341,39 +349,46 @@ import { gql } from '../jest/helpers' factory.create('Post', { author: peterLustig, id: 'p0', + language: sample(languages), image: faker.image.unsplash.food(), categoryIds: ['cat16'], }), factory.create('Post', { author: bobDerBaumeister, id: 'p1', + language: sample(languages), image: faker.image.unsplash.technology(), categoryIds: ['cat1'], }), 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', + language: sample(languages), image: faker.image.unsplash.buildings(), categoryIds: ['cat6'], }), factory.create('Post', { author: huey, id: 'p9', + language: sample(languages), categoryIds: ['cat9'], }), factory.create('Post', { @@ -384,23 +399,27 @@ import { gql } from '../jest/helpers' factory.create('Post', { author: louie, id: 'p11', + language: sample(languages), image: faker.image.unsplash.people(), categoryIds: ['cat11'], }), factory.create('Post', { author: bobDerBaumeister, id: 'p13', + language: sample(languages), categoryIds: ['cat13'], }), factory.create('Post', { author: jennyRostock, id: 'p14', + language: sample(languages), image: faker.image.unsplash.objects(), categoryIds: ['cat14'], }), factory.create('Post', { author: huey, id: 'p15', + language: sample(languages), categoryIds: ['cat15'], }), ]) @@ -910,6 +929,7 @@ import { gql } from '../jest/helpers' }), ) + await factory.create('Donations') /* eslint-disable-next-line no-console */ console.log('Seeded Data...') process.exit(0) diff --git a/backend/yarn.lock b/backend/yarn.lock index 3a8b24a45..7dc7c6503 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -14,18 +14,17 @@ 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.4": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.4.tgz#9b35a4e15fa7d8f487418aaa8229c8b0bc815f20" - integrity sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ== +"@babel/cli@~7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.0.tgz#8d10c9acb2acb362d7614a9493e1791c69100d89" + integrity sha512-jECEqAq6Ngf3pOhLSg7od9WKyrIacyh1oNNYtRXNn+ummSHCTXBamGywOAtiae34Vk7zKuQNnLvo2BKTMCoV4A== dependencies: commander "^2.8.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: @@ -38,19 +37,19 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@~7.6.4": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff" - integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ== +"@babel/core@^7.1.0", "@babel/core@~7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.2.tgz#ea5b99693bcfc058116f42fa1dd54da412b29d91" + integrity sha512-eeD7VEZKfhK1KUXGiyPFettgF3m513f8FoBSWiQ1xTvl1RAopLs42Wp9+Ze911I6H0N9lNqJMDgoZT7gHsipeQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.4" - "@babel/helpers" "^7.6.2" - "@babel/parser" "^7.6.4" - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.3" - "@babel/types" "^7.6.3" - convert-source-map "^1.1.0" + "@babel/generator" "^7.7.2" + "@babel/helpers" "^7.7.0" + "@babel/parser" "^7.7.2" + "@babel/template" "^7.7.0" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.7.2" + convert-source-map "^1.7.0" debug "^4.1.0" json5 "^2.1.0" lodash "^4.17.13" @@ -58,12 +57,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671" - integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w== +"@babel/generator@^7.4.0", "@babel/generator@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.2.tgz#2f4852d04131a5e17ea4f6645488b5da66ebf3af" + integrity sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ== dependencies: - "@babel/types" "^7.6.3" + "@babel/types" "^7.7.2" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -75,6 +74,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-annotate-as-pure@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.0.tgz#efc54032d43891fe267679e63f6860aa7dbf4a5e" + integrity sha512-k50CQxMlYTYo+GGyUGFwpxKVtxVJi9yh61sXZji3zYHccK9RYliZGSTOgci85T+r+0VFN2nWbGM04PIqwfrpMg== + dependencies: + "@babel/types" "^7.7.0" + "@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" @@ -92,13 +98,21 @@ "@babel/traverse" "^7.4.4" "@babel/types" "^7.4.4" -"@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-create-regexp-features-plugin@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.0.tgz#2e8badfe201cfafb5d930f46cf1e0b6f1cdcab23" + integrity sha512-ZhagAAVGD3L6MPM9/zZi7RRteonfBFLVUz3kjsnYsMAtr9hOJCKI9BAKIMpqn3NyWicPieoX779UL+7/3BEAOA== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.5.5" + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + +"@babel/helper-define-map@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.0.tgz#60b0e9fd60def9de5054c38afde8c8ee409c7529" + integrity sha512-kPKWPb0dMpZi+ov1hJiwse9dWweZsz3V9rP4KdytnX1E7z3cTNmFGglwklzFPuqIcHLIY3bgKSs4vkwXXdflQA== + dependencies: + "@babel/helper-function-name" "^7.7.0" + "@babel/types" "^7.7.0" lodash "^4.17.13" "@babel/helper-explode-assignable-expression@^7.1.0": @@ -109,14 +123,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz#44a5ad151cfff8ed2599c91682dda2ec2c8430a3" + integrity sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q== 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.7.0" + "@babel/template" "^7.7.0" + "@babel/types" "^7.7.0" "@babel/helper-get-function-arity@^7.0.0": version "7.0.0" @@ -125,6 +139,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-get-function-arity@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz#c604886bc97287a1d1398092bc666bc3d7d7aa2d" + integrity sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw== + dependencies: + "@babel/types" "^7.7.0" + "@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" @@ -132,6 +153,13 @@ dependencies: "@babel/types" "^7.4.4" +"@babel/helper-hoist-variables@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.0.tgz#b4552e4cfe5577d7de7b183e193e84e4ec538c81" + integrity sha512-LUe/92NqsDAkJjjCEWkNe+/PcpnisvnqdlRe19FahVapa4jndeuJ+FBiTX1rcAKWKcJGE+C3Q3tuEuxkSmCEiQ== + dependencies: + "@babel/types" "^7.7.0" + "@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" @@ -139,14 +167,21 @@ dependencies: "@babel/types" "^7.5.5" -"@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-member-expression-to-functions@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.0.tgz#472b93003a57071f95a541ea6c2b098398bcad8a" + integrity sha512-QaCZLO2RtBcmvO/ekOLp8p7R5X2JriKRizeDpm5ChATAFWrrYDcDxPuCIBXKyBjY+i1vYSdcUTMIb8psfxHDPA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.7.0" -"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.0.tgz#99c095889466e5f7b6d66d98dffc58baaf42654d" + integrity sha512-Dv3hLKIC1jyfTkClvyEkYP2OlkzNvWs5+Q8WgPbxM5LMeorons7iPP91JM+DU7tRbhqA1ZeooPaMFvQrn23RHw== + dependencies: + "@babel/types" "^7.7.0" + +"@babel/helper-module-transforms@^7.1.0": 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== @@ -158,6 +193,18 @@ "@babel/types" "^7.5.5" lodash "^4.17.13" +"@babel/helper-module-transforms@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.0.tgz#154a69f0c5b8fd4d39e49750ff7ac4faa3f36786" + integrity sha512-rXEefBuheUYQyX4WjV19tuknrJFwyKw0HgzRwbkyTbB+Dshlq7eqkWbyjzToLrMZk/5wKVKdWFluiAsVkHXvuQ== + dependencies: + "@babel/helper-module-imports" "^7.7.0" + "@babel/helper-simple-access" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" + "@babel/template" "^7.7.0" + "@babel/types" "^7.7.0" + 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" @@ -165,6 +212,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-optimise-call-expression@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.0.tgz#4f66a216116a66164135dc618c5d8b7a959f9365" + integrity sha512-48TeqmbazjNU/65niiiJIJRc5JozB8acui1OS7bSd6PgxfuovWsvjfWSzlgx+gPFdVveNzUdpdIg5l56Pl5jqg== + dependencies: + "@babel/types" "^7.7.0" + "@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" @@ -177,16 +231,16 @@ 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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.0.tgz#4d69ec653e8bff5bce62f5d33fc1508f223c75a7" + integrity sha512-pHx7RN8X0UNHPB/fnuDnRXVZ316ZigkO8y8D835JlZ2SSdFKb6yH9MIYRU4fy/KPe5sPHDFOPvf8QLdbAGGiyw== 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.7.0" + "@babel/helper-wrap-function" "^7.7.0" + "@babel/template" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" "@babel/helper-replace-supers@^7.5.5": version "7.5.5" @@ -198,6 +252,16 @@ "@babel/traverse" "^7.5.5" "@babel/types" "^7.5.5" +"@babel/helper-replace-supers@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.0.tgz#d5365c8667fe7cbd13b8ddddceb9bd7f2b387512" + integrity sha512-5ALYEul5V8xNdxEeWvRsBzLMxQksT7MaStpxjJf9KsnLxpAKBtfw5NeMKZJSYDa0lKdOcy0g+JT/f5mPSulUgg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.7.0" + "@babel/helper-optimise-call-expression" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + "@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" @@ -206,6 +270,14 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-simple-access@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.0.tgz#97a8b6c52105d76031b86237dc1852b44837243d" + integrity sha512-AJ7IZD7Eem3zZRuj5JtzFAptBw7pMlS3y8Qv09vaBWoFsle0d1kAn5Wq6Q9MyBXITPOKnxwkZKoAm4bopmv26g== + dependencies: + "@babel/template" "^7.7.0" + "@babel/types" "^7.7.0" + "@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" @@ -213,24 +285,31 @@ dependencies: "@babel/types" "^7.4.4" -"@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-split-export-declaration@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz#1365e74ea6c614deeb56ebffabd71006a0eb2300" + integrity sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.2.0" + "@babel/types" "^7.7.0" -"@babel/helpers@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.2.tgz#681ffe489ea4dcc55f23ce469e58e59c1c045153" - integrity sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA== +"@babel/helper-wrap-function@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.0.tgz#15af3d3e98f8417a60554acbb6c14e75e0b33b74" + integrity sha512-sd4QjeMgQqzshSjecZjOp8uKfUtnpmCyQhKQrVJBBgeHAB/0FPi33h3AbVlVp07qQtMD4QgYSzaMI7VwncNK/w== dependencies: - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.2" - "@babel/types" "^7.6.0" + "@babel/helper-function-name" "^7.7.0" + "@babel/template" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + +"@babel/helpers@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.0.tgz#359bb5ac3b4726f7c1fde0ec75f64b3f4275d60b" + integrity sha512-VnNwL4YOhbejHb7x/b5F39Zdg5vIQpUUNzJwx0ww1EcVRt41bbGRZWhAURrfY32T5zTT3qwNOQFWpn+P0i0a2g== + dependencies: + "@babel/template" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" "@babel/highlight@^7.0.0": version "7.5.0" @@ -241,12 +320,12 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/node@~7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.6.3.tgz#f175ab6718dde55431cbd4d9dee95f65c38be527" - integrity sha512-+nHje5AcE9TPlB/TRGYyOSQyTfhfU/WXniG6SkVf+V5+ibAjEqkH79lYdiEcytBTH4KeSf25IriySXs6TjaLjg== +"@babel/node@~7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.7.0.tgz#fba73fdaf75ab1a0eaf03923f5f4ce7fa41c9974" + integrity sha512-CZFTjfCGysChOJ90ksndqct5bXkByzV5Ef8YgYS3A513MhyFQgsXJMRu2QyGOlfoP3hBZ3AmDd37ARyv/L1Zvw== dependencies: - "@babel/register" "^7.6.2" + "@babel/register" "^7.7.0" commander "^2.8.1" core-js "^3.2.1" lodash "^4.17.13" @@ -254,24 +333,24 @@ regenerator-runtime "^0.13.3" v8flags "^3.1.1" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" - integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.2.tgz#ea8334dc77416bfd9473eb470fd00d8245b3943b" + integrity sha512-DDaR5e0g4ZTb9aP7cpSZLkACEBdoLGwJDWgHtBhrGX7Q1RjhdoMOfexICj5cqTAtpowjGQWfcvfnQG7G2kAB5w== -"@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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.0.tgz#83ef2d6044496b4c15d8b4904e2219e6dccc6971" + integrity sha512-ot/EZVvf3mXtZq0Pd0+tSOfGWMizqmOohXmNZg6LNFjHOV+wOPv7BvVYh8oPR8LhpIP3ye8nNooKL50YRWxpYA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/helper-remap-async-to-generator" "^7.7.0" "@babel/plugin-syntax-async-generators" "^7.2.0" -"@babel/plugin-proposal-dynamic-import@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506" - integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw== +"@babel/plugin-proposal-dynamic-import@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.0.tgz#dc02a8bad8d653fb59daf085516fa416edd2aa7f" + integrity sha512-7poL3Xi+QFPC7sGAzEIbXUyYzGJwbc2+gSD0AkiC5k52kH2cqHdqxm5hNFfLW3cRSTcx9bN0Fl7/6zWcLLnKAQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0" @@ -308,14 +387,13 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-throw-expressions" "^7.2.0" -"@babel/plugin-proposal-unicode-property-regex@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz#05413762894f41bfe42b9a5e80919bd575dcc802" - integrity sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw== +"@babel/plugin-proposal-unicode-property-regex@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.0.tgz#549fe1717a1bd0a2a7e63163841cb37e78179d5d" + integrity sha512-mk34H+hp7kRBWJOOAR0ZMGCydgKMD4iN9TpDRp3IIcbunltxEY89XSimc6WbtSLCDrwcdy/EEw7h5CFCzxTchw== dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.6.0" "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" @@ -359,6 +437,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-top-level-await@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.0.tgz#f5699549f50bbe8d12b1843a4e82f0a37bb65f4d" + integrity sha512-hi8FUNiFIY1fnUI2n1ViB1DR0R4QeK4iHcTlW6aJkrPoTdb8Rf1EMQ6GT3f67DDkYyWgew9DFoOZ6gOoEsdzTA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.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" @@ -366,14 +451,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.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-transform-async-to-generator@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.0.tgz#e2b84f11952cf5913fe3438b7d2585042772f492" + integrity sha512-vLI2EFLVvRBL3d8roAMqtVY0Bm9C1QzLkdS57hiKrjUBSqsQYrBsMCeOg/0KK7B0eK9V71J5mWcha9yyoI2tZw== dependencies: - "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-module-imports" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/helper-remap-async-to-generator" "^7.7.0" "@babel/plugin-transform-block-scoped-functions@^7.2.0": version "7.2.0" @@ -390,18 +475,18 @@ "@babel/helper-plugin-utils" "^7.0.0" 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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.0.tgz#b411ecc1b8822d24b81e5d184f24149136eddd4a" + integrity sha512-/b3cKIZwGeUesZheU9jNYcwrEA7f/Bo4IdPmvp7oHgvks2majB5BoT5byAql44fiNQYOPzhk2w8DbgfuafkMoA== 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-annotate-as-pure" "^7.7.0" + "@babel/helper-define-map" "^7.7.0" + "@babel/helper-function-name" "^7.7.0" + "@babel/helper-optimise-call-expression" "^7.7.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-replace-supers" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.2.0": @@ -418,14 +503,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz#44abb948b88f0199a627024e1508acaf8dc9b2f9" - integrity sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA== +"@babel/plugin-transform-dotall-regex@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.0.tgz#c5c9ecacab3a5e0c11db6981610f0c32fd698b3b" + integrity sha512-3QQlF7hSBnSuM1hQ0pS3pmAbWLax/uGNCbPBND9y+oJ4Y776jsyujG2k0Sn2Aj2a0QwVOiOFL5QVPA7spjvzSA== dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.6.0" "@babel/plugin-transform-duplicate-keys@^7.5.0": version "7.5.0" @@ -449,12 +533,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.0.tgz#0fa786f1eef52e3b7d4fc02e54b2129de8a04c2a" + integrity sha512-P5HKu0d9+CzZxP5jcrWdpe7ZlFDe24bmqP6a6X8BHEBl/eizAsY8K6LX8LASZL0Jxdjm5eEfzp+FIrxCm/p8bA== dependencies: - "@babel/helper-function-name" "^7.1.0" + "@babel/helper-function-name" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-literals@^7.2.0": @@ -480,39 +564,39 @@ "@babel/helper-plugin-utils" "^7.0.0" 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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.0.tgz#3e5ffb4fd8c947feede69cbe24c9554ab4113fe3" + integrity sha512-KEMyWNNWnjOom8vR/1+d+Ocz/mILZG/eyHHO06OuBQ2aNhxT62fr4y6fGOplRx+CxCSp3IFwesL8WdINfY/3kg== dependencies: - "@babel/helper-module-transforms" "^7.4.4" + "@babel/helper-module-transforms" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-simple-access" "^7.7.0" 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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.0.tgz#9baf471213af9761c1617bb12fd278e629041417" + integrity sha512-ZAuFgYjJzDNv77AjXRqzQGlQl4HdUM6j296ee4fwKVZfhDR9LAGxfvXjBkb06gNETPnN0sLqRm9Gxg4wZH6dXg== dependencies: - "@babel/helper-hoist-variables" "^7.4.4" + "@babel/helper-hoist-variables" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-umd@^7.2.0": - version "7.2.0" - 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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.0.tgz#d62c7da16670908e1d8c68ca0b5d4c0097b69966" + integrity sha512-u7eBA03zmUswQ9LQ7Qw0/ieC1pcAkbp5OQatbWUzY1PaBccvuJXUkYzoN1g7cqp7dbTu6Dp9bXyalBvD04AANA== dependencies: - "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-module-transforms" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf" - integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.0.tgz#358e6fd869b9a4d8f5cbc79e4ed4fc340e60dcaf" + integrity sha512-+SicSJoKouPctL+j1pqktRVCgy+xAch1hWWTMy13j0IflnyNjaoskj+DwRQFimHbLqO3sq2oN2CXMvXq3Bgapg== dependencies: - regexpu-core "^4.6.0" + "@babel/helper-create-regexp-features-plugin" "^7.7.0" "@babel/plugin-transform-new-target@^7.4.4": version "7.4.4" @@ -545,10 +629,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@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.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.0.tgz#f1b20b535e7716b622c99e989259d7dd942dd9cc" + integrity sha512-AXmvnC+0wuj/cFkkS/HFHIojxH3ffSXE+ttulrqWjZZRaUOonfJc60e1wSNT4rV8tIunvu/R3wCp71/tLAa9xg== dependencies: regenerator-transform "^0.14.0" @@ -596,81 +680,81 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-unicode-regex@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz#b692aad888a7e8d8b1b214be6b9dc03d5031f698" - integrity sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw== +"@babel/plugin-transform-unicode-regex@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.0.tgz#743d9bcc44080e3cc7d49259a066efa30f9187a3" + integrity sha512-RrThb0gdrNwFAqEAAx9OWgtx6ICK69x7i9tCnMdVrxQwSDp/Abu9DXFU5Hh16VP33Rmxh04+NGW28NsIkFvFKA== dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.6.0" -"@babel/preset-env@~7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271" - integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ== +"@babel/preset-env@~7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.1.tgz#04a2ff53552c5885cf1083e291c8dd5490f744bb" + integrity sha512-/93SWhi3PxcVTDpSqC+Dp4YxUu3qZ4m7I76k0w73wYfn7bGVuRIO4QUz95aJksbS+AD1/mT1Ie7rbkT0wSplaA== dependencies: - "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-module-imports" "^7.7.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-async-generator-functions" "^7.7.0" + "@babel/plugin-proposal-dynamic-import" "^7.7.0" "@babel/plugin-proposal-json-strings" "^7.2.0" "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" + "@babel/plugin-proposal-unicode-property-regex" "^7.7.0" "@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-syntax-top-level-await" "^7.7.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.5.0" + "@babel/plugin-transform-async-to-generator" "^7.7.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" "@babel/plugin-transform-block-scoping" "^7.6.3" - "@babel/plugin-transform-classes" "^7.5.5" + "@babel/plugin-transform-classes" "^7.7.0" "@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.6.2" + "@babel/plugin-transform-dotall-regex" "^7.7.0" "@babel/plugin-transform-duplicate-keys" "^7.5.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" "@babel/plugin-transform-for-of" "^7.4.4" - "@babel/plugin-transform-function-name" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.7.0" "@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.3" + "@babel/plugin-transform-modules-commonjs" "^7.7.0" + "@babel/plugin-transform-modules-systemjs" "^7.7.0" + "@babel/plugin-transform-modules-umd" "^7.7.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.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-regenerator" "^7.7.0" "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" "@babel/plugin-transform-spread" "^7.6.2" "@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.6.2" - "@babel/types" "^7.6.3" + "@babel/plugin-transform-unicode-regex" "^7.7.0" + "@babel/types" "^7.7.1" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/register@^7.6.2", "@babel/register@~7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.6.2.tgz#25765a922202cb06f8bdac5a3b1e70cd6bf3dd45" - integrity sha512-xgZk2LRZvt6i2SAUWxc7ellk4+OYRgS3Zpsnr13nMS1Qo25w21Uu8o6vTOAqNaxiqrnv30KTYzh9YWY2k21CeQ== +"@babel/register@^7.7.0", "@babel/register@~7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.7.0.tgz#4e23ecf840296ef79c605baaa5c89e1a2426314b" + integrity sha512-HV3GJzTvSoyOMWGYn2TAh6uL6g+gqKTgEZ99Q3+X9UURT1VPT/WcU46R61XftIc5rXytcOHZ4Z0doDlsjPomIg== 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" @@ -687,34 +771,34 @@ 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.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" + integrity sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/parser" "^7.7.0" + "@babel/types" "^7.7.0" -"@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.2", "@babel/traverse@^7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" - integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw== +"@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.7.0", "@babel/traverse@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.2.tgz#ef0a65e07a2f3c550967366b3d9b62a2dcbeae09" + integrity sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.3" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.3" - "@babel/types" "^7.6.3" + "@babel/generator" "^7.7.2" + "@babel/helper-function-name" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" + "@babel/parser" "^7.7.2" + "@babel/types" "^7.7.2" 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", "@babel/types@^7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" - integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.7.0", "@babel/types@^7.7.1", "@babel/types@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.2.tgz#550b82e5571dcd174af576e23f0adba7ffc683f7" + integrity sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA== dependencies: esutils "^2.0.2" lodash "^4.17.13" @@ -728,6 +812,34 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@graphql-toolkit/common@0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.6.8.tgz#7e80f4821d8484970d768b7cb19a0bc1e7a90784" + integrity sha512-vvxLMumGhFsSU/SPbMHsHPtr1WpD98EngSVzDoH4EfiobT8O9RKCaK/Nr57nWxbk1+A0iAC3OrHYugp46au5sQ== + dependencies: + "@kamilkisiela/graphql-tools" "4.0.6" + aggregate-error "3.0.1" + lodash "4.17.15" + +"@graphql-toolkit/file-loading@0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/file-loading/-/file-loading-0.6.8.tgz#6b03c752c2c5dd917172f7070cf92626e0fd83bf" + integrity sha512-kR7EizV5COWhYhkf0eq4/EAtGhbamjq3EvNifiFoRdGzJxIVwfD4If56rFAVCp7dBmUHl4mRG8RySydwIu1xGQ== + dependencies: + "@graphql-toolkit/common" "0.6.8" + "@kamilkisiela/graphql-tools" "4.0.6" + glob "7.1.6" + +"@graphql-toolkit/schema-merging@0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@graphql-toolkit/schema-merging/-/schema-merging-0.6.8.tgz#67c0d83f9975026b20a69fca6a01f9b7d04ebbdb" + integrity sha512-/QhyfscZLqJLOgM0fD9Dxb5ps9OYyaOPxNS2mgjvX++jbNkJW2128b3KrB0nJzFdVEiOllRoaYHGKM4w8lvhmg== + dependencies: + "@graphql-toolkit/common" "0.6.8" + "@kamilkisiela/graphql-tools" "4.0.6" + deepmerge "4.2.2" + tslib "1.10.0" + "@hapi/address@2.x.x", "@hapi/address@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.2.tgz#1c794cd6dbf2354d1eb1ef10e0303f573e1c7222" @@ -963,7 +1075,7 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.7.14", "@metascraper/helpers@^5.7.17", "@metascraper/helpers@^5.7.6": +"@metascraper/helpers@^5.7.17": version "5.7.17" resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.17.tgz#401897c7239090ca7149b83e581712845bbb3709" integrity sha512-t21LqfDpaIrWg2JaivXG6mVzUsIVW05cAsKySA5Tj9Hgi9oZXxaaNes5XipOzk6P242RI48SDo7CkSbYiio7Tw== @@ -1042,44 +1154,44 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@sentry/core@5.7.1": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.7.1.tgz#3eb2b7662cac68245931ee939ec809bf7a639d0e" - integrity sha512-AOn3k3uVWh2VyajcHbV9Ta4ieDIeLckfo7UMLM+CTk2kt7C89SayDGayJMSsIrsZlL4qxBoLB9QY4W2FgAGJrg== +"@sentry/core@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.8.0.tgz#bbfd2f4711491951a8e3a0e8fa8b172fdf7bff6f" + integrity sha512-aAh2KLidIXJVGrxmHSVq2eVKbu7tZiYn5ylW6yzJXFetS5z4MA+JYaSBaG2inVYDEEqqMIkb17TyWxxziUDieg== dependencies: - "@sentry/hub" "5.7.1" - "@sentry/minimal" "5.7.1" + "@sentry/hub" "5.8.0" + "@sentry/minimal" "5.8.0" "@sentry/types" "5.7.1" - "@sentry/utils" "5.7.1" + "@sentry/utils" "5.8.0" tslib "^1.9.3" -"@sentry/hub@5.7.1": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.7.1.tgz#a52acd9fead7f3779d96e9965c6978aecc8b9cad" - integrity sha512-evGh323WR073WSBCg/RkhlUmCQyzU0xzBzCZPscvcoy5hd4SsLE6t9Zin+WACHB9JFsRQIDwNDn+D+pj3yKsig== +"@sentry/hub@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.8.0.tgz#56aaeb7324cb66d90db838011cb0127f5558007f" + integrity sha512-VdApn1ZCNwH1wwQwoO6pu53PM/qgHG+DQege0hbByluImpLBhAj9w50nXnF/8KzV4UoMIVbzCb6jXzMRmqqp9A== dependencies: "@sentry/types" "5.7.1" - "@sentry/utils" "5.7.1" + "@sentry/utils" "5.8.0" tslib "^1.9.3" -"@sentry/minimal@5.7.1": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.7.1.tgz#56afc537737586929e25349765e37a367958c1e1" - integrity sha512-nS/Dg+jWAZtcxQW8wKbkkw4dYvF6uyY/vDiz/jFCaux0LX0uhgXAC9gMOJmgJ/tYBLJ64l0ca5LzpZa7BMJQ0g== +"@sentry/minimal@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.8.0.tgz#b7ad5113504ab67f1ef2b0f465b7ba608e6b8dc5" + integrity sha512-MIlFOgd+JvAUrBBmq7vr9ovRH1HvckhnwzHdoUPpKRBN+rQgTyZy1o6+kA2fASCbrRqFCP+Zk7EHMACKg8DpIw== dependencies: - "@sentry/hub" "5.7.1" + "@sentry/hub" "5.8.0" "@sentry/types" "5.7.1" tslib "^1.9.3" -"@sentry/node@^5.7.1": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.7.1.tgz#94e2fbac94f6cc061be3bc14b22813536c59698d" - integrity sha512-hVM10asFStrOhYZzMqFM7V1lrHkr1ydc2n/SFG0ZmIQxfTjCVElyXV/BJASIdqadM1fFIvvtD/EfgkTcZmub1g== +"@sentry/node@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.8.0.tgz#68ec032b0e7fb840cc8ccc1b39c09ac6febc1046" + integrity sha512-hIzt1BysyQJez8ChgWpFkLcGq3t/HaLMqzrXF5vu+Uuekl5OfwsvzZ+8Dlv78rI4CvlL9a2EuI/94iqUNwhOSQ== dependencies: - "@sentry/core" "5.7.1" - "@sentry/hub" "5.7.1" + "@sentry/core" "5.8.0" + "@sentry/hub" "5.8.0" "@sentry/types" "5.7.1" - "@sentry/utils" "5.7.1" + "@sentry/utils" "5.8.0" cookie "^0.3.1" https-proxy-agent "^3.0.0" lru_map "^0.3.3" @@ -1090,10 +1202,10 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.7.1.tgz#4c4c1d4d891b6b8c2c3c7b367d306a8b1350f090" integrity sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ== -"@sentry/utils@5.7.1": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.7.1.tgz#cf37ad55f78e317665cd8680f202d307fa77f1d0" - integrity sha512-nhirUKj/qFLsR1i9kJ5BRvNyzdx/E2vorIsukuDrbo8e3iZ11JMgCOVrmC8Eq9YkHBqgwX4UnrPumjFyvGMZ2Q== +"@sentry/utils@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.8.0.tgz#34683088159b9935f973b6e6cad1a1cc26bbddac" + integrity sha512-KDxUvBSYi0/dHMdunbxAxD3389pcQioLtcO6CI6zt/nJXeVFolix66cRraeQvqupdLhvOk/el649W4fCPayTHw== dependencies: "@sentry/types" "5.7.1" tslib "^1.9.3" @@ -1182,11 +1294,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" @@ -1211,15 +1318,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" @@ -1299,11 +1397,6 @@ 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@*", "@types/node@>=6": version "12.7.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44" @@ -1454,7 +1547,15 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -aggregate-error@3.0.0, aggregate-error@^3.0.0: +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: + clean-stack "^2.0.0" + indent-string "^4.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== @@ -1931,11 +2032,6 @@ asynckit@^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" @@ -2556,10 +2652,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.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" @@ -2633,22 +2729,6 @@ cross-env@~6.0.3: dependencies: cross-spawn "^7.0.0" -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@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2785,10 +2865,10 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-fns@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.6.0.tgz#a5bc82e6a4c3995ae124b0ba1a71aec7b8cbd666" - integrity sha512-F55YxqRdEfP/eYQmQjLN798v0AwLjmZ8nMBjdQvNwEE3N/zWVrlkkqT+9seBlPlsbkybG4JmWg3Ee3dIV9BcGQ== +date-fns@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.7.0.tgz#8271d943cc4636a1f27698f1b8d6a9f1ceb74026" + integrity sha512-wxYp2PGoUDN5ZEACc61aOtYFvSsJUylIvCjpjDOqM1UDaKIIuMJ9fAnMYFHV3TQaDpfTVxhwNK/GiCaHKuemTA== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" @@ -2845,10 +2925,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" @@ -3262,10 +3342,10 @@ eslint-plugin-import@~2.18.2: read-pkg-up "^2.0.0" resolve "^1.11.0" -eslint-plugin-jest@~23.0.2: - version "23.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.0.2.tgz#54a59bfe77245186afe13711a297067aefefff0a" - integrity sha512-fkxcvOJm0hC/jbJqYJjtuC9mvpTJqXd0Nixx7joVQvJoBQuXk/ws3+MtRYzD/4TcKSgvr21uuSLdwSxKJKC2cg== +eslint-plugin-jest@~23.0.3: + version "23.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.0.3.tgz#d3f157f7791f97713372c13259ba1dfc436eb4c1" + integrity sha512-9cNxr66zeOyz1S9AkQL4/ouilR6QHpYj8vKOQZ60fu9hAt5PJWS4KqWqfr1aqN5NFEZSPjFOla2Azn+KTWiGwg== dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" @@ -3589,7 +3669,6 @@ extsprintf@^1.2.0: faker@Marak/faker.js#master: version "4.1.0" - uid "9fd8d7d37b398842d0784a116a340f7aa6afb89b" resolved "https://codeload.github.com/Marak/faker.js/tar.gz/9fd8d7d37b398842d0784a116a340f7aa6afb89b" fast-deep-equal@^2.0.1: @@ -3902,10 +3981,10 @@ glob-parent@^5.0.0: 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.1.6: + 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" @@ -3914,7 +3993,7 @@ glob@7.1.4, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.5" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0" integrity sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ== @@ -4005,14 +4084,6 @@ graphql-extensions@^0.10.4: apollo-server-env "^2.4.3" apollo-server-types "^0.2.5" -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" - 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" @@ -4023,27 +4094,19 @@ graphql-middleware-sentry@^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@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-4.0.1.tgz#8c627b22cc046a47e9474a813cf9e0bd50fa0c4b" - integrity sha512-r9r+pcHV4yZW7LAOcjQYTbNY6nR9SrLgpVZKbrtgXxpQW/MUc1N8q3PESciebvp5s0EEUgRchcRjUkyaArCIFw== +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== - dependencies: - cross-fetch "2.2.2" - -graphql-shield@~7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.0.tgz#0cdca5c16af4ba7dd4fbcce6df279c5c8e463452" - integrity sha512-hr0PR6t/UXAO5+BMOOD2W3zTIKbtO/u8twjLn8hw4646E08NeLrIxDAmUFKKlLhyTe5JzlH4nNflP6SRtL6Q2A== +graphql-shield@~7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.0.2.tgz#65d51528b4e36c4a5023276267abbd1abb57ce0b" + integrity sha512-ZgCQ9efzgehkIVMFoxaOubnvwHVqthtsIXA+xRu8B4ga2Ch/EHiswNCDKpTXTBQZmjeDCSHeAj4XDmbzR4Trxw== dependencies: "@types/yup" "0.26.24" - lightercollective "^0.3.0" object-hash "^2.0.0" yup "^0.27.0" @@ -4059,25 +4122,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" @@ -4609,11 +4653,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" @@ -4646,20 +4685,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" @@ -4667,6 +4692,13 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +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-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" @@ -4675,13 +4707,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" @@ -4706,11 +4731,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" @@ -4803,13 +4823,6 @@ 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" @@ -5492,11 +5505,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" @@ -5602,7 +5610,7 @@ 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.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== @@ -5708,12 +5716,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.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/merge-graphql-schemas/-/merge-graphql-schemas-1.7.3.tgz#220445a43b969564b874c580c4005fb7ebe3d473" + integrity sha512-qXROTOo3R/pfHYpPcrY59FCojdPGPUvyuh51FVeImrxVXEWulZ8tq0NZIFO5y8GK6/pl5m3FayJIhOJp/g1PkA== dependencies: - graphql-toolkit "0.5.0" + "@graphql-toolkit/file-loading" "0.6.8" + "@graphql-toolkit/schema-merging" "0.6.8" tslib "1.10.0" merge-stream@^2.0.0: @@ -5721,12 +5730,12 @@ 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.7.14: - version "5.7.14" - resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.7.14.tgz#f5edbc0249af3fcd09863580c3531eb4b8546589" - integrity sha512-ev2PIDNTILznkDeWy/CFaKdhFlG/3URF0OOO4J2MXy8VNKd5IyP/+LF7zoPR9q5+C40+zRAmy8cqJId3qVAa2A== +metascraper-audio@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.7.17.tgz#b8e78a797deb155b02f30bcbe39da554bf1bf898" + integrity sha512-g11lRNVor5Pu4D1j3tL7aakSQM51CUl2Evp8QgFKcuYGjF+a1RiGq6veojiTf/9nWcKX8dUSTUJkQSIzdoJrFQ== dependencies: - "@metascraper/helpers" "^5.7.14" + "@metascraper/helpers" "^5.7.17" metascraper-author@^5.7.17: version "5.7.17" @@ -5750,19 +5759,19 @@ metascraper-date@^5.7.17: dependencies: "@metascraper/helpers" "^5.7.17" -metascraper-description@^5.7.14: - version "5.7.14" - resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.7.14.tgz#4b77b04120f5f4f17dcabb4f549a7954da74b581" - integrity sha512-++qN4Rf0Hx13SbhJgRiLSuVOZHsYwhUkMfHa5sVVihSJkrLVjOSdBTpNBajRC7yHwG6m6/qIesuERbT1jdu5bw== +metascraper-description@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.7.17.tgz#b0daa54d0345546ececcc033065790402aabb5ec" + integrity sha512-cQfg9Spl3FLK2x8O7DvecwSYEBUmRjtdZW2y1EVqHsOKwT13SeUy1kp+lZa8+8vFh4o8oJPzXHxgbLhAfAmVqQ== dependencies: - "@metascraper/helpers" "^5.7.14" + "@metascraper/helpers" "^5.7.17" -metascraper-image@^5.7.6: - version "5.7.6" - resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.7.6.tgz#77c45dfd28f6f0903417545196c129ec64cdbe6d" - integrity sha512-SIztcREe0m1p1wCUNh+mFVfXPbFQfOu6dZVhU1BIxb6+0km+pUOtPi6Kxnax10ZH4k0ZlFDmLr98yV/ydYdgvw== +metascraper-image@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.7.17.tgz#186b29979cb8aefc6c21d0342c386a8fef80be55" + integrity sha512-bwAUJrJibJ+fJGxL8T789Ki1z+8sqsz0sqb3W+mfR/ZLkhCu+jWLYqPVtMgTPM9Zaqqqxg5uTQs1uAVrnguKDA== dependencies: - "@metascraper/helpers" "^5.7.6" + "@metascraper/helpers" "^5.7.17" metascraper-lang-detector@^4.8.5: version "4.10.2" @@ -5787,12 +5796,12 @@ metascraper-logo@^5.7.17: dependencies: "@metascraper/helpers" "^5.7.17" -metascraper-publisher@^5.7.14: - version "5.7.14" - resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.7.14.tgz#74218cd2c1042264a3df754930e18d5e1f731750" - integrity sha512-6WHVr03tu4+KtcETR/q8y7ND9HxKI8QEEI1NAVpAolm1szDSpzV1PXVxWILcc2zPsaAHBSvM0iMwmE1zBEs+BA== +metascraper-publisher@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.7.17.tgz#38455e035d8d34c42eff529316ee15f31726d641" + integrity sha512-BxiweB0vxXX0UF2YVxzwC7Y8X0A5mU+eaa6TsTrTGHPBWeZCUJaLJ2Ge35c00SIC+USgdu8KFyzF6+pJBObwvQ== dependencies: - "@metascraper/helpers" "^5.7.14" + "@metascraper/helpers" "^5.7.17" metascraper-soundcloud@^5.7.17: version "5.7.17" @@ -5803,20 +5812,20 @@ metascraper-soundcloud@^5.7.17: memoize-one "~5.1.1" tldts "~5.6.1" -metascraper-title@^5.7.14: - version "5.7.14" - resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.7.14.tgz#4abea12bc9f0d3df5b442cb3c1a8c6559e417ea4" - integrity sha512-ZiVo4LEfqiNHlCGjht5OSZ3yRKxcZnbaXeRmUReMkCHcFujok5YZBj5ktDpAANmG9T3x2gn3twM3ZbBSyXLYyg== +metascraper-title@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.7.17.tgz#5b947635361bfb4d7557eadcb623489c812322e6" + integrity sha512-YCEbiU2MbPMLulXmLbSBN/N7ti9tBVr45yqMKSuFsWiNJ98bFsM1IQp1LN5KqRQmNkOg+8JsYgK+R9vqYwaGjg== dependencies: - "@metascraper/helpers" "^5.7.14" + "@metascraper/helpers" "^5.7.17" lodash "~4.17.15" -metascraper-url@^5.7.14: - version "5.7.14" - resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.7.14.tgz#20645ec0299f1fe4bf194b08037e344b9555bfd0" - integrity sha512-scsXsbhI9VFcmgtMI/bsr+onvzzWGX4h80pitQQpECA7X2K2qcm5qic+anv6K2simbPJ/brDkhHC2rMRm9snbw== +metascraper-url@^5.7.17: + version "5.7.17" + resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.7.17.tgz#e8ba40a17a59b54139f42d6e3cf430dc6f32e7d7" + integrity sha512-7OOhCXpxdMiJatrbxa9rqLmUT/t/s34PDgtknoE/2FfmZY7X/xyORamcuqUHjV37sOpCPTun+GcJL4l3ddCi3Q== dependencies: - "@metascraper/helpers" "^5.7.14" + "@metascraper/helpers" "^5.7.17" metascraper-video@^5.7.17: version "5.7.17" @@ -6114,12 +6123,7 @@ 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== @@ -6457,15 +6461,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" @@ -7563,10 +7558,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" @@ -8406,11 +8401,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" @@ -8419,6 +8409,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-12.0.0.tgz#fb33221f5320abe2422cda2f517dc3838064e813" + integrity sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -8475,16 +8470,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: 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: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" 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 51f0eb43c..0f75127e5 100644 --- a/deployment/human-connection/deployment-backend.yaml +++ b/deployment/human-connection/deployment-backend.yaml @@ -1,47 +1,60 @@ ---- - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: nitro-backend - namespace: human-connection - spec: - replicas: 1 - minReadySeconds: 15 - progressDeadlineSeconds: 60 - strategy: - rollingUpdate: - maxSurge: 0 - maxUnavailable: "100%" - selector: - matchLabels: +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + human-connection.org/commit: COMMIT + human-connection.org/selector: deployment-human-connection-backend + name: backend + namespace: human-connection +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 60 + replicas: 1 + revisionHistoryLimit: 2147483647 + selector: + matchLabels: + human-connection.org/selector: deployment-human-connection-backend + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 100% + type: RollingUpdate + template: + metadata: + annotations: + backup.velero.io/backup-volumes: uploads + creationTimestamp: null + labels: + human-connection.org/commit: COMMIT human-connection.org/selector: deployment-human-connection-backend - template: - metadata: - annotations: - backup.velero.io/backup-volumes: uploads - labels: - human-connection.org/commit: COMMIT - human-connection.org/selector: deployment-human-connection-backend - name: "nitro-backend" - spec: - containers: - - name: nitro-backend - image: humanconnection/nitro-backend:latest - imagePullPolicy: Always - ports: - - containerPort: 4000 - envFrom: - - configMapRef: - name: configmap - - secretRef: - name: human-connection - volumeMounts: - - mountPath: /nitro-backend/public/uploads - name: uploads - volumes: - - name: uploads - persistentVolumeClaim: - claimName: uploads-claim - restartPolicy: Always - terminationGracePeriodSeconds: 30 - status: {} + name: backend + spec: + containers: + - envFrom: + - configMapRef: + name: configmap + - secretRef: + name: human-connection + image: humanconnection/nitro-backend:latest + imagePullPolicy: Always + name: nitro-backend + ports: + - containerPort: 4000 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /nitro-backend/public/uploads + name: uploads + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: uploads + persistentVolumeClaim: + claimName: uploads-claim +status: {} 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/package.json b/package.json index 980391d97..38509a631 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nitro-cypress", - "version": "0.1.8", + "version": "0.1.10", "description": "Fullstack tests with cypress for Human Connection", "author": "Human Connection gGmbh", "license": "MIT", @@ -24,11 +24,11 @@ "bcryptjs": "^2.4.3", "codecov": "^3.6.1", "cross-env": "^6.0.3", - "cypress": "^3.5.0", + "cypress": "^3.6.1", "cypress-cucumber-preprocessor": "^1.16.2", - "cypress-file-upload": "^3.4.0", - "cypress-plugin-retries": "^1.3.0", - "date-fns": "^2.6.0", + "cypress-file-upload": "^3.5.0", + "cypress-plugin-retries": "^1.4.0", + "date-fns": "^2.7.0", "dotenv": "^8.2.0", "faker": "Marak/faker.js#master", "graphql-request": "^1.8.2", 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/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/webapp/README.md b/webapp/README.md index b9c235196..def0b739e 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -33,48 +33,74 @@ $ yarn build $ yarn start ``` -### Storybook +### Run tests -We encourage contributors to use Storybook to test out new components in an isolated way, and benefit from its many features. -See the docs for live examples and answers to FAQ, among other helpful information. ![Storybook docs](https://storybook.js.org/docs/basics/introduction/) +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 + +For more information see our [frontend testing guide](testing.md). Use these commands to run the tests: {% tabs %} -{% tab title="Docker" %} +{% tab title="With Docker" %} -After you have started the application following the instructions above, in another terminal run: +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 ``` -The output should look similar to this: -![Storybook output](../.gitbook/assets/storybook-output.png) - -Click on the link http://localhost:3002/ to open the browser to your interactive storybook. +You can then visit the Storybook playground on `http://localhost:3002` {% endtab %} {% tab title="Without Docker" %} -Run the following command: + +After starting the application following the above guidelines, open new terminal windows and navigate to the `/webapp` directory for each of these commands: ```bash -# in webapp/ -yarn storybook +# run eslint in /webapp +$ yarn lint ``` -Open http://localhost:3002/ in your browser +```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 `view` folder to separate them from the old, yet untouched components. -## Styleguide +### Folder Structure -All reusable Components \(for example avatar\) should be done inside the [Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) repository. +The folder structure we are aiming for is based on the [directory setup proposed by Nuxt.js](https://nuxtjs.org/guide/directory-structure): -![Styleguide Screenshot](../.gitbook/assets/screenshot-styleguide%20%281%29.png) - -More information can be found here: [https://github.com/Human-Connection/Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) - -If you need to change something in the styleguide and want to see the effects on the frontend immediately, then we have you covered. You need to clone the styleguide to the parent directory `../Nitro-Styleguide` and run `yarn && yarn run dev`. After that you run `yarn run dev:styleguide` instead of `yarn run dev` and you will see your changes reflected inside the frontend! +- **assets** contains icons, images and logos in `svg` format +- **components** 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 +- **styles** holds all shared SCSS files such as `variables` and `mixins` 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/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/AvatarMenu/AvatarMenu.spec.js b/webapp/components/AvatarMenu/AvatarMenu.spec.js new file mode 100644 index 000000000..6327ded0a --- /dev/null +++ b/webapp/components/AvatarMenu/AvatarMenu.spec.js @@ -0,0 +1,168 @@ +import { config, mount, createLocalVue } from '@vue/test-utils' +import Vuex from 'vuex' +import VTooltip from 'v-tooltip' +import Styleguide from '@human-connection/styleguide' +import AvatarMenu from './AvatarMenu.vue' +import Filters from '~/plugins/vue-filters' + +const localVue = createLocalVue() +localVue.use(Styleguide) +localVue.use(Vuex) +localVue.use(Filters) +localVue.use(VTooltip) + +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 HcAvatar component', () => { + wrapper.find('.avatar-menu-trigger').trigger('click') + expect(wrapper.find('.ds-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..393963997 --- /dev/null +++ b/webapp/components/AvatarMenu/AvatarMenu.vue @@ -0,0 +1,146 @@ + + + diff --git a/webapp/components/DonationInfo/DonationInfo.spec.js b/webapp/components/DonationInfo/DonationInfo.spec.js new file mode 100644 index 000000000..ca719c19a --- /dev/null +++ b/webapp/components/DonationInfo/DonationInfo.spec.js @@ -0,0 +1,80 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import Styleguide from '@human-connection/styleguide' +import DonationInfo from './DonationInfo.vue' + +const localVue = createLocalVue() +localVue.use(Styleguide) + +const mockDate = new Date(2019, 11, 6) +global.Date = jest.fn(() => mockDate) + +describe('DonationInfo.vue', () => { + let mocks, wrapper + + beforeEach(() => { + mocks = { + $t: jest.fn(string => string), + $i18n: { + locale: () => 'de', + }, + } + }) + + const Wrapper = () => mount(DonationInfo, { mocks, localVue }) + + it('includes a link to the Human Connection donations website', () => { + expect( + Wrapper() + .find('a') + .attributes('href'), + ).toBe('https://human-connection.org/spenden/') + }) + + it('displays a call to action button', () => { + expect( + Wrapper() + .find('.ds-button') + .text(), + ).toBe('donations.donate-now') + }) + + it('creates a title from the current month and a translation string', () => { + mocks.$t = jest.fn(() => 'Spenden für') + expect(Wrapper().vm.title).toBe('Spenden für Dezember') + }) + + describe('mount with data', () => { + beforeEach(() => { + wrapper = Wrapper() + wrapper.setData({ goal: 50000, progress: 10000 }) + }) + + describe('given german locale', () => { + it('creates a label from the given amounts and a translation string', () => { + expect(mocks.$t).toBeCalledWith( + 'donations.amount-of-total', + expect.objectContaining({ + amount: '10.000', + total: '50.000', + }), + ) + }) + }) + + describe('given english locale', () => { + beforeEach(() => { + mocks.$i18n.locale = () => 'en' + }) + + it('creates a label from the given amounts and a translation string', () => { + expect(mocks.$t).toBeCalledWith( + 'donations.amount-of-total', + expect.objectContaining({ + amount: '10,000', + total: '50,000', + }), + ) + }) + }) + }) +}) diff --git a/webapp/components/DonationInfo/DonationInfo.vue b/webapp/components/DonationInfo/DonationInfo.vue new file mode 100644 index 000000000..10f42e880 --- /dev/null +++ b/webapp/components/DonationInfo/DonationInfo.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/webapp/components/DropdownFilter/DropdownFilter.spec.js b/webapp/components/DropdownFilter/DropdownFilter.spec.js new file mode 100644 index 000000000..9566fa6ef --- /dev/null +++ b/webapp/components/DropdownFilter/DropdownFilter.spec.js @@ -0,0 +1,78 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import VTooltip from 'v-tooltip' +import Styleguide from '@human-connection/styleguide' +import DropdownFilter from './DropdownFilter.vue' + +const localVue = createLocalVue() +localVue.use(Styleguide) +localVue.use(VTooltip) + +describe('DropdownFilter.vue', () => { + let propsData, wrapper, mocks + + beforeEach(() => { + propsData = {} + mocks = { + $t: jest.fn(a => a), + } + }) + + const Wrapper = () => { + return mount(DropdownFilter, { propsData, localVue, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('selected', () => { + it('displays selected filter', () => { + propsData.selected = 'Read' + wrapper = Wrapper() + expect(wrapper.find('.dropdown-filter label').text()).toEqual(propsData.selected) + }) + }) + + describe('menu items', () => { + let allLink + beforeEach(() => { + propsData.filterOptions = [ + { label: 'All', value: null }, + { label: 'Read', value: true }, + { label: 'Unread', value: false }, + ] + wrapper = Wrapper() + wrapper.find('.dropdown-filter').trigger('click') + allLink = wrapper + .findAll('.dropdown-menu-item') + .at(propsData.filterOptions.findIndex(option => option.label === 'All')) + }) + + it('displays a link for All', () => { + expect(allLink.text()).toEqual('All') + }) + + it('displays a link for Read', () => { + const readLink = wrapper + .findAll('.dropdown-menu-item') + .at(propsData.filterOptions.findIndex(option => option.label === 'Read')) + expect(readLink.text()).toEqual('Read') + }) + + it('displays a link for Unread', () => { + const unreadLink = wrapper + .findAll('.dropdown-menu-item') + .at(propsData.filterOptions.findIndex(option => option.label === 'Unread')) + expect(unreadLink.text()).toEqual('Unread') + }) + + it('clicking on menu item emits filterNotifications', () => { + allLink.trigger('click') + expect(wrapper.emitted().filterNotifications[0]).toEqual( + propsData.filterOptions.filter(option => option.label === 'All'), + ) + }) + }) + }) +}) diff --git a/webapp/components/DropdownFilter/DropdownFilter.story.js b/webapp/components/DropdownFilter/DropdownFilter.story.js new file mode 100644 index 000000000..0703c5c47 --- /dev/null +++ b/webapp/components/DropdownFilter/DropdownFilter.story.js @@ -0,0 +1,30 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import { action } from '@storybook/addon-actions' +import DropdownFilter from '~/components/DropdownFilter/DropdownFilter' +import helpers from '~/storybook/helpers' + +helpers.init() +const filterOptions = [ + { label: 'All', value: null }, + { label: 'Read', value: true }, + { label: 'Unread', value: false }, +] +storiesOf('DropdownFilter', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('filter dropdown', () => ({ + components: { DropdownFilter }, + data: () => ({ + filterOptions, + selected: filterOptions[0].label, + }), + methods: { + filterNotifications: action('filterNotifications'), + }, + template: ``, + })) diff --git a/webapp/components/DropdownFilter/DropdownFilter.vue b/webapp/components/DropdownFilter/DropdownFilter.vue new file mode 100644 index 000000000..c24cf18bc --- /dev/null +++ b/webapp/components/DropdownFilter/DropdownFilter.vue @@ -0,0 +1,78 @@ + + + diff --git a/webapp/components/Empty/Empty.spec.js b/webapp/components/Empty/Empty.spec.js new file mode 100644 index 000000000..a4220fa63 --- /dev/null +++ b/webapp/components/Empty/Empty.spec.js @@ -0,0 +1,54 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils' +import Styleguide from '@human-connection/styleguide' +import Empty from './Empty.vue' + +const localVue = createLocalVue() +localVue.use(Styleguide) + +describe('Empty.vue', () => { + let propsData, wrapper + + beforeEach(() => { + propsData = {} + }) + + const Wrapper = () => { + return shallowMount(Empty, { propsData, localVue }) + } + + describe('shallowMount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders an image with an alert icon as default', () => { + expect(wrapper.find('img[alt="Empty"]').attributes().src).toBe('/img/empty/alert.svg') + }) + + describe('receives icon prop', () => { + it('renders an image with that icon', () => { + propsData.icon = 'messages' + wrapper = Wrapper() + expect(wrapper.find('img[alt="Empty"]').attributes().src).toBe( + `/img/empty/${propsData.icon}.svg`, + ) + }) + }) + + describe('receives message prop', () => { + it('renders that message', () => { + propsData.message = 'this is a custom message for Empty component' + wrapper = Wrapper() + expect(wrapper.find('.hc-empty-message').text()).toEqual(propsData.message) + }) + }) + + describe('receives margin prop', () => { + it('sets margin to that margin', () => { + propsData.margin = 'xxx-small' + wrapper = Wrapper() + expect(wrapper.find('.hc-empty').attributes().margin).toEqual(propsData.margin) + }) + }) + }) +}) diff --git a/webapp/components/Empty/Empty.story.js b/webapp/components/Empty/Empty.story.js new file mode 100644 index 000000000..44d241df7 --- /dev/null +++ b/webapp/components/Empty/Empty.story.js @@ -0,0 +1,24 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import HcEmpty from '~/components/Empty/Empty' +import helpers from '~/storybook/helpers' + +helpers.init() + +storiesOf('Empty', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add( + 'tasks icon with message', + () => ({ + components: { HcEmpty }, + template: '', + }), + { + notes: "Possible icons include 'messages', 'events', 'alert', 'tasks', 'docs', and 'file'", + }, + ) + .add('default icon, no message', () => ({ + components: { HcEmpty }, + template: '', + })) diff --git a/webapp/components/Empty.vue b/webapp/components/Empty/Empty.vue similarity index 97% rename from webapp/components/Empty.vue rename to webapp/components/Empty/Empty.vue index 8760a6e6f..ea99702b5 100644 --- a/webapp/components/Empty.vue +++ b/webapp/components/Empty/Empty.vue @@ -26,7 +26,7 @@ export default { */ icon: { type: String, - required: true, + default: 'alert', validator: value => { return value.match(/(messages|events|alert|tasks|docs|file)/) }, diff --git a/webapp/components/FilterPosts/FilterPosts.spec.js b/webapp/components/FilterPosts/FilterPosts.spec.js index 1f0ee920d..504112572 100644 --- a/webapp/components/FilterPosts/FilterPosts.spec.js +++ b/webapp/components/FilterPosts/FilterPosts.spec.js @@ -3,6 +3,9 @@ import VTooltip from 'v-tooltip' import Styleguide from '@human-connection/styleguide' import Vuex from 'vuex' import FilterPosts from './FilterPosts.vue' +import locales from '~/locales' +import orderBy from 'lodash/orderBy' + const localVue = createLocalVue() localVue.use(Styleguide) @@ -12,6 +15,8 @@ localVue.use(Vuex) let mutations let getters +const languages = orderBy(locales, 'name') + describe('FilterPosts.vue', () => { let mocks let propsData @@ -20,6 +25,8 @@ describe('FilterPosts.vue', () => { let environmentAndNatureButton let democracyAndPoliticsButton let happyEmotionButton + let englishButton + let spanishButton beforeEach(() => { mocks = { @@ -54,6 +61,8 @@ describe('FilterPosts.vue', () => { 'posts/RESET_CATEGORIES': jest.fn(), 'posts/TOGGLE_CATEGORY': jest.fn(), 'posts/TOGGLE_EMOTION': jest.fn(), + 'posts/TOGGLE_LANGUAGE': jest.fn(), + 'posts/RESET_LANGUAGES': jest.fn(), } getters = { 'posts/isActive': () => false, @@ -64,6 +73,7 @@ describe('FilterPosts.vue', () => { 'posts/filteredCategoryIds': jest.fn(() => []), 'posts/filteredByUsersFollowed': jest.fn(), 'posts/filteredByEmotions': jest.fn(() => []), + 'posts/filteredLanguageCodes': jest.fn(() => []), } const openFilterPosts = () => { const store = new Vuex.Store({ mutations, getters }) @@ -97,6 +107,15 @@ describe('FilterPosts.vue', () => { expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4') }) + it('calls TOGGLE_LANGUAGE when clicked', () => { + const wrapper = openFilterPosts() + englishButton = wrapper + .findAll('button.language-buttons') + .at(languages.findIndex(l => l.code === 'en')) + englishButton.trigger('click') + expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en') + }) + it('sets category button attribute `primary` when corresponding category is filtered', () => { getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9']) const wrapper = openFilterPosts() @@ -104,6 +123,15 @@ describe('FilterPosts.vue', () => { expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary') }) + it('sets language button attribute `primary` when corresponding language is filtered', () => { + getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es']) + const wrapper = openFilterPosts() + spanishButton = wrapper + .findAll('button.language-buttons') + .at(languages.findIndex(l => l.code === 'es')) + expect(spanishButton.attributes().class).toContain('ds-button-primary') + }) + it('sets "filter-by-followed-authors-only" button attribute `primary`', () => { getters['posts/filteredByUsersFollowed'] = jest.fn(() => true) const wrapper = openFilterPosts() diff --git a/webapp/components/FilterPosts/FilterPosts.vue b/webapp/components/FilterPosts/FilterPosts.vue index 58f0794d2..1dd0fa737 100644 --- a/webapp/components/FilterPosts/FilterPosts.vue +++ b/webapp/components/FilterPosts/FilterPosts.vue @@ -14,6 +14,7 @@ + @@ -24,12 +25,14 @@ import Dropdown from '~/components/Dropdown' import { mapGetters } from 'vuex' import CategoriesFilterMenuItems from './CategoriesFilterMenuItems' import GeneralFilterMenuItems from './GeneralFilterMenuItems' +import LanguageFilterMenuItems from './LanguageFilterMenuItems' export default { components: { Dropdown, CategoriesFilterMenuItems, GeneralFilterMenuItems, + LanguageFilterMenuItems, }, props: { placement: { type: String }, diff --git a/webapp/components/FilterPosts/LanguageFilterMenuItems.vue b/webapp/components/FilterPosts/LanguageFilterMenuItems.vue new file mode 100644 index 000000000..258b3acdc --- /dev/null +++ b/webapp/components/FilterPosts/LanguageFilterMenuItems.vue @@ -0,0 +1,104 @@ + + + diff --git a/webapp/components/Hashtag/Hashtag.spec.js b/webapp/components/Hashtag/Hashtag.spec.js index cfb7d7e3f..cfd1a7c2a 100644 --- a/webapp/components/Hashtag/Hashtag.spec.js +++ b/webapp/components/Hashtag/Hashtag.spec.js @@ -1,10 +1,12 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils' +import { config, shallowMount, createLocalVue } from '@vue/test-utils' import Styleguide from '@human-connection/styleguide' import Hashtag from './Hashtag' const localVue = createLocalVue() localVue.use(Styleguide) +config.stubs['nuxt-link'] = '' + describe('Hashtag', () => { let id diff --git a/webapp/components/Hashtag/Hashtag.vue b/webapp/components/Hashtag/Hashtag.vue index a6085fabc..35762c81c 100644 --- a/webapp/components/Hashtag/Hashtag.vue +++ b/webapp/components/Hashtag/Hashtag.vue @@ -1,5 +1,7 @@ diff --git a/webapp/components/notifications/Notification/Notification.spec.js b/webapp/components/Notification/Notification.spec.js similarity index 100% rename from webapp/components/notifications/Notification/Notification.spec.js rename to webapp/components/Notification/Notification.spec.js diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/Notification/Notification.vue similarity index 95% rename from webapp/components/notifications/Notification/Notification.vue rename to webapp/components/Notification/Notification.vue index dc9383c85..446c5321a 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/Notification/Notification.vue @@ -69,10 +69,9 @@ export default { } - diff --git a/webapp/components/NotificationsTable/NotificationsTable.spec.js b/webapp/components/NotificationsTable/NotificationsTable.spec.js new file mode 100644 index 000000000..59b8953e9 --- /dev/null +++ b/webapp/components/NotificationsTable/NotificationsTable.spec.js @@ -0,0 +1,174 @@ +import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils' +import Styleguide from '@human-connection/styleguide' +import VTooltip from 'v-tooltip' +import Vuex from 'vuex' +import NotificationsTable from './NotificationsTable' +import Filters from '~/plugins/vue-filters' +import { notifications } from '~/components/utils/Notifications' +const localVue = createLocalVue() + +localVue.use(Styleguide) +localVue.use(Filters) +localVue.use(VTooltip) +localVue.use(Vuex) +localVue.filter('truncate', string => string) + +config.stubs['client-only'] = '' + +describe('NotificationsTable.vue', () => { + let wrapper, mocks, propsData, stubs + const postNotification = notifications[0] + const commentNotification = notifications[1] + + beforeEach(() => { + mocks = { + $t: jest.fn(string => string), + } + stubs = { + NuxtLink: RouterLinkStub, + } + propsData = {} + }) + + describe('mount', () => { + const Wrapper = () => { + const store = new Vuex.Store({ + getters: { + 'auth/isModerator': () => false, + 'auth/user': () => { + return {} + }, + }, + }) + return mount(NotificationsTable, { + propsData, + mocks, + localVue, + store, + stubs, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('no notifications', () => { + it('renders HcEmpty component', () => { + expect(wrapper.find('.hc-empty').exists()).toBe(true) + }) + }) + + describe('given notifications', () => { + beforeEach(() => { + propsData.notifications = notifications + wrapper = Wrapper() + }) + + it('renders a table', () => { + expect(wrapper.find('.ds-table').exists()).toBe(true) + }) + + describe('renders 4 columns', () => { + it('for icon', () => { + expect(wrapper.vm.fields.icon).toBeTruthy() + }) + + it('for user', () => { + expect(wrapper.vm.fields.user).toBeTruthy() + }) + + it('for post', () => { + expect(wrapper.vm.fields.post).toBeTruthy() + }) + + it('for content', () => { + expect(wrapper.vm.fields.content).toBeTruthy() + }) + }) + + describe('Post', () => { + let firstRowNotification + beforeEach(() => { + firstRowNotification = wrapper.findAll('tbody tr').at(0) + }) + + it('renders the author', () => { + const username = firstRowNotification.find('.username') + expect(username.text()).toEqual(postNotification.from.author.name) + }) + + it('renders the reason for the notification', () => { + const dsTexts = firstRowNotification.findAll('.ds-text') + const reason = dsTexts.filter( + element => element.text() === 'notifications.reason.mentioned_in_post', + ) + expect(reason.exists()).toBe(true) + }) + + it('renders a link to the Post', () => { + const postLink = firstRowNotification.find('a.notification-mention-post') + expect(postLink.text()).toEqual(postNotification.from.title) + }) + + it("renders the Post's content", () => { + const boldTags = firstRowNotification.findAll('b') + const content = boldTags.filter( + element => element.text() === postNotification.from.contentExcerpt, + ) + expect(content.exists()).toBe(true) + }) + }) + + describe('Comment', () => { + let secondRowNotification + beforeEach(() => { + secondRowNotification = wrapper.findAll('tbody tr').at(1) + }) + + it('renders the author', () => { + const username = secondRowNotification.find('.username') + expect(username.text()).toEqual(commentNotification.from.author.name) + }) + + it('renders the reason for the notification', () => { + const dsTexts = secondRowNotification.findAll('.ds-text') + const reason = dsTexts.filter( + element => element.text() === 'notifications.reason.mentioned_in_comment', + ) + expect(reason.exists()).toBe(true) + }) + + it('renders a link to the Post', () => { + const postLink = secondRowNotification.find('a.notification-mention-post') + expect(postLink.text()).toEqual(commentNotification.from.post.title) + }) + + it("renders the Post's content", () => { + const boldTags = secondRowNotification.findAll('b') + const content = boldTags.filter( + element => element.text() === commentNotification.from.contentExcerpt, + ) + expect(content.exists()).toBe(true) + }) + }) + + describe('unread status', () => { + it('does not have class `notification-status`', () => { + expect(wrapper.find('.notification-status').exists()).toBe(false) + }) + + it('clicking on a Post link emits `markNotificationAsRead`', () => { + wrapper.find('a.notification-mention-post').trigger('click') + expect(wrapper.emitted().markNotificationAsRead[0][0]).toEqual(postNotification.from.id) + }) + + it('adds class `notification-status` when read is true', () => { + postNotification.read = true + wrapper = Wrapper() + expect(wrapper.find('.notification-status').exists()).toBe(true) + }) + }) + }) + }) +}) diff --git a/webapp/components/NotificationsTable/NotificationsTable.story.js b/webapp/components/NotificationsTable/NotificationsTable.story.js new file mode 100644 index 000000000..d58f4ff73 --- /dev/null +++ b/webapp/components/NotificationsTable/NotificationsTable.story.js @@ -0,0 +1,86 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import { action } from '@storybook/addon-actions' +import NotificationsTable from '~/components/NotificationsTable/NotificationsTable' +import helpers from '~/storybook/helpers' +import { post } from '~/components/PostCard/PostCard.story.js' +import { user } from '~/components/User/User.story.js' + +helpers.init() +export const notifications = [ + { + read: true, + reason: 'mentioned_in_post', + createdAt: '2019-10-29T15:36:02.106Z', + from: { + __typename: 'Post', + ...post, + }, + __typename: 'NOTIFIED', + index: 9, + }, + { + read: false, + reason: 'commented_on_post', + createdAt: '2019-10-29T15:38:25.199Z', + from: { + __typename: 'Comment', + id: 'b6b38937-3efc-4d5e-b12c-549e4d6551a5', + createdAt: '2019-10-29T15:38:25.184Z', + updatedAt: '2019-10-29T15:38:25.184Z', + disabled: false, + deleted: false, + content: + '

@peter-lustig

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Turpis egestas pretium aenean pharetra magna ac placerat. Tempor id eu nisl nunc mi ipsum faucibus vitae. Nibh praesent tristique magna sit amet purus gravida quis blandit. Magna eget est lorem ipsum dolor. In fermentum posuere urna nec. Eleifend donec pretium vulputate sapien nec sagittis aliquam. Augue interdum velit euismod in pellentesque. Id diam maecenas ultricies mi eget mauris pharetra. Donec pretium vulputate sapien nec. Dolor morbi non arcu risus quis varius quam quisque. Blandit turpis cursus in hac habitasse. Est ultricies integer quis auctor elit sed vulputate mi sit. Nunc consequat interdum varius sit amet mattis vulputate enim. Semper feugiat nibh sed pulvinar. Eget felis eget nunc lobortis mattis aliquam. Ultrices vitae auctor eu augue. Tellus molestie nunc non blandit massa enim nec dui. Pharetra massa massa ultricies mi quis hendrerit dolor. Nisl suscipit adipiscing bibendum est ultricies integer.

', + contentExcerpt: + '

@peter-lustig

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Turpis egestas pretium aenean pharetra …

', + ...post, + author: user, + }, + __typename: 'NOTIFIED', + index: 1, + }, + { + read: false, + reason: 'mentioned_in_comment', + createdAt: '2019-10-29T15:38:13.422Z', + from: { + __typename: 'Comment', + id: 'b91f4d4d-b178-4e42-9764-7fbcbf097f4c', + createdAt: '2019-10-29T15:38:13.41Z', + updatedAt: '2019-10-29T15:38:13.41Z', + disabled: false, + deleted: false, + content: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Turpis egestas pretium aenean pharetra magna ac placerat. Tempor id eu nisl nunc mi ipsum faucibus vitae. Nibh praesent tristique magna sit amet purus gravida quis blandit. Magna eget est lorem ipsum dolor. In fermentum posuere urna nec. Eleifend donec pretium vulputate sapien nec sagittis aliquam. Augue interdum velit euismod in pellentesque. Id diam maecenas ultricies mi eget mauris pharetra. Donec pretium vulputate sapien nec. Dolor morbi non arcu risus quis varius quam quisque. Blandit turpis cursus in hac habitasse. Est ultricies integer quis auctor elit sed vulputate mi sit. Nunc consequat interdum varius sit amet mattis vulputate enim. Semper feugiat nibh sed pulvinar. Eget felis eget nunc lobortis mattis aliquam. Ultrices vitae auctor eu augue. Tellus molestie nunc non blandit massa enim nec dui. Pharetra massa massa ultricies mi quis hendrerit dolor. Nisl suscipit adipiscing bibendum est ultricies integer.

@peter-lustig

', + contentExcerpt: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Turpis egestas pretium aenean pharetra magna ac …

', + ...post, + author: user, + }, + __typename: 'NOTIFIED', + index: 2, + }, +] +storiesOf('NotificationsTable', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('with notifications', () => ({ + components: { NotificationsTable }, + store: helpers.store, + data: () => ({ + notifications, + }), + methods: { + markNotificationAsRead: action('markNotificationAsRead'), + }, + template: ``, + })) + .add('without notifications', () => ({ + components: { NotificationsTable }, + store: helpers.store, + template: ``, + })) diff --git a/webapp/components/NotificationsTable/NotificationsTable.vue b/webapp/components/NotificationsTable/NotificationsTable.vue new file mode 100644 index 000000000..6a9d9b033 --- /dev/null +++ b/webapp/components/NotificationsTable/NotificationsTable.vue @@ -0,0 +1,110 @@ + + + diff --git a/webapp/components/Paginate/Paginate.spec.js b/webapp/components/Paginate/Paginate.spec.js new file mode 100644 index 000000000..034d33301 --- /dev/null +++ b/webapp/components/Paginate/Paginate.spec.js @@ -0,0 +1,72 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import Styleguide from '@human-connection/styleguide' +import Paginate from './Paginate' + +const localVue = createLocalVue() + +localVue.use(Styleguide) + +describe('Paginate.vue', () => { + let propsData, wrapper, Wrapper, nextButton, backButton + + beforeEach(() => { + propsData = {} + }) + + Wrapper = () => { + return mount(Paginate, { propsData, localVue }) + } + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('next button', () => { + beforeEach(() => { + propsData.hasNext = true + wrapper = Wrapper() + nextButton = wrapper.findAll('.ds-button').at(0) + }) + + it('is disabled by default', () => { + propsData = {} + wrapper = Wrapper() + nextButton = wrapper.findAll('.ds-button').at(0) + expect(nextButton.attributes().disabled).toEqual('disabled') + }) + + it('is not disabled if hasNext is true', () => { + expect(nextButton.attributes().disabled).toBeUndefined() + }) + + it('emits next when clicked', async () => { + await nextButton.trigger('click') + expect(wrapper.emitted().next).toHaveLength(1) + }) + }) + + describe('back button', () => { + beforeEach(() => { + propsData.hasPrevious = true + wrapper = Wrapper() + backButton = wrapper.findAll('.ds-button').at(1) + }) + + it('is disabled by default', () => { + propsData = {} + wrapper = Wrapper() + backButton = wrapper.findAll('.ds-button').at(1) + expect(backButton.attributes().disabled).toEqual('disabled') + }) + + it('is not disabled if hasPrevious is true', () => { + expect(backButton.attributes().disabled).toBeUndefined() + }) + + it('emits back when clicked', async () => { + await backButton.trigger('click') + expect(wrapper.emitted().back).toHaveLength(1) + }) + }) + }) +}) diff --git a/webapp/components/Paginate/Paginate.story.js b/webapp/components/Paginate/Paginate.story.js new file mode 100644 index 000000000..6efc9353f --- /dev/null +++ b/webapp/components/Paginate/Paginate.story.js @@ -0,0 +1,28 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import { action } from '@storybook/addon-actions' +import Paginate from '~/components/Paginate/Paginate' +import helpers from '~/storybook/helpers' + +helpers.init() + +storiesOf('Paginate', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('basic pagination', () => ({ + components: { Paginate }, + data: () => ({ + hasNext: true, + hasPrevious: false, + }), + methods: { + back: action('back'), + next: action('next'), + }, + template: ``, + })) diff --git a/webapp/components/Paginate/Paginate.vue b/webapp/components/Paginate/Paginate.vue new file mode 100644 index 000000000..aa1455d19 --- /dev/null +++ b/webapp/components/Paginate/Paginate.vue @@ -0,0 +1,26 @@ + + diff --git a/webapp/components/PasswordReset/Request.spec.js b/webapp/components/PasswordReset/Request.spec.js index 594d6628d..4a6dbde9f 100644 --- a/webapp/components/PasswordReset/Request.spec.js +++ b/webapp/components/PasswordReset/Request.spec.js @@ -84,5 +84,18 @@ describe('Request', () => { }) }) }) + + describe('capital letters in a gmail address', () => { + beforeEach(async () => { + wrapper = Wrapper() + wrapper.find('input#email').setValue('mAiL@gmail.com') + await wrapper.find('form').trigger('submit') + }) + + it('normalizes email to lower case letters', () => { + const expected = expect.objectContaining({ variables: { email: 'mail@gmail.com' } }) + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) + }) + }) }) }) diff --git a/webapp/components/PasswordReset/Request.vue b/webapp/components/PasswordReset/Request.vue index bcececcca..1cf575574 100644 --- a/webapp/components/PasswordReset/Request.vue +++ b/webapp/components/PasswordReset/Request.vue @@ -46,6 +46,7 @@ + + diff --git a/webapp/components/Registration/Signup.spec.js b/webapp/components/Registration/Signup.spec.js index b6b83a82e..b78a18541 100644 --- a/webapp/components/Registration/Signup.spec.js +++ b/webapp/components/Registration/Signup.spec.js @@ -48,7 +48,7 @@ describe('Signup', () => { describe('submit', () => { beforeEach(async () => { wrapper = Wrapper() - wrapper.find('input#email').setValue('mail@example.org') + wrapper.find('input#email').setValue('mAIL@exAMPLE.org') await wrapper.find('form').trigger('submit') }) @@ -59,7 +59,7 @@ describe('Signup', () => { it('delivers email to backend', () => { const expected = expect.objectContaining({ - variables: { email: 'mail@example.org', token: null }, + variables: { email: 'mAIL@exAMPLE.org', token: null }, }) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) diff --git a/webapp/components/Registration/Signup.vue b/webapp/components/Registration/Signup.vue index 00309b1c6..341e77839 100644 --- a/webapp/components/Registration/Signup.vue +++ b/webapp/components/Registration/Signup.vue @@ -1,5 +1,5 @@ @@ -139,22 +98,20 @@ import { mapGetters, mapActions } from 'vuex' import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch' import SearchInput from '~/components/SearchInput.vue' import Modal from '~/components/Modal' -import NotificationMenu from '~/components/notifications/NotificationMenu/NotificationMenu' -import Dropdown from '~/components/Dropdown' -import HcAvatar from '~/components/Avatar/Avatar.vue' +import NotificationMenu from '~/components/NotificationMenu/NotificationMenu' import seo from '~/mixins/seo' import FilterPosts from '~/components/FilterPosts/FilterPosts.vue' import CategoryQuery from '~/graphql/CategoryQuery.js' import PageFooter from '~/components/PageFooter/PageFooter' +import AvatarMenu from '~/components/AvatarMenu/AvatarMenu' export default { components: { - Dropdown, LocaleSwitch, SearchInput, Modal, NotificationMenu, - HcAvatar, + AvatarMenu, FilterPosts, PageFooter, }, @@ -168,49 +125,10 @@ export default { }, computed: { ...mapGetters({ - user: 'auth/user', isLoggedIn: 'auth/isLoggedIn', - isModerator: 'auth/isModerator', - isAdmin: 'auth/isAdmin', quickSearchResults: 'search/quickResults', quickSearchPending: 'search/quickPending', }), - userName() { - const { name } = this.user || {} - return name || this.$t('profile.userAnonym') - }, - routes() { - if (!this.user.slug) { - return [] - } - let routes = [ - { - name: this.$t('profile.name'), - path: `/profile/${this.user.slug}`, - icon: 'user', - }, - { - name: this.$t('settings.name'), - path: `/settings`, - icon: 'cogs', - }, - ] - if (this.isModerator) { - routes.push({ - name: this.$t('moderation.name'), - path: `/moderation`, - icon: 'balance-scale', - }) - } - if (this.isAdmin) { - routes.push({ - name: this.$t('admin.name'), - path: `/admin`, - icon: 'shield', - }) - } - return routes - }, showFilterPostsDropdown() { const [firstRoute] = this.$route.matched return firstRoute && firstRoute.name === 'index' @@ -225,6 +143,7 @@ export default { ...mapActions({ quickSearchClear: 'search/quickClear', quickSearch: 'search/quickSearch', + refreshPosts: 'posts/refreshPosts', }), goToPost(item) { this.$nextTick(() => { @@ -234,13 +153,6 @@ export default { }) }) }, - matcher(url, route) { - if (url.indexOf('/profile') === 0) { - // do only match own profile - return this.$route.path === url - } - return this.$route.path.indexOf(url) === 0 - }, toggleMobileMenuView() { this.toggleMobileMenu = !this.toggleMobileMenu }, @@ -284,45 +196,6 @@ export default { .main-navigation-right .desktop-view { float: right; } -.avatar-menu { - margin: 2px 0px 0px 5px; -} -.avatar-menu-trigger { - user-select: none; - display: flex; - align-items: center; - padding-left: $space-xx-small; -} -.avatar-menu-popover { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - hr { - color: $color-neutral-90; - background-color: $color-neutral-90; - } - .logout-link { - margin-left: -$space-small; - margin-right: -$space-small; - margin-top: -$space-xxx-small; - margin-bottom: -$space-x-small; - padding: $space-x-small $space-small; - // subtract menu border with from padding - padding-left: $space-small - 2; - color: $text-color-base; - &:hover { - color: $text-color-link-active; - } - } - nav { - margin-left: -$space-small; - margin-right: -$space-small; - margin-top: -$space-xx-small; - margin-bottom: -$space-xx-small; - a { - padding-left: 12px; - } - } -} @media only screen and (min-width: 960px) { .mobile-hamburger-menu { display: none; diff --git a/webapp/locales/de.json b/webapp/locales/de.json index a06aed797..262fe31aa 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -62,6 +62,11 @@ } } }, + "donations": { + "donations-for": "Spenden für", + "donate-now": "Jetzt spenden", + "amount-of-total": "{amount} von {total} € erreicht" + }, "maintenance": { "title": "Human Connection befindet sich in der Wartung", "explanation": "Zurzeit führen wir einige geplante Wartungsarbeiten durch, bitte versuch es später erneut.", @@ -86,6 +91,10 @@ }, "followers": { "label": "Benutzern, denen ich folge" + }, + "language": { + "header": "Sprachen", + "all": "Alle" } }, "site": { @@ -171,7 +180,18 @@ "mentioned_in_comment": "Hat dich in einem Kommentar erwähnt …", "commented_on_post": "Hat deinen Beitrag kommentiert …" }, - "comment": "Kommentar" + "comment": "Kommentar", + "title": "Benachrichtigungen", + "pageLink": "Alle Benachrichtigungen", + "post": "Beitrag", + "user": "Benutzer", + "content": "Inhalt", + "filterLabel": { + "all": "Alle", + "read": "Gelesen ", + "unread": "Ungelesen" + }, + "empty": "Sorry, du hast im Moment keine Benachrichtigungen." }, "search": { "placeholder": "Suchen", @@ -365,6 +385,12 @@ "name": "Benutzer einladen", "title": "Leute einladen", "description": "Einladungen sind ein wunderbarer Weg, deine Freund in deinem Netzwerk zu haben …" + }, + "donations": { + "name": "Spendeninfo", + "goal": "Monatlich benötigte Spenden", + "progress": "Bereits gesammelte Spenden", + "successfulUpdate": "Spenden-Info erfolgreich aktualisiert!" } }, "post": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index ca06c1313..752359306 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -63,6 +63,11 @@ } } }, + "donations": { + "donations-for": "Donations for", + "donate-now": "Donate now", + "amount-of-total": "{amount} of {total} € collected" + }, "maintenance": { "title": "Human Connection is under maintenance", "explanation": "At the moment we are doing some scheduled maintenance, please try again later.", @@ -87,6 +92,10 @@ }, "followers": { "label": "Users I follow" + }, + "language": { + "header": "Languages", + "all": "All" } }, "site": { @@ -172,7 +181,18 @@ "mentioned_in_comment": "Mentioned you in a comment …", "commented_on_post": "Commented on your post …" }, - "comment": "Comment" + "comment": "Comment", + "title": "Notifications", + "pageLink": "All notifications", + "post": "Post", + "user": "User", + "content": "Content", + "filterLabel": { + "all": "All", + "read": "Read", + "unread": "Unread" + }, + "empty": "Sorry, you don't have any notifications at the moment." }, "search": { "placeholder": "Search", @@ -366,6 +386,12 @@ "name": "Invite users", "title": "Invite people", "description": "Invitations are a wonderful way to have your friends in your network …" + }, + "donations": { + "name": "Donations info", + "goal": "Monthly donations needed", + "progress": "Donations collected so far", + "successfulUpdate": "Donations info updated successfully!" } }, "post": { diff --git a/webapp/locales/index.js b/webapp/locales/index.js index c4a21c724..28cbd27d0 100644 --- a/webapp/locales/index.js +++ b/webapp/locales/index.js @@ -1,4 +1,4 @@ -import { enUS, de, nl, fr, es, it, pt, pl } from 'date-fns/locale' +import { enUS, de, nl, fr, es, it, pt, pl, ru } from 'date-fns/locale' import find from 'lodash/find' const locales = [ @@ -58,6 +58,13 @@ const locales = [ enabled: true, dateFnsLocale: pl, }, + { + name: 'Русский', + code: 'ru', + iso: 'ru-RU', + enabled: true, + dateFnsLocale: ru, + }, ] export default locales diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json index 809302ff7..76560aba9 100644 --- a/webapp/locales/pt.json +++ b/webapp/locales/pt.json @@ -1,10 +1,106 @@ { + "components": { + "password-reset": { + "request": { + "title": "Redefinir sua senha", + "form": { + "description": "Um e-mail de redefinição de senha será enviado ao endereço de e-mail fornecido.", + "submit": "Solicitar e-mail", + "submitted": "Um e-mail com mais instruções foi enviado para {email}" + } + }, + "change-password": { + "success": "A alteração da sua senha foi bem-sucedida!", + "error": "A alteração da sua senha falhou. Talvez o código de segurança não estava correto?", + "help": "Em caso de problemas, sinta-se à vontade para pedir ajuda, enviando um e-mail para:" + } + }, + "enter-nonce": { + "form": { + "nonce": "Digite seu código", + "description": "Abra a sua caixa de entrada e digite o código que lhe enviamos.", + "next": "Continue", + "validations": { + "length": "deve ter 6 caracteres" + } + } + }, + "registration": { + "signup": { + "unavailable": "Infelizmente, o registo público para usuário não está disponível neste servidor.", + "title": "Junte-se à Human Connection!", + "form": { + "description": "Para começar, digite seu endereço de e-mail:", + "terms-and-condition": "Eu concordo com os Termos e condições.", + "data-privacy": "Eu li e entendi o Política de Privacidade ", + "minimum-age": "Tenho 18 anos ou mais.", + "invitation-code": "O seu código de convite é: {code}", + "errors": { + "email-exists": "Já existe uma conta de usuário com este endereço de e-mail!", + "invalid-invitation-token": "Parece que o convite já foi usado. Os links para convites só podem ser usados uma vez." + }, + "submit": "Criar uma conta", + "success": "Um e-mail com um link para completar o seu registo foi enviado para {email}" + } + }, + "create-user-account": { + "title": "Criar uma conta de usuário", + "success": "A sua conta foi criada!", + "error": "Nenhuma conta de usuário pode ser criada!", + "help": " Talvez a confirmação tenha sido inválida? Em caso de problemas, sinta-se à vontade para pedir ajuda, enviando um e-mail para:" + } + } + }, + "store": { + "posts": { + "orderBy": { + "newest": { + "label": "Mais recentes" + }, + "oldest": { + "label": "Mais antigos" + } + } + } + }, + "maintenance": { + "title": "Human Connection está em manutenção", + "explanation": "No momento estamos em manutenção, por favor tente novamente mais tarde.", + "questions": "Qualquer dúvida, envie um e-mail para" + }, + "index": { + "no-results": "Nenhuma contribuição encontrada.", + "change-filter-settings": "Altere suas configurações de filtro para obter mais resultados." + }, + "filter-menu": { + "title": "Sua bolha de filtro", + "hashtag-search": "Procurando por #{hashtag}", + "clearSearch": "Limpar pesquisa" + }, + "filter-posts": { + "categories": { + "header": "Categorias de Conteúdo", + "all": "Todos" + }, + "general": { + "header": "Filtrar por …" + }, + "followers": { + "label": "Usuários que eu sigo" + }, + "language": { + "header": "Idiomas", + "all": "Todos" + } + }, "site": { + "thanks": "Obrigado(a)!", + "error-occurred": "Ocorreu um erro.", "made": "Feito com ❤", "imprint": "Impressão", - "termsAc": "Termos e Condições", + "termsAndConditions": "Termos e Condições", "data-privacy": "Proteção de Dados", - "changelog": "Mudanças e Histórico", + "changelog": "Mudanças", "contact": "Contato", "tribunal": "tribunal de registo", "register": "número de registo", @@ -14,7 +110,8 @@ "bank": "conta bancária", "germany": "Alemanha", "code-of-conduct": "Codigo de Conduto", - "login": "Voltar para o Login" + "back-to-login": "Voltar para o Login", + "faq": "FAQ" }, "login": { "copy": "Se você já tem uma conta no Human Connection, por favor faça o login.", @@ -22,15 +119,30 @@ "logout": "Sair", "email": "Seu email", "password": "Sua senha", + "forgotPassword": "Esqueceu a sua senha?", + "no-account": "Ainda não tem uma conta?", + "register": "Cadastrar-se", "moreInfo": "O que é o Human Connection?", + "moreInfoURL": "https://human-connection.org/en/", "hello": "Olá", - "forgotPassword": "Esqueceu a sua senha?" + "success": "Você está conectado!", + "failure": "Endereço de e-mail ou senha incorretos." }, - "password-reset": { - "form": { - "description": "Um e-mail de redefinição de senha será enviado para o endereço de e-mail fornecido.", - "submit": "Solicitar email", - "submitted": "Um e-mail com mais instruções foi enviado para {email}" + "editor": { + "placeholder": " Escreva algo inspirador…", + "mention": { + "noUsersFound": "Nenhum usuário encontrado" + }, + "hashtag": { + "noHashtagsFound": "Nenhuma hashtag encontrada", + "addHashtag": "Nova hashtag", + "addLetter": "Digite uma letra" + }, + "embed": { + "data_privacy_warning": "Aviso de Privacidade de Dados!", + "data_privacy_info": "Seus dados ainda não foram compartilhados com terceiros. Se continuar assistindo este vídeo, o seguinte fornecedor irá provavelmente recolher dados do utilizador:", + "play_now": "Assista agora", + "always_allow": "Sempre permita conteúdo incorporado por provedores de terceiros (esta configuração pode ser alterada a qualquer momento)" } }, "profile": { @@ -39,36 +151,174 @@ "follow": "Seguir", "followers": "Seguidores", "following": "Seguindo", - "shouted": "Aclamou", + "shouted": "Recomendou", "commented": "Comentou", - "userAnonym": "Anonymous" + "userAnonym": "Anônimo", + "socialMedia": "Onde mais posso encontrar", + "network": { + "title": "Rede", + "following": "está seguindo:", + "followingNobody": "não segue ninguém.", + "followedBy": "é seguido por:", + "followedByNobody": "não é seguido por ninguém.", + "andMore": "e {number} mais …" + }, + "invites": { + "title": "Convidar alguém para Human Connection!", + "description": "Digite o endereço de e-mail para o convite.", + "emailPlaceholder": "E-mail para convidar" + } + }, + "notifications": { + "reason": { + "mentioned_in_post": "Mencionou você em uma publicação …", + "mentioned_in_comment": "Mencionou você em um comentário …", + "commented_on_post": "Comentou na sua publicação …" + }, + "comment": "Comentário" + }, + "search": { + "placeholder": "Pesquisar", + "hint": "O que você está pesquisando??", + "failed": "Nada foi encontrado" + }, + "notifications": { + "reason": { + "mentioned_in_post": "Mencinou você em um post …", + "mentioned_in_comment": "Mentionou você em um comentário …", + "commented_on_post": "Comentou no seu post …" + }, + "comment": "Comentário", + "title": "Notificações", + "pageLink": "Todas as notificações", + "post": "Post", + "user": "Usuário", + "content": "Conteúdo", + "filterLabel": { + "all": "Todos", + "read": "Lido", + "unread": "Não lido" + }, + "empty": "Desculpe, não tem nenhuma notificação neste momento." }, "settings": { "name": "Configurações", "data": { "name": "Seus dados", - "labelName": "Seu nome", - "namePlaceholder": "Anonymous", - "labelCity": "Sua cidade ou estado", - "labelBio": "Sobre você" + "labelName": "Seu Nome", + "labelSlug": "Seu nome de usuário exclusivo", + "namePlaceholder": "Femanon Funny", + "labelCity": "Sua cidade ou região", + "labelBio": "Sobre você", + "success": "Seus dados foram atualizados com sucesso!" + }, + "email": { + "validation": { + "same-email": "Este é o seu endereço de e-mail atual" + }, + "name": "Seu email", + "labelEmail": "Alterar o seu endereço de e-mail", + "labelNewEmail": "Novo endereço de e-mail", + "labelNonce": "Digite o seu código", + "success": "Um novo endereço de e-mail foi registrado.", + "submitted": "Um e-mail para verificar o seu endereço foi enviado para {email}.", + "change-successful": "O seu endereço de e-mail foi alterado com sucesso.", + "verification-error": { + "message": "O seu e-mail não pode ser alterado.", + "explanation": "Isto pode ter diferentes causas:", + "reason": { + "invalid-nonce": "O código de confirmação esta inválido?", + "no-email-request": "Você tem certeza de que solicitou uma alteração no seu endereço de e-mail?" + }, + "support": "Se o problema persistir, por favor contacte-nos por e-mail" + } + }, + "validation": { + "slug": { + "regex": "Os caracteres permitidos são apenas letras minúsculas, números, sublinhados e hífens.", + "alreadyTaken": "Este nome de usuário já está registrado." + } }, "security": { - "name": "Segurança" + "name": "Segurança", + "change-password": { + "button": "Alterar senha", + "success": "Senha alterada com sucesso!", + "label-old-password": "Sua senha antiga", + "label-new-password": "Sua nova senha", + "label-new-password-confirm": "Confirme sua nova senha", + "message-old-password-required": "Digite sua senha antiga", + "message-new-password-required": "Digite uma nova senha", + "message-new-password-confirm-required": "Confirme sua nova senha", + "message-new-password-missmatch": "Digite a mesma senha novamente", + "passwordSecurity": "Segurança da senha", + "passwordStrength0": "Senha muito insegura", + "passwordStrength1": "Senha insegura", + "passwordStrength2": "Senha medíocre", + "passwordStrength3": "Senha forte", + "passwordStrength4": "Senha muito forte" + } }, "invites": { "name": "Convites" }, "download": { - "name": "Baixar dados" + "name": "Baixar Dados" }, - "delete": { - "name": "Apagar conta" + "deleteUserAccount": { + "name": "Deletar dados", + "contributionsCount": "Deletar minhas {count} publicações", + "commentedCount": "Deletar meus {count} comentários", + "accountDescription": "Esteja ciente de que o suas Publicações e Comentários são importantes para a nossa comunidade. Se você ainda optar por excluí-los, você tem que marcá-los abaixo.", + "accountWarning": "Você NÃO PODE GERENCIAR e NÃO PODE RECUPERAR sua conta, Publicações, ou Comentários após excluir sua conta!", + "success": "Conta eliminada com sucesso!", + "pleaseConfirm": "Ação destrutiva! Digitar {confirm} para confirmar" + }, + "embeds": { + "name": "Fornecedores de terceiros", + "info-description": "Se você concordar, as publicações da seguinte lista de provedores incluirão automaticamente código de terceiros de outros provedores (terceiros) na forma de vídeos, imagens ou texto incorporados.", + "description": "Você concordou que nas contribuições da seguinte lista de provedores, o código estrangeiro de outros provedores (terceiros) na forma de vídeos, imagens ou texto incorporados são automaticamente incorporados", + "statustext": "No momento, isto é uma incorporação automática:", + "statuschange": "Modificar configuração", + "false": "Desligado", + "true": "Admitido", + "button-tofalse": "desligar", + "button-totrue": "permitir permanentemente", + "third-party-false": "Integra automaticamente no serviços de terceiros.", + "third-party-true": "A inclusão de serviços de terceiros é permanentemente permitida e armazenadas para sessões futuras." }, "organizations": { "name": "Minhas Organizações" }, "languages": { "name": "Linguagens" + }, + "social-media": { + "name": "Mídias sociais", + "placeholder": "Sua url de mídia social", + "requireUnique": "Você já adicionou esta url", + "submit": "Adicionar link", + "successAdd": "Mídias sociais adicionadas. Perfil de usuário atualizado!", + "successDelete": "Mídias sociais removidas. Perfil de usuário atualizado!" + }, + "blocked-users": { + "name": "Usuários bloqueados", + "explanation": { + "intro": "Se outro usuário foi bloqueado por você, isto é o que acontece:", + "your-perspective": "As mensagens da pessoa bloqueada não aparecerão mais no seu feed de notícias.", + "their-perspective": "Vice versa: A pessoa bloqueada também não verá mais suas mensagens em seu feed de notícias.", + "search": "Publicações de pessoas bloqueadas desaparecem dos resultados da sua pesquisa.", + "notifications": "Usuários bloqueados não receberão mais notificações se forem mencionados em suas mensagens.", + "closing": "Isso deve ser suficiente por enquanto para que os usuários bloqueados não possam mais incomodá-lo." + }, + "columns": { + "name": "Nome", + "slug": "Slug" + }, + "empty": "Até agora, você não bloqueou ninguém.", + "how-to": "Você pode bloquear outros usuários em suas páginas de perfil através do menu de conteúdo.", + "block": "Bloquear usuário", + "unblock": "Desbloquear usuário" } }, "admin": { @@ -76,20 +326,34 @@ "dashboard": { "name": "Painel de controle", "users": "Usuários", - "posts": "Postagens", + "posts": "Publicações", "comments": "Comentários", "notifications": "Notificações", "organizations": "Organizações", "projects": "Projetos", "invites": "Convites", "follows": "Segue", - "shouts": "Aclamações" + "shouts": "Recomendar" }, "organizations": { "name": "Organizações" }, "users": { - "name": "Usuários" + "name": "Usuários", + "form": { + "placeholder": "e-mail, nome ou descrição" + }, + "table": { + "columns": { + "number": "N.º", + "name": "Nome", + "email": "E-mail", + "slug": "Slug", + "role": "Função", + "createdAt": "Criado em" + } + }, + "empty": "Nenhum usuário encontrado" }, "pages": { "name": "Páginas" @@ -100,44 +364,87 @@ "categories": { "name": "Categorias", "categoryName": "Nome", - "postCount": "Postagens" + "postCount": "Publicações" }, - "tags": { - "name": "Etiquetas", + "hashtags": { + "name": "Hashtags", + "number": "Não.", + "nameOfHashtag": "Nome", "tagCountUnique": "Usuários", - "tagCount": "Postagens" + "tagCount": "Publicações" }, "settings": { "name": "Configurações" + }, + "invites": { + "name": "Convidar usuários", + "title": "Convidar pessoas", + "description": "Convites são uma maneira maravilhosa de ter seus amigos em sua rede …" + }, + "donations": { + "name": "Informações sobre Doações", + "goal": "Doações mensais necessárias", + "progress": "Doações arrecadadas até o momento", + "successfulUpdate": "Informações sobre doações atualizadas com sucesso!" } }, "post": { - "name": "Postar", + "name": "Publicação", + "pinned": "Anúncio", "moreInfo": { - "name": "Mais informações" + "name": "Mais informações", + "title": "Mais informações", + "description": "Aqui você pode encontrar mais informações sobre este tópico.", + "titleOfCategoriesSection": "Categorias", + "titleOfHashtagsSection": "Hashtags", + "titleOfRelatedContributionsSection": "Publicações relacionadas" }, "takeAction": { - "name": "Tomar uma ação" + "name": "Tomar providências" + }, + "menu": { + "edit": "Editar publicação", + "delete": "Excluir publicação" }, "comment": { - "submit": "Commentar" + "submit": "Comentário", + "submitted": "Comentário Enviado", + "updated": "Alterações salvas" + }, + "edited": "editado" + }, + "comment": { + "content": { + "unavailable-placeholder": "… este comentário não está mais disponível" + }, + "menu": { + "edit": "Editar Comentário", + "delete": "Apagar Comentário", + "pin": "Fixar publicação", + "pinnedSuccessfully": "Publicação fixada com successo!", + "unpin": "Desafixar publicação", + "unpinnedSuccessfully": "Publicação desafixada com successo!" + }, + "show": { + "more": "mostrar mais", + "less": "mostrar menos" }, "edited": "editado" }, "quotes": { "african": { - "quote": "Muitas pessoas pequenas, em muitos lugares pequenos, fazem muitas coisas pequenas, que podem mudar a face do mundo.", + "quote": "Muitas pessoas pequenas em muitos lugares pequenos fazem muitas coisas pequenas, que podem alterar a face do mundo.", "author": "Provérbio africano" } }, "common": { - "post": "Postagem ::: Postagens", + "post": "Publicação::: Publicações", "comment": "Comentário ::: Comentários", "letsTalk": "Vamos Conversar", "versus": "Contra", "moreInfo": "Mais informações", "takeAction": "Tomar uma ação", - "shout": "Aclamação ::: Aclamações", + "shout": "Recomendar ::: Recomendações", "user": "Usuário ::: Usuários", "category": "Categoria ::: Categorias", "organization": "Organização ::: Organizações", @@ -146,7 +453,11 @@ "name": "Nome", "loadMore": "Carregar mais", "loading": "Carregando", - "reportContent": "Denunciar" + "reportContent": "Denunciar", + "validations": { + "email": "deve ser um endereço de e-mail válido", + "url": "deve ser uma URL válida" + } }, "actions": { "loading": "Carregando", @@ -160,12 +471,19 @@ "moderation": { "name": "Moderação", "reports": { - "empty": "Parabéns, nada a moderar.", + "empty": "Parabéns, nada para moderar.", "name": "Denúncias", - "reporter": "Denunciado por" + "reasonCategory": "Categoria", + "reasonDescription": "Descrição", + "createdAt": "Data", + "submitter": "denunciado por", + "disabledBy": "desativado por" } }, "disable": { + "submit": "Desativar", + "cancel": "Cancelar", + "success": "Desativado com sucesso!", "user": { "title": "Desativar usuário", "type": "Usuário", @@ -182,52 +500,230 @@ "message": "Você realmente deseja desativar o comentário de \" {name} \"?" } }, + "delete": { + "submit": "Excluir", + "cancel": "Cancelar", + "contribution": { + "title": "Excluir publicação", + "type": "Contribuição", + "message": "Você realmente deseja excluir a publicação \"{name}\"?", + "success": "Publicação excluída com êxito!" + }, + "comment": { + "title": "Excluir Comentário", + "type": "Comentário", + "message": "Você realmente deseja excluir o comentário \"{name}\"?", + "success": "Comentário excluído com sucesso!" + } + }, "report": { "submit": "Enviar denúncia", "cancel": "Cancelar", + "success": "Obrigado por reportar!", "user": { "title": "Denunciar usuário", "type": "Usuário", - "message": "Você realmente deseja denunciar o usuário \" {name} \"?" + "message": "Você realmente deseja denunciar o usuário \" {name} \"?", + "error": "Você já denunciou o usuário!" }, "contribution": { "title": "Denunciar Contribuição", "type": "Contribuição", - "message": "Você realmente deseja denunciar a contribuição \" {name} \"?" + "message": "Você realmente deseja denunciar a contribuição \" {name} \"?", + "error": "Você já denunciou a contribuição!" }, "comment": { "title": "Denunciar Comentário", "type": "Comentário", - "message": "Você realmente deseja denunciar o comentário de \"{name}\"?" + "message": "Você realmente deseja denunciar o comentário de \"{name}\"?", + "error": "Você já denunciou o comentário!" + }, + "reason": { + "category": { + "label": "Selecione uma categoria:", + "placeholder": "Categoria …", + "options": { + "discrimination_etc": "Mensagens, comentários, afirmações ou insultos discriminatórios.", + "pornographic_content_links": "Publicação ou vinculação de material claramente pornográfico.", + "glorific_trivia_of_cruel_inhuman_acts": "Glorificação ou banalização de atos de violência cruel ou desumana.", + "doxing": "A divulgação de informações pessoais de terceiros sem o seu consentimento ou ameaça de (\"doxing\").", + "intentional_intimidation_stalking_persecution": "Intimidação intencional, assédio ou perseguição.", + "advert_products_services_commercial": "Publicidade de produtos e serviços com intenção comercial.", + "criminal_behavior_violation_german_law": "Comportamento criminoso ou violação da lei alemã.", + "other": "Outros …" + }, + "invalid": "Selecione uma categoria válida" + }, + "description": { + "label": "Por favor, explique: Por que você gostaria de denunciar isso?", + "placeholder": "Informações adicionais …" + } } }, - "contribution": { - "title": "Título", - "edit": "Editar Contribuição", - "delete": "Apagar Contribuição", - "teaserImage": { - "cropperConfirm": "Confirmar" - } - }, - "comment": { - "content": { - "unavailable-placeholder": "… este commenttário não está disponível" - }, - "menu": { - "edit": "Editar Comentário", - "delete": "Apagar Comentário" - }, - "show": { - "more": "mostrar mais", - "less": "mostrar menos" - }, - "edited": "editado" - }, "followButton": { "follow": "Seguir", "following": "Seguindo" }, "shoutButton": { - "shouted": "Aclamou" + "shouted": "Recomendou" + }, + "release": { + "submit": "Liberar", + "cancel": "Cancelar", + "success": "Liberado com sucesso!", + "user": { + "title": "Liberar usuário", + "type": "Usuário", + "message": "Você realmente quer liberar o usuário \"{name}\"?", + "error": "Você já denunciou o usuário!" + }, + "contribution": { + "title": "Liberar contribuição ", + "type": "Contribuição", + "message": "Você realmente quer liberar a contribuição \"{name}\"?", + "error": "Você já denunciou a contribuição!" + }, + "comment": { + "title": "Liberar Comentário ", + "type": "Comentário", + "message": "Você realmente quer liberar o comentário de \"{name}\"?", + "error": "Você já denunciou o comentário!" + } + }, + "user": { + "avatar": { + "submitted": "Carregado com sucesso!" + } + }, + "contribution": { + "title": "Título", + "newPost": "Criar uma nova publicação", + "filterFollow": "Filtrar contribuições de usuários que eu sigo", + "filterALL": "Ver todas as contribuições", + "success": "Salvo!", + "languageSelectLabel": "Idioma", + "categories": { + "infoSelectedNoOfMaxCategories": "{chosen} of {max} categorias selecionadas" + }, + "emotions-label": { + "funny": "Engraçado", + "happy": "Feliz", + "surprised": "Surpreso", + "cry": "Chorando", + "angry": "Irritado" + }, + "category": { + "name": { + "freedom-of-speech": "Liberdade de expressão", + "consumption-sustainability": "Consumo e Sustentabilidade", + "global-peace-nonviolence": "Paz Mundial e Não-Violência", + "just-for-fun": "Só por diversão", + "happiness-values": "Felicidade e Valores", + "health-wellbeing": "Saúde e Bem-estar", + "environment-nature": "Meio Ambiente e Natureza", + "animal-protection": "Proteção Animal", + "human-rights-justice": "Direitos Humanos e Justiça", + "education-sciences": "Educação e Ciências", + "cooperation-development": "Cooperação e Desenvolvimento", + "democracy-politics": "Democracia e Política", + "economy-finances": "Economia e Finanças", + "energy-technology": "Energia e tecnologia", + "it-internet-data-privacy": "TI, Internet e Privacidade de Dados", + "art-culture-sport": "Arte, Cultura e Esporte" + } + }, + "teaserImage": { + "cropperConfirm": "Confirmar" + } + }, + "code-of-conduct": { + "subheader": "para a rede social da Human Connection gGmbH", + "preamble": { + "title": "Introdução", + "description": "A Human Connection é uma rede de conhecimento e ação social sem fins lucrativos da próxima geração. Feito por pessoas - para pessoas. Open Source, justo e transparente. Para uma mudança local e global positiva em todas as áreas da vida. Redesenhamos completamente a troca pública de conhecimentos, idéias e projetos. As funções da Human Connection reúnem as pessoas - offline e online - para que possamos fazer do mundo um lugar melhor." + }, + "purpose": { + "title": "Propósito", + "description": "Com este código de conduta, regulamentamos os princípios essenciais para o comportamento em nossa rede social. A Carta dos Direitos Humanos das Nações Unidas é a nossa orientação e forma o coração da nossa compreensão dos valores. O código de conduta serve como princípios orientadores para a nossa aparência pessoal e interação uns com os outros. Qualquer pessoa ativa como usuário da Human Connection Network, que escreve artigos, comentários ou se conecta com outros usuários, incluindo aqueles fora da rede, reconhece estas regras de conduta como obrigatórias." + }, + "expected-behaviour": { + "title": "Comportamentos esperados", + "description": "Os seguintes comportamentos são esperados e solicitados a todos os membros da comunidade:", + "list": { + "0": "Exercitar ponderação e respeito no seu discurso e ações.", + "1": "Tente a colaboração antes do conflito.", + "2": "Evite comportamentos e discursos humilhantes, discriminatórios ou assediadores.", + "3": "Esteja atento ao que o rodeia e aos outros participantes. Alerte os líderes comunitários se você perceber uma situação perigosa, alguém em perigo ou violações deste Código de Conduta, mesmo que pareçam inconsequentes." + } + }, + "unacceptable-behaviour": { + "title": "Comportamentos Inaceitáveis", + "description": "Os seguintes comportamentos são inaceitáveis dentro da nossa comunidade:", + "list": { + "0": "Mensagens, comentários, afirmações ou insultos discriminatórios, particularmente aqueles relacionados a gênero, orientação sexual, raça, religião, orientação política ou filosófica ou deficiência.", + "1": "Publicar ou vincular material claramente pornográfico.", + "2": "Glorificação ou banalização de atos de violência cruel ou desumana.", + "3": "A divulgação de informações pessoais de terceiros sem o seu consentimento ou ameaça de (\"doxing\").", + "4": "Intimidação intencional, assédio ou perseguição.", + "5": "Publicidade de produtos e serviços com intenção comercial.", + "6": "Comportamento criminoso ou violação da lei alemã.", + "7": "Apoiar ou incentivar tal conduta." + } + }, + "consequences": { + "title": "Consequências de um comportamento inaceitável", + "description": "Se um membro da comunidade apresentar um comportamento inaceitável, os operadores, moderadores e administradores responsáveis da rede podem tomar medidas adequadas, incluindo, entre outras:", + "list": { + "0": "Pedido de suspensão imediata de uma conduta inaceitável", + "1": "Bloquear ou excluir comentários", + "2": "Exclusão temporária da respectiva publicação ou contribuição", + "3": "Bloquear ou eliminar conteúdo", + "4": "Retirada temporária de permissão de escrita", + "5": "Exclusão temporária da rede", + "6": "Exclusão definitiva da rede", + "7": "Violações da lei alemã podem ser denunciadas", + "8": "Divulgação ou incentivo a estes comportamentos." + } + }, + "get-help": "Se você for vítima ou testemunhar um comportamento inaceitável, ou tiver qualquer outra preocupação, por favor notifique um organizador da comunidade o mais rápido possível e inclua o link ou mencione o conteúdo correspondente:" + }, + "termsAndConditions": { + "newTermsAndConditions": "Novos Termos e Condições", + "termsAndConditionsConfirmed": "Eu li e confirmei os Terms and Conditions.", + "termsAndConditionsNewConfirmText": "Por favor, leia os novos termos de uso agora!", + "termsAndConditionsNewConfirm": "Eu li e concordo com os novos termos de condições.", + "agree": "Eu concordo!", + "terms-of-service": { + "title": "Termos do Serviço", + "description": "As seguintes condições de utilização constituem a base para a utilização da nossa rede. Ao registar-se, deve aceitar os termos de utilização e informaremos mais tarde sobre quaisquer alterações que possam ocorrer. A Human Connection Network é operada na Alemanha e, portanto, está sujeita à lei alemã. O local de jurisdição é Kirchheim / Teck. Para mais detalhes, veja a nossa impressão: https://human-connection.org/imprint " + }, + "use-and-license" : { + "title": "Uso e Licença", + "description": "Se qualquer conteúdo que você publicar para nós for protegido por direitos de propriedade intelectual, você nos concede uma licença não exclusiva, transferível, sublicenciável e mundial para usar tal conteúdo para postar em nossa rede. Esta licença expira quando você exclui seu conteúdo ou toda a sua conta. Lembre-se de que outros podem compartilhar seu conteúdo e nós não podemos excluí-lo." + }, + "privacy-statement" : { + "title": "Política de Privacidade", + "description": "Nossa rede é uma rede social de conhecimento e ação. Portanto, é particularmente importante para nós que o máximo de conteúdo possível seja acessível ao público. No decurso do desenvolvimento da nossa rede, haverá cada vez mais a possibilidade de decidir sobre a visibilidade dos dados pessoais. Iremos informá-lo sobre estas novas funcionalidades. Caso contrário, você deve sempre pensar sobre quais dados pessoais você divulga sobre si mesmo (ou outros). Isto aplica-se em particular ao conteúdo das mensagens e comentários, uma vez que estes têm um carácter largamente público. Mais tarde, haverá possibilidades de limitar a visibilidade do seu perfil. Parte dos termos de serviço é a nossa declaração de privacidade, que o informa sobre as operações individuais de processamento de dados na nossa rede: https://human-connection.org/datenschutz/#netzwerk bzw. https://human-connection.org/datenschutz/ Nossa declaração de privacidade é adaptada à situação legal e às características de nossa rede e é sempre válida na versão mais atual." + }, + "code-of-conduct" : { + "title": "Código de Conduta", + "description": "Nosso código de conduta serve como um manual de aparência pessoal e interação entre si. Quem quer que seja ativo como utilizador na rede Human Connection, escreva artigos, comentários ou faça contato com outros utilizadores, mesmo fora da rede, reconhece estas regras de conduta como sendo vinculativas. https://alpha.human-connection.org/code-of-conduct" + }, + "moderation" : { + "title": "Moderação", + "description": "Até que nossas possibilidades financeiras nos permitam implementar o sistema de moderação comunitária, moderamos com um sistema simplificado e com pessoal próprio ou possivelmente voluntário. Nós treinamos esses moderadores e por isso só eles tomam as decisões apropriadas. Estes moderadores realizam o seu trabalho de forma anônima. Você pode denunciar mensagens, comentários e usuários para nós (por exemplo, se eles fornecem informações em seu perfil ou têm imagens que violam estes Termos de Uso). Se você relatar algo para nós, você precisa dar uma razão e pode dar uma breve explicação. Em seguida, analisaremos o que você denunciou e o sancionaremos, se necessário, por exemplo, bloqueando contribuições, comentários ou usuários. Infelizmente, você e a pessoa em questão não receberão qualquer feedback da nossa parte neste momento, mas isto está na fase de planeamento. Independentemente disso, reservamo-nos o direito de impor sanções em princípio por razões que podem ou não estar listadas no nosso Código de Conduta ou nestes termos de serviço." + }, + "errors-and-feedback" : { + "title": "Erros e Feedback", + "description": "Fazemos todos os esforços para manter a nossa rede e dados seguros e disponíveis. Cada nova versão do software passa por testes automatizados e manuais. No entanto, podem ocorrer erros imprevistos. Portanto, somos gratos por quaisquer erros reportados. Você é bem-vindo para relatar quaisquer erros que você descobrir enviando um e-mail para o Suporte em support@human-connection.org." + }, + "help-and-questions" : { + "title": "Ajuda e Perguntas", + "description": "Para ajuda e perguntas, nós compilamos uma coleção abrangente de perguntas e respostas frequentes (FAQ) para você. Você pode encontrá-las aqui: https://support.human-connection.org/kb/ " + }, + "addition" : { + "title": "Além disso, realizamos regularmente eventos onde você também pode compartilhar suas impressões e fazer perguntas. Você pode encontrar uma visão geral atual aqui:", + "description": " https://human-connection.org/events/ " + } } } diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json new file mode 100644 index 000000000..35566533b --- /dev/null +++ b/webapp/locales/ru.json @@ -0,0 +1,717 @@ +{ + "actions": { + "cancel": "Отменить", + "create": "Создать", + "delete": "Удалить", + "edit": "Редактировать", + "loading": "погрузочный", + "loadMore": "Загрузить больше", + "save": "Сохранить" + }, + "admin": { + "categories": { + "categoryName": "Имя", + "name": "Категории", + "postCount": "Сообщения" + }, + "dashboard": { + "comments": "Комментарии", + "follows": "Следит", + "invites": "Приглашения", + "name": "Приборная панель", + "notifications": "Уведомления", + "organizations": "Организаций", + "posts": "сообщения", + "projects": "Проекты", + "shouts": "Оглашения", + "users": "Пользователи" + }, + "hashtags": { + "name": "Хэштеги", + "nameOfHashtag": "Имя", + "number": "Но.", + "tagCount": "Сообщений", + "tagCountUnique": "Пользователи" + }, + "invites": { + "description": "Приглашения — это замечательный способ завести своих друзей в своей сети ...", + "name": "Пригласить пользователей", + "title": "Пригласить людей" + }, + "name": "Администратор", + "notifications": { + "name": "Уведомления" + }, + "organizations": { + "name": "Организаций" + }, + "pages": { + "name": "Страницы" + }, + "settings": { + "name": "Настройки" + }, + "tags": { + "name": "Теги", + "tagCount": "Сообщения", + "tagCountUnique": "Пользователи" + }, + "users": { + "empty": "Не найдено пользователей", + "form": { + "placeholder": "Электронная почта, имя или описание" + }, + "name": "Пользователи", + "table": { + "columns": { + "createdAt": "Создано в", + "email": "Эл. почта", + "name": "Имя", + "number": "Но.", + "role": "Роль", + "slug": "Slug" + } + } + } + }, + "code-of-conduct": { + "consequences": { + "description": "Если участник сообщества проявляет неприемлемое поведение, ответственные операторы, модераторы и администраторы сети могут принять соответствующие меры, включая, но не ограничиваясь:", + "list": { + "0": "Просьба о немедленном прекращении неприемлемого поведения", + "1": "запирание или удаление контента", + "2": "Временное исключение из соответствующей должности или вклада", + "3": "блокирование или удаление контента", + "4": "Временное снятие разрешений на запись", + "5": "Временное исключение из сети", + "6": "Окончательное исключение из сети", + "7": "Можно сообщить о нарушениях немецкого законодательства.", + "8": "Адвокатура или поощрение такого поведения." + }, + "title": "Последствия неприемлемого поведения" + }, + "expected-behaviour": { + "description": "Следующее поведение ожидается и требуется от всех членов сообщества:", + "list": { + "0": "Будь внимательны и уважительны к тому, что ты пишешь и делаешь.", + "1": "Попытайтесь подойти к другим, прежде чем возникнет конфликт.", + "2": "Унижающего достоинство, дискриминационного или преследующего поведения и выражений.", + "3": "Будь внимательны к своей среде. Сообщи о поддержке в опасных ситуациях, когда человек нуждается или нарушает настоящий Кодекс поведения, даже если он кажется незначительным." + }, + "title": "Ожидаемое поведение" + }, + "get-help": "Если ты стал жертвой или свидетелем неприемлемого поведения или у вас возникли какие-либо другие проблемы, пожалуйста, как можно скорее сообщите об этом организатору сообщества и укажите ссылку на соответствующий контент:", + "preamble": { + "description": "Human Connection-это некоммерческая сеть социальных знаний и действий следующего поколения. Из людей – для людей. Открытый исходный код, справедливый и прозрачный. Для позитивных локальных и глобальных изменений во всех сферах жизни. Мы полностью перестраиваем публичный обмен знаниями, идеями и проектами. Функции Human Connection объединяют людей – в автономном режиме и в интернете – так что мы можем сделать мир лучше.", + "title": "Преамбула" + }, + "purpose": { + "description": "С помощью этих правил поведения мы регулируем основные принципы поведения в нашей социальной сети. При этом Устав ООН по правам человека является нашей ориентацией и лежит в основе нашего понимания ценностей. Правила поведения служат руководящими принципами для личного выступления и общения друг с другом. Любой, кто является активным пользователем в сети Human Connection, публикует сообщения, комментирует или контактирует с другими пользователями, в том числе за пределами сети, признает эти правила поведения обязательными.", + "title": "Цель" + }, + "subheader": "для социальной сети \"Human Connection gGmbH\"", + "unacceptable-behaviour": { + "description": "В нашем сообществе неприемлемы следующие типы поведения:", + "list": { + "0": "Дискриминационные сообщения, комментарии, высказывания или оскорбления, в частности, касающиеся пола, сексуальной ориентации, расы, религии, политической или мировоззренческой ориентации, или инвалидности.", + "1": "Публикация или ссылка явно порнографический материал.", + "2": "Прославление или умаление жестоких, или бесчеловечных актов насилия.", + "3": "Публикация персональных данных других лиц без их согласия или угрозы (\"Доксинг\").", + "4": "Преднамеренное запугивание, сталкинг или преследование.", + "5": "Рекламировать продукты и услуги с коммерческим намерением.", + "6": "Уголовного поведение или нарушении немецкое право.", + "7": "Одобрение или поощрение этого поведения." + }, + "title": "Недопустимое поведение" + } + }, + "comment": { + "content": { + "unavailable-placeholder": "...этот комментарий больше не доступен" + }, + "delete": "Удалить комментарий", + "edit": "редакти́ровать Комментарий", + "edited": "Отредактированы", + "menu": { + "delete": "Удалить комментарий", + "edit": "Редактировать комментарий" + }, + "show": { + "less": "показать меньше", + "more": "показать больше" + } + }, + "common": { + "category": "Категория ::: Категории", + "comment": "Комментарий ::: Комментарии", + "letsTalk": "Давай поговорим", + "loading": "идёт загрузка", + "loadMore": "Загрузить больше", + "moreInfo": "Больше информации", + "name": "Имя", + "organization": "Организация ::: Организации", + "post": "Сообщение ::: Сообщения", + "project": "Проект ::: Проекты", + "reportContent": "Отчет", + "shout": "Оглашения ::: Оглашении", + "tag": "Тег ::: Теги", + "takeAction": "принять меры", + "user": "Пользователь ::: Пользователи", + "validations": { + "email": "должен быть действительный адрес электронной почты", + "url": "должен быть действительным URL" + }, + "versus": "Против" + }, + "components": { + "enter-nonce": { + "form": { + "description": "Откройте папку \"Входящие\" и введите код, который мы тебя отправили.", + "next": "Продолжить", + "nonce": "Введите свой код", + "validations": { + "length": "должна состоять из 6 символов." + } + } + }, + "password-reset": { + "change-password": { + "error": "Смена пароля не удалась. Может быть, код безопасности был неправильным?", + "help": "В случае возникновения проблем, не стесняйся обращаться за помощью, отправив нам письмо по адресу:", + "success": "Смена пароля прошла успешно!" + }, + "request": { + "form": { + "description": "На указанный адрес электронной почты будет отправлено сообщение для сброса пароля.", + "submit": "Запросить электронную почту", + "submitted": "На адрес {email}<\/b>было отправлено электронное письмо с дальнейшими инструкциями" + }, + "title": "Сбросить пароль" + } + }, + "registration": { + "create-user-account": { + "error": "Невозможно создать учетную запись пользователя!", + "help": "Может быть, подтверждение было недействительным? В случае возникновения проблем, не стесняйтесь обращаться за помощью, отправив нам письмо по электронной почте:", + "success": "Твоя учетная запись создана!", + "title": "Создать учетную запись пользователя" + }, + "signup": { + "form": { + "data-privacy": "Я прочитал и понял Заявление о конфиденциальности<\/ds-text><\/a>", + "description": "Чтобы начать работу, введи свой адрес электронной почты:", + "errors": { + "email-exists": "Уже есть учетная запись пользователя с этим адресом электронной почты!", + "invalid-invitation-token": "Похоже, что приглашение уже использовалось. Ссылки на приглашения можно использовать только один раз." + }, + "invitation-code": "Твой код приглашения: {code}<\/b>", + "minimum-age": "Мне 18 лет или старше.", + "submit": "Создать учетную запись", + "success": "Письмо со ссылкой для завершения регистрации было отправлено на {email} <\/b>", + "terms-and-condition": "Подтверждаю, на Условия и положения<\/ds-text><\/a>.ю" + }, + "title": "Присоединяйся к Human Connection!", + "unavailable": "К сожалению, публичная регистрация учетных записей пользователей на этом сервере сейчас недоступна." + } + } + }, + "contribution": { + "categories": { + "infoSelectedNoOfMaxCategories": "{chosen} из {max} выбранных категорий" + }, + "category": { + "name": { + "animal-protection": "Защита животных", + "art-culture-sport": "Искусство, культура и спорт", + "consumption-sustainability": "Потребление И Устойчивость", + "cooperation-development": "Сотрудничество и развитие", + "democracy-politics": "Демократия и политика", + "economy-finances": "Экономика и финансы", + "education-sciences": "Образование и наука", + "energy-technology": "Энергия и технологии", + "environment-nature": "Окружающая среда и природа", + "freedom-of-speech": "Свобода слова", + "global-peace-nonviolence": "Глобальный мир и ненасилие", + "happiness-values": "Счастье и Ценности", + "health-wellbeing": "Здоровье и благополучие", + "human-rights-justice": "Права Человека и Справедливость", + "it-internet-data-privacy": "ИТ, интернет и конфиденциальность", + "just-for-fun": "Просто для удовольствия" + } + }, + "delete": "Удалить вклад", + "edit": "редакти́ровать вклад", + "emotions-label": { + "angry": "Сердитый", + "cry": "Плакать", + "funny": "Смешно́й", + "happy": "Счастливый", + "surprised": "Удивленный" + }, + "filterALL": "Просмотреть все вклады", + "filterFollow": "Фильтровать сообщения пользователей, на которых я слежу", + "languageSelectLabel": "Язык", + "newPost": "Создать новое сообщение", + "success": "Сохранено!", + "teaserImage": { + "cropperConfirm": "Подтвердить" + }, + "title": "Заглавие" + }, + "delete": { + "cancel": "Отменить", + "comment": { + "message": "Ты уверены, что хочешь удалить комментарий \"{name}<\/b>\"?", + "success": "Комментарий успешно удален!", + "title": "Удалить комментарий", + "type": "Комментарий" + }, + "contribution": { + "message": "Ты уверены, что хочешь удалить сообщение \"{name}<\/b>\"?", + "success": "Сообщение успешно удалено!", + "title": "Удаленный вклад", + "type": "Вклад" + }, + "submit": "Удалить" + }, + "disable": { + "cancel": "Отменить", + "comment": { + "message": "ты действительно хочешь отключить комментарий от «{name}<\/b>»?", + "title": "Отключить комментарий", + "type": "Комментарий" + }, + "contribution": { + "message": "Вы действительно хотите отключить вклад «{name}<\/b>»?", + "title": "Отключить вклад", + "type": "Вклад" + }, + "submit": "Отключить", + "success": "Успешно отключен", + "user": { + "message": "ты действительно хочешь отключить пользователя «{name}<\/b>»?", + "title": "Отключить пользователя", + "type": "Пользователь" + } + }, + "editor": { + "embed": { + "always_allow": "Всегда отображать содержимое сторонних производителей (эту настройку можно изменить в любое время).", + "data_privacy_info": "Твои данные еще не были переданы третьим лицам. Если ты воспроизводишь это видео сейчас, следующий провайдер, вероятно, зарегистрирует твои данные пользователя:", + "data_privacy_warning": "Внимание Конфиденциальность данных!", + "play_now": "Смотреть сейчас" + }, + "hashtag": { + "addHashtag": "Новый хэштег", + "addLetter": "Введите букву", + "noHashtagsFound": "Хэштеги не найдены" + }, + "mention": { + "noUsersFound": "Пользователи не найдены" + }, + "placeholder": "Оставь свои вдохновляющие мысли ..." + }, + "filter-menu": { + "clearSearch": "Опорожнить поиск", + "hashtag-search": "Поиск по #{hashtag}", + "title": "Твой фильтр-пузырь" + }, + "filter-posts": { + "categories": { + "all": "Все", + "header": "Категории контента" + }, + "followers": { + "label": "Пользователи, за которыми я слежу" + }, + "general": { + "header": "Фильтровать по …" + } + }, + "followButton": { + "follow": "Следовать", + "following": "Следующий" + }, + "index": { + "change-filter-settings": "Измени настройки фильтра, чтобы получить больше результатов.", + "no-results": "Вклады не найдены." + }, + "login": { + "copy": "Если у тебя уже есть учетная запись Human Connection, войдите здесь.", + "email": "Твой адрес электронной почты", + "failure": "Неверный адрес электронной почты или пароль.", + "forgotPassword": "Забыл пароль?", + "hello": "Привет", + "login": "Вход", + "logout": "Выйти", + "moreInfo": "Что такое Human Connection", + "moreInfoHint": "на страницу презентации", + "moreInfoURL": "https:\/\/human-connection.org\/en\/", + "no-account": "У вас нет аккаунта?", + "password": "Твой пароль", + "register": "Зарегистрироваться", + "success": "Ты вошёл в систему!" + }, + "maintenance": { + "explanation": "В данный момент мы проводим плановое техническое обслуживание, пожалуйста, повтори попытку позже.", + "questions": "Любые вопросы или проблемы, отправь по электронной почте по адресу", + "title": "Human Connection находится в технического обслуживания" + }, + "moderation": { + "name": "Модерация", + "reports": { + "createdAt": "Дата", + "disabledBy": "отключены", + "empty": "Поздравляю, модерировать нечего.", + "name": "Отчеты", + "reasonCategory": "Категория", + "reasonDescription": "Описание", + "reporter": "сообщила", + "submitter": "сообщила" + } + }, + "notifications": { + "comment": "Комментарий", + "reason": { + "commented_on_post": "Прокомментировал твой вклад...", + "mentioned_in_comment": "Упоминал тебе в комментарии....", + "mentioned_in_post": "Упоминал тебе в сообщении...." + } + }, + "post": { + "comment": { + "submit": "Комментируй", + "submitted": "Комментарий отправлен", + "updated": "Сохраненные изменения" + }, + "edited": "Отредактированы", + "menu": { + "delete": "Удалить вклад", + "edit": "Редактировать вклад", + "pin": "Закрепить Сообщение", + "pinnedSuccessfully": "Сообщение успешно закреплен!", + "unpin": "Не прикрепи́ть Сообщение больше", + "unpinnedSuccessfully": "Сообщение успешно не закреплено!" + }, + "moreInfo": { + "description": "Здесь ты найдешь больше информации по теме.", + "name": "Больше информации", + "title": "Больше информация", + "titleOfCategoriesSection": "Категории", + "titleOfHashtagsSection": "Хэштеги", + "titleOfRelatedContributionsSection": "Похожие вклады" + }, + "name": "Пост", + "pinned": "Объявление", + "takeAction": { + "name": "Действовать" + } + }, + "profile": { + "commented": "Прокомментированный", + "follow": "Следовать", + "followers": "Последователей", + "following": "Следующий", + "invites": { + "description": "Введи адрес электронной почты для приглашения.", + "emailPlaceholder": "Электронная почта для приглашения", + "title": "Пригласи кого-нибудь в Human Connection!" + }, + "memberSince": "член с тех пор", + "name": "Мой профайл", + "network": { + "andMore": "и {number} больше…", + "followedBy": "за ним следуют:", + "followedByNobody": "никому не следует.", + "following": "Следует:", + "followingNobody": "не следует ни за кем.", + "title": "Сеть" + }, + "shouted": "оглашали", + "socialMedia": "Где еще я могу найти", + "userAnonym": "Анонимный" + }, + "quotes": { + "african": { + "author": "Африканская пословица", + "quote": "Многие маленькие люди во многих маленьких местах делают много маленьких вещей, которые могут изменить лицо мира." + } + }, + "release": { + "cancel": "Отменить", + "comment": { + "error": "Ты уже сообщил о комментариях!", + "message": "Ты уверены, что хочешь поделиться комментарием \"{name}<\/b>\"?", + "title": "Выпускать Комментарий", + "type": "Комментарий" + }, + "contribution": { + "error": "Ты уже сообщил о вкладе!", + "message": "Ты уверены, что хочешь поделиться постом \"{name}<\/b>\"?", + "title": "Опубликовать вклад", + "type": "Вклад" + }, + "submit": "Выпускать", + "success": "Выпущен успешно!", + "user": { + "error": "Ты уже сообщил о пользователе!", + "message": "Ты уверены, что хочешь поделиться пользователем \"{name}<\/b>\"?", + "title": "Выпускать пользователя", + "type": "Пользователь" + } + }, + "report": { + "cancel": "Отменить", + "comment": { + "error": "Ты уже сообщил о комментарии!", + "message": "Ты действительно хочешь сообщить комментарий от « {name} <\/b> »?", + "title": "Пожаловаться на комментарий", + "type": "Комментарий" + }, + "contribution": { + "error": "Ты уже сообщил о посте!", + "message": "Ты действительно хочешь сообщить о вкладе «{name}<\/b>»?", + "title": "Отчет Вклад", + "type": "Вклад" + }, + "reason": { + "category": { + "invalid": "Пожалуйста, выберите действительную категорию", + "label": "Выбери категорию:", + "options": { + "advert_products_services_commercial": "Рекламировать продукты и услуги с коммерческим намерением.", + "criminal_behavior_violation_german_law": "Уголовного поведение или нарушении немецкое право.", + "discrimination_etc": "Дискриминационные вклады, комментарии, заявления или оскорбления.", + "doxing": "Публикация персональных данных других лиц без их согласия или угрозы (\"Доксинг\").", + "glorific_trivia_of_cruel_inhuman_acts": "Прославление или умаление жестоких, или бесчеловечных актов насилия.", + "intentional_intimidation_stalking_persecution": "Преднамеренное запугивание, сталкинг или преследование.", + "other": "Другие...", + "pornographic_content_links": "Публикация или ссылка явно порнографический материал." + }, + "placeholder": "Категория ...." + }, + "description": { + "label": "Пожалуйста, объясни: Зачем ты хочешь сообщить об этом?", + "placeholder": "Дополнительная информация ..." + } + }, + "submit": "Отправить жалобу", + "success": "Спасибо за сообщение!", + "user": { + "error": "Ты уже сообщил о пользователе!", + "message": "ты действительно хочешь сообщить пользователя «{name}<\/b>»?", + "title": "Пожаловаться на пользователя", + "type": "Пользователь" + } + }, + "search": { + "failed": "Ничего не найдено", + "hint": "Что ты ищешь?", + "placeholder": "Поиск" + }, + "settings": { + "blocked-users": { + "block": "Блокировать пользователя", + "columns": { + "name": "Имя", + "slug": "Псевдоним" + }, + "empty": "До сих пор ты никого не блокировал.", + "explanation": { + "closing": "На данный момент этого должно быть достаточно, чтобы заблокированные пользователи больше не беспокоили тебя.", + "intro": "Если ты заблокировал другого пользователя, произойдет следующее:", + "notifications": "Люди, которых ты блокируешь, больше не будут получать уведомления, когда они упоминаются в твоих сообщениях.", + "search": "Сообщения от заблокированных пользователей исчезают из результатов поиска.", + "their-perspective": "И наоборот, то же самое: заблокированный человек больше не видит твоих сообщения в своем обзоре.", + "your-perspective": "В твоем обзоре сообщений больше не отображаются сообщения заблокированного человека." + }, + "how-to": "Ты можешь заблокировать других пользователей на странице их профиля с помощью меню содержимого.", + "name": "Заблокированные пользователи", + "unblock": "Разблокировать пользователей" + }, + "data": { + "labelBio": "О тебя", + "labelCity": "Твой город или регион", + "labelName": "Твое имя", + "labelSlug": "Твой уникальное имя пользователя", + "name": "твой данные", + "namePlaceholder": "Маша Медведкина", + "success": "Твои данные были успешно обновлены!" + }, + "delete": { + "name": "Удалить аккаунт" + }, + "deleteUserAccount": { + "accountDescription": "Помни, что твои сообщение и комментарии важны для нашего сообщества. Если ты все равно хочешь удалить их, ты должны помечать их ниже.", + "accountWarning": "Ты можешь удалить свой аккаунт, сообщения или комментарии после их удаления НИ УПРАВЛЯТЬ<\/b>, НИ ВОССТАНАВЛИВАТЬ!<\/b>", + "commentedCount": "Удалить мои {count} комментарии", + "contributionsCount": "Удалить мои {count} сообщения", + "name": "Удалить данные", + "pleaseConfirm": "Разрушительное действие!<\/b> Введите {confirm}<\/b> для подтверждения.", + "success": "Аккаунт успешно удален!" + }, + "download": { + "name": "Скачать данные" + }, + "email": { + "change-successful": "Твой адрес электронной почты был успешно изменен.", + "labelEmail": "Изменить адрес электронной почты", + "labelNewEmail": "Новый адрес электронной почты", + "labelNonce": "Введите свой код", + "name": "Твой адрес электронной почты", + "submitted": "Электронное письмо с подтверждением твоего адреса отправлено на {email}<\/b>.", + "success": "Новый адрес электронной почты был зарегистрирован.", + "validation": { + "same-email": "Это твой текущий адрес электронной почты." + }, + "verification-error": { + "explanation": "Это может иметь разные причины:", + "message": "Твой адрес электронной почты не может быть подтвержден.", + "reason": { + "invalid-nonce": "Код подтверждения неверен?", + "no-email-request": "Ты уверены, что запросил изменение своего адреса электронной почты?" + }, + "support": "Если проблема сохраняется, пожалуйста, свяжешься с нами по электронной почте" + } + }, + "embeds": { + "info-description": "Вот список сторонних провайдеров, чьё содержимое может отображаться в виде иностранного кода, например, в виде встроенных видео:", + "name": "Сторонние поставщики", + "status": { + "change": { + "allow": "Конечно.", + "deny": "Лучше не", + "question": "Ты хочешь, чтобы встроенный сторонний код всегда отображался для тебя?" + }, + "description": "По умолчанию настройки для тебя будет встроенный сторонний код", + "disabled": { + "off": "сначала не отображается", + "on": "отображается сразу" + } + } + }, + "invites": { + "name": "Приглашения" + }, + "languages": { + "name": "Языки" + }, + "name": "Настройки", + "organizations": { + "name": "Мои организации" + }, + "security": { + "change-password": { + "button": "Изменить пароль", + "label-new-password": "Твой новый пароль", + "label-new-password-confirm": "Подтверди свой новый пароль", + "label-old-password": "Твой старый пароль", + "message-new-password-confirm-required": "Подтверди свой новый пароль", + "message-new-password-missmatch": "Введите тот же пароль еще раз", + "message-new-password-required": "Введи новый пароль", + "message-old-password-required": "Введи свой старый пароль", + "passwordSecurity": "Безопасность пароля", + "passwordStrength0": "Очень небезопасный пароль", + "passwordStrength1": "Небезопасный пароль", + "passwordStrength2": "Посредственный пароль", + "passwordStrength3": "Надежный пароль", + "passwordStrength4": "Очень надежный пароль", + "success": "Пароль успешно изменен!" + }, + "name": "Безопасность" + }, + "social-media": { + "name": "Социальные Медиа", + "placeholder": "Твой URL социальной сети", + "requireUnique": "Эта ссылка уже существует", + "submit": "Добавить ссылку", + "successAdd": "Добавлены Социальные Медиа. Профиль обновлен!", + "successDelete": "Социальные медиа удалены. Профиль обновлен!" + }, + "validation": { + "slug": { + "alreadyTaken": "Это имя пользователя уже занято.", + "regex": "Допускаются только строчные буквы, цифры, подчеркивания или дефисы." + } + } + }, + "shoutButton": { + "shouted": "оглашали" + }, + "site": { + "back-to-login": "Вернуться на страницу входа", + "bank": "банковский счет", + "changelog": "Изменения", + "code-of-conduct": "Кодекс поведения", + "contact": "Контакт", + "data-privacy": "Конфиденциальность данных", + "director": "Управляющий директор", + "error-occurred": "Произошла ошибка.", + "faq": "ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ (FAQ)", + "germany": "Германия", + "imprint": "Импрессум", + "made": "Сделано с #10084;", + "register": "Номер регистрации", + "responsible": "ответственный за содержание этой страницы (§ 55 Abs. 2 RStV)", + "taxident": "UST-ID. в соответствии с §27a Закона о налоге с продаж Германии:", + "termsAndConditions": "Условия и положения", + "thanks": "Спасибо!", + "tribunal": "Суд регистрации" + }, + "store": { + "posts": { + "orderBy": { + "newest": { + "label": "Новейший" + }, + "oldest": { + "label": "Старейший" + } + } + } + }, + "termsAndConditions": { + "addition": { + "description": " https:\/\/human-connection.org\/events\/ <\/a>", + "title": "Кроме того, мы регулярно проводим мероприятия, где ты также можешь\nподелиться своими впечатлениями и задать вопросы. Здесь ты можешь найти текущий обзор :" + }, + "agree": "Я согласен!", + "code-of-conduct": { + "description": "Наш кодекс поведения служит руководством для личного появления и взаимодействия друг с другом. Тот, кто является активным пользователем в Сети Human Connection, пишет статьи, комментирует или вступает в контакт с другими пользователями, даже за пределами сети, признает эти правила поведения обязательными. https:\/\/alpha.human-connection.org\/code-of-conduct<\/a>", + "title": "Кодекс поведения" + }, + "errors-and-feedback": { + "description": "Мы прилагаем все усилия для обеспечения безопасности и доступности нашей сети и данных. Каждый новый выпуск программного обеспечения проходит как автоматическое, так и ручное тестирование. Однако могут возникнуть непредвиденные ошибки. Поэтому мы благодарны за любые обнаруженные ошибки. ты можешь сообщить о любых обнаруженных ошибках, отправив электронное письмо в службу поддержки по адресу support@human-connection.org", + "title": "Ошибки и обратная связь" + }, + "help-and-questions": { + "description": "Для справки и вопросов мы собрали для вас исчерпывающую подборку часто задаваемых вопросов и ответов (FAQ). Ты можешь\nнайти их здесь: https:\/\/support.human-connection.org\/kb\/ <\/a>", + "title": "Помощь и вопросы" + }, + "moderation": { + "description": "Пока наши финансовые возможности позволяют нам реализовать это сообщество-система Модерации, модерировать мы с упрощенной системой и собственными или, при необходимости, добровольных сотрудниках. Мы обучаем этих модераторов, и по этой причине они тоже принимают соответствующие решения. Эти модераторы выполняют свою деятельность анонимно. ты можешь сообщать нам о сообщениях, комментариях и пользователях (например, если они предоставляют информацию в своем профиле или имеют изображения, которые нарушают настоящие Условия использования). Если ты сообщаешь нам что-то, ты можешь указать причину сообщения и дать еще одно краткое объяснение. Давайте посмотрим на то Сообщается и санкционировать в случае необходимости, например, путем блокировки сообщения, комментарии или пользователей. К сожалению, ты, а также пострадавший в настоящее время не получаешь от нас обратной связи, но это в планировании. Несмотря на это, мы оставляем за собой право на санкции по причинам, которые не могут или не могут быть указаны в нашем кодексе поведения или настоящих Условиях использования.", + "title": "ведение" + }, + "newTermsAndConditions": "Новые условия и положения", + "privacy-statement": { + "description": "Наша сеть — это социальная сеть знаний и действий. Поэтому для нас особенно важно, чтобы как можно больше контента было общедоступным. В процессе развития нашей сети будет намного больше возможностей, чтобы решить вопрос о видимости сами указанные личные данные. Об этих новых функциях мы сообщим. В противном случае ты должны всегда думать о том, какие личные данные ты раскрываешь о себе (или других). Это особенно актуально для содержания сообщений и комментариев, поскольку они имеют в основном общедоступный характер. В профиле позже появятся способы ограничения самой видимости. Часть условий использования - это наша политика конфиденциальности, которая информирует тебя о каждой обработке данных в нашей сети: \nhttps:\/\/human-connection.org\/datenschutz\/#netzwerk<\/a>\n или. \nhttps:\/\/human-connection.org\/datenschutz<\/a>\nНаша политика конфиденциальности корректируется в соответствии с законодательством и характеристиками нашей сети и действует в текущей версии.", + "title": "Заявление о конфиденциальности" + }, + "terms-of-service": { + "description": "Следующие условия использования являются основой для использования нашей сети. При Регистрации ты должны признать их и мы сообщим Тебе позже через происходящий при необходимости Изменений. Сеть Human Connection работает в Германии и поэтому регулируется немецким законодательством. Суд Kirchheim \/ Teck Stand. Подробности смотри в наших выходных данных: https:\/\/human-connection.org\/impressum<\/a>", + "title": "Условия обслуживания" + }, + "termsAndConditionsConfirmed": "Я прочитал и подтвердил Условия и положения<\/a>.", + "termsAndConditionsNewConfirm": "Я прочитал и согласен с новыми условиями.", + "termsAndConditionsNewConfirmText": "Пожалуйста, ознакомляешься с новыми условиями использования прямо сейчас!", + "use-and-license": { + "description": "Если контент, который ты нанимаешь у нас, защищен правами на интеллектуальную собственность, ты предоставляешь нам неисключительную, передаваемую, сублицензируемую и всемирную лицензию на использование этого контента для развертывания в нашей сети. Эта лицензия заканчивается, как только вт удаляешь свой контент или всю учетную запись. Имей в виду, что другие могут продолжать делиться вашим контентом, и мы не можем его удалить.", + "title": "Использование и лицензия" + } + }, + "user": { + "avatar": { + "submitted": "Загрузка успешная" + } + } +} \ No newline at end of file diff --git a/webapp/middleware.md b/webapp/middleware.md deleted file mode 100644 index a899527e2..000000000 --- a/webapp/middleware.md +++ /dev/null @@ -1,5 +0,0 @@ -# MIDDLEWARE - -This directory contains our application middleware. The middleware lets you define custom functions to be ran before rendering a page or a group of pages \(layouts\). - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index ec2502379..17bb5290c 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -100,7 +100,7 @@ export default { ** Global processed styles */ styleResources: { - scss: styleguideStyles, + scss: [styleguideStyles, '~view/styles/tokens.scss'], }, /* diff --git a/webapp/package.json b/webapp/package.json index d6cad2c99..a75d80095 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -65,10 +65,9 @@ "cookie-universal-nuxt": "~2.0.18", "cropperjs": "^1.5.5", "cross-env": "~6.0.3", - "date-fns": "2.6.0", + "date-fns": "2.7.0", "express": "~4.17.1", "graphql": "~14.5.8", - "isemail": "^3.2.0", "jsonwebtoken": "~8.5.1", "linkify-it": "~2.2.0", "node-fetch": "^2.6.0", @@ -82,6 +81,7 @@ "tiptap-extensions": "~1.28.4", "trunc-html": "^1.1.2", "v-tooltip": "~2.0.2", + "validator": "^12.0.0", "vue-count-to": "~1.0.13", "vue-infinite-scroll": "^2.0.2", "vue-izitoast": "^1.2.1", @@ -92,17 +92,18 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@babel/core": "~7.6.4", + "@babel/core": "~7.7.2", "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/preset-env": "~7.6.3", - "@storybook/addon-a11y": "^5.2.5", - "@storybook/addon-actions": "^5.2.5", - "@storybook/vue": "~5.2.5", + "@babel/preset-env": "~7.7.1", + "@storybook/addon-a11y": "^5.2.6", + "@storybook/addon-actions": "^5.2.6", + "@storybook/addon-notes": "^5.2.5", + "@storybook/vue": "~5.2.6", "@vue/cli-shared-utils": "~4.0.5", "@vue/eslint-config-prettier": "~5.0.0", "@vue/server-test-utils": "~1.0.0-beta.29", "@vue/test-utils": "~1.0.0-beta.29", - "async-validator": "^3.2.0", + "async-validator": "^3.2.2", "babel-core": "~7.0.0-bridge.0", "babel-eslint": "~10.0.3", "babel-jest": "~24.9.0", @@ -120,7 +121,7 @@ "eslint-plugin-prettier": "~3.1.1", "eslint-plugin-promise": "~4.2.1", "eslint-plugin-standard": "~4.0.1", - "eslint-plugin-vue": "~5.2.3", + "eslint-plugin-vue": "~6.0.0", "faker": "^4.1.0", "flush-promises": "^1.0.2", "fuse.js": "^3.4.5", @@ -130,11 +131,13 @@ "node-sass": "~4.13.0", "prettier": "~1.18.2", "sass-loader": "~8.0.0", + "storybook-design-token": "^0.4.1", + "storybook-vue-router": "^1.0.7", "style-loader": "~0.23.1", - "style-resources-loader": "~1.2.1", + "style-resources-loader": "~1.3.2", "vue-jest": "~3.0.5", - "vue-loader": "~15.7.0", - "vue-svg-loader": "~0.12.0", + "vue-loader": "~15.7.2", + "vue-svg-loader": "~0.15.0", "vue-template-compiler": "^2.6.10" } } diff --git a/webapp/pages.md b/webapp/pages.md deleted file mode 100644 index 352c9fa04..000000000 --- a/webapp/pages.md +++ /dev/null @@ -1,6 +0,0 @@ -# PAGES - -This directory contains your Application Views and Routes. The framework reads all the `*.vue` files inside this directory and create the router of your application. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). - diff --git a/webapp/pages/admin.vue b/webapp/pages/admin.vue index 7852673b0..8f2b989d6 100644 --- a/webapp/pages/admin.vue +++ b/webapp/pages/admin.vue @@ -55,6 +55,10 @@ export default { name: this.$t('admin.invites.name'), path: `/admin/invite`, }, + { + name: this.$t('admin.donations.name'), + path: '/admin/donations', + }, // TODO implement /* { name: this.$t('admin.settings.name'), diff --git a/webapp/pages/admin/donations.vue b/webapp/pages/admin/donations.vue new file mode 100644 index 000000000..7f0205be5 --- /dev/null +++ b/webapp/pages/admin/donations.vue @@ -0,0 +1,63 @@ + + + diff --git a/webapp/pages/admin/notifications.vue b/webapp/pages/admin/notifications.vue index 0d348633f..faad87a46 100644 --- a/webapp/pages/admin/notifications.vue +++ b/webapp/pages/admin/notifications.vue @@ -5,7 +5,7 @@ + diff --git a/webapp/pages/post/_id/_slug/more-info.vue b/webapp/pages/post/_id/_slug/more-info.vue index 07b4969d3..dae3021aa 100644 --- a/webapp/pages/post/_id/_slug/more-info.vue +++ b/webapp/pages/post/_id/_slug/more-info.vue @@ -36,7 +36,7 @@