diff --git a/.travis.yml b/.travis.yml
index a2aae3cdb..19ba3ff9d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ install:
- yarn global add wait-on
# Install Codecov
- yarn install
- - cp cypress.env.template.json cypress.env.json
+ - cp backend/.env.template backend/.env
before_script:
- docker-compose -f docker-compose.yml build --parallel
@@ -63,14 +63,14 @@ before_deploy:
deploy:
- provider: script
- script: scripts/docker_push.sh
+ script: bash scripts/docker_push.sh
on:
branch: master
- provider: script
- script: scripts/deploy.sh
+ script: bash scripts/deploy.sh
on:
branch: master
- provider: script
- script: scripts/github_release.sh
+ script: bash scripts/github_release.sh
on:
branch: master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72ca59fad..5fe6b9619 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,131 @@ 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).
-#### [v0.1.11](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.11)
+#### [v0.1.13](https://github.com/Human-Connection/Human-Connection/compare/v0.1.12...v0.1.13)
-> 25 November 2019
+> 13 December 2019
+- Update de.json [`#2492`](https://github.com/Human-Connection/Human-Connection/pull/2492)
+- Fix broken scroll behaviour on index and profile page [`#2487`](https://github.com/Human-Connection/Human-Connection/pull/2487)
+- Lokalise: Translations update [`#2503`](https://github.com/Human-Connection/Human-Connection/pull/2503)
+- build(deps): bump node from 13.1.0-alpine to 13.3.0-alpine in /webapp [`#2454`](https://github.com/Human-Connection/Human-Connection/pull/2454)
+- Lokalise: Translations update [`#2485`](https://github.com/Human-Connection/Human-Connection/pull/2485)
+- build(deps-dev): bump css-loader from 3.3.0 to 3.3.2 in /webapp [`#2505`](https://github.com/Human-Connection/Human-Connection/pull/2505)
+- build(deps-dev): bump cypress from 3.7.0 to 3.8.0 [`#2504`](https://github.com/Human-Connection/Human-Connection/pull/2504)
+- Favor transaction functions [`#2433`](https://github.com/Human-Connection/Human-Connection/pull/2433)
+- build(deps): bump nodemailer from 6.4.1 to 6.4.2 in /backend [`#2500`](https://github.com/Human-Connection/Human-Connection/pull/2500)
+- Update en.json [`#2491`](https://github.com/Human-Connection/Human-Connection/pull/2491)
+- Update es.json [`#2493`](https://github.com/Human-Connection/Human-Connection/pull/2493)
+- Update fr.json [`#2494`](https://github.com/Human-Connection/Human-Connection/pull/2494)
+- Update it.json [`#2496`](https://github.com/Human-Connection/Human-Connection/pull/2496)
+- build(deps-dev): bump nodemon from 2.0.1 to 2.0.2 in /backend [`#2499`](https://github.com/Human-Connection/Human-Connection/pull/2499)
+- build(deps): bump @nuxtjs/apollo from 4.0.0-rc18 to 4.0.0-rc19 in /webapp [`#2498`](https://github.com/Human-Connection/Human-Connection/pull/2498)
+- build(deps): bump neo4j-graphql-js from 2.10.0 to 2.10.1 in /backend [`#2497`](https://github.com/Human-Connection/Human-Connection/pull/2497)
+- Fix docker manifest on Travis CI [`#2488`](https://github.com/Human-Connection/Human-Connection/pull/2488)
+- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 [`#2453`](https://github.com/Human-Connection/Human-Connection/pull/2453)
+- build(deps-dev): bump cypress-file-upload from 3.5.0 to 3.5.1 [`#2489`](https://github.com/Human-Connection/Human-Connection/pull/2489)
+- build(deps): bump cookie-universal-nuxt from 2.0.19 to 2.1.0 in /webapp [`#2490`](https://github.com/Human-Connection/Human-Connection/pull/2490)
+- Update to version 0.1.12 [`#2483`](https://github.com/Human-Connection/Human-Connection/pull/2483)
+- Lokalise: update of locale/ru.json [`60b3035`](https://github.com/Human-Connection/Human-Connection/commit/60b3035a3d475cb481130c6fe94f2901711a4053)
+- Write test/refactor tests/resolvers/middleware [`d375ebe`](https://github.com/Human-Connection/Human-Connection/commit/d375ebe7d90e3251b17f59ffba8fb1470923ebe8)
+- Fix this annoying bug with a tested helper [`e24d803`](https://github.com/Human-Connection/Human-Connection/commit/e24d8035b13040dc29f5f9cb033de8c1a401ac34)
+
+#### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.12)
+
+> 10 December 2019
+
+- Show the comments again [`#2482`](https://github.com/Human-Connection/Human-Connection/pull/2482)
+- Improve notification query performance by reducing db calls [`#2470`](https://github.com/Human-Connection/Human-Connection/pull/2470)
+- Fix `Cannot read 'Post' of undefined` [`#2481`](https://github.com/Human-Connection/Human-Connection/pull/2481)
+- Hope to fix our deployment with explicit call of `bash` [`#2480`](https://github.com/Human-Connection/Human-Connection/pull/2480)
+- Revert layout changes image aspect ratio [`#2467`](https://github.com/Human-Connection/Human-Connection/pull/2467)
+- Quick fix for null pointer error in User.vue [`#2472`](https://github.com/Human-Connection/Human-Connection/pull/2472)
+- Checkbox 'no comercial + no political account' add to creat user account [`#2416`](https://github.com/Human-Connection/Human-Connection/pull/2416)
+- Remove data-test attriubutes in non-dev env [`#2421`](https://github.com/Human-Connection/Human-Connection/pull/2421)
+- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 [`#2452`](https://github.com/Human-Connection/Human-Connection/pull/2452)
+- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 in /backend [`#2455`](https://github.com/Human-Connection/Human-Connection/pull/2455)
+- build(deps-dev): bump @babel/cli from 7.7.4 to 7.7.5 in /backend [`#2458`](https://github.com/Human-Connection/Human-Connection/pull/2458)
+- build(deps): bump @sentry/node from 5.10.1 to 5.10.2 in /backend [`#2473`](https://github.com/Human-Connection/Human-Connection/pull/2473)
+- build(deps-dev): bump eslint-plugin-import from 2.18.2 to 2.19.1 in /backend [`#2474`](https://github.com/Human-Connection/Human-Connection/pull/2474)
+- build(deps-dev): bump css-loader from 3.2.1 to 3.3.0 in /webapp [`#2475`](https://github.com/Human-Connection/Human-Connection/pull/2475)
+- build(deps-dev): bump eslint-plugin-import from 2.18.2 to 2.19.1 in /webapp [`#2477`](https://github.com/Human-Connection/Human-Connection/pull/2477)
+- Fix #2237, Comments 4 times as long before "show more" [`#2443`](https://github.com/Human-Connection/Human-Connection/pull/2443)
+- Get rid of inconsistency with neode setup [`#2404`](https://github.com/Human-Connection/Human-Connection/pull/2404)
+- Bump styleguide to version 0.5.22 [`#2468`](https://github.com/Human-Connection/Human-Connection/pull/2468)
+- build(deps): bump nodemailer from 6.4.0 to 6.4.1 in /backend [`#2456`](https://github.com/Human-Connection/Human-Connection/pull/2456)
+- build(deps-dev): bump eslint-loader from 3.0.2 to 3.0.3 in /webapp [`#2459`](https://github.com/Human-Connection/Human-Connection/pull/2459)
+- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 in /backend [`#2460`](https://github.com/Human-Connection/Human-Connection/pull/2460)
+- build(deps-dev): bump @babel/core from 7.7.4 to 7.7.5 in /webapp [`#2461`](https://github.com/Human-Connection/Human-Connection/pull/2461)
+- build(deps-dev): bump @babel/preset-env from 7.7.4 to 7.7.6 in /webapp [`#2463`](https://github.com/Human-Connection/Human-Connection/pull/2463)
+- build(deps-dev): bump async-validator from 3.2.2 to 3.2.3 in /webapp [`#2464`](https://github.com/Human-Connection/Human-Connection/pull/2464)
+- build(deps): bump styleguide from `808b3c5` to `7ef8340` [`#2465`](https://github.com/Human-Connection/Human-Connection/pull/2465)
+- Paginate moderations page without losing filtering [`#2466`](https://github.com/Human-Connection/Human-Connection/pull/2466)
+- Update it.json [`#2451`](https://github.com/Human-Connection/Human-Connection/pull/2451)
+- build(deps): bump metascraper from 5.8.8 to 5.8.9 in /backend [`#2304`](https://github.com/Human-Connection/Human-Connection/pull/2304)
+- build(deps): bump metascraper-video from 5.8.7 to 5.8.9 in /backend [`#2303`](https://github.com/Human-Connection/Human-Connection/pull/2303)
+- build(deps): bump neo4j-graphql-js from 2.9.3 to 2.10.0 in /backend [`#2440`](https://github.com/Human-Connection/Human-Connection/pull/2440)
+- Hide Donations Bar [`#2422`](https://github.com/Human-Connection/Human-Connection/pull/2422)
+- build(deps): bump @sentry/node from 5.10.0 to 5.10.1 in /backend [`#2436`](https://github.com/Human-Connection/Human-Connection/pull/2436)
+- build(deps-dev): bump cypress-cucumber-preprocessor from 1.17.0 to 1.18.0 [`#2437`](https://github.com/Human-Connection/Human-Connection/pull/2437)
+- build(deps-dev): bump apollo-server-testing from 2.9.12 to 2.9.13 in /backend [`#2439`](https://github.com/Human-Connection/Human-Connection/pull/2439)
+- build(deps): bump apollo-server from 2.9.12 to 2.9.13 in /backend [`#2441`](https://github.com/Human-Connection/Human-Connection/pull/2441)
+- Eliminate database calls for reports query [`#2435`](https://github.com/Human-Connection/Human-Connection/pull/2435)
+- Use babel-loader with vue-svg-loader [`#2430`](https://github.com/Human-Connection/Human-Connection/pull/2430)
+- Remove disable from reports.disable Query [`#2432`](https://github.com/Human-Connection/Human-Connection/pull/2432)
+- 2253 fix scroll layout issue [`#2317`](https://github.com/Human-Connection/Human-Connection/pull/2317)
+- Update test description [`#2424`](https://github.com/Human-Connection/Human-Connection/pull/2424)
+- Update yarn.lock after pulling in latest changes [`#2419`](https://github.com/Human-Connection/Human-Connection/pull/2419)
+- Update privacy path [`#2417`](https://github.com/Human-Connection/Human-Connection/pull/2417)
+- Add browserstack logo to attributions [`#2431`](https://github.com/Human-Connection/Human-Connection/pull/2431)
+- build(deps): bump @sentry/node from 5.9.0 to 5.10.0 in /backend [`#2428`](https://github.com/Human-Connection/Human-Connection/pull/2428)
+- build(deps): bump nodemailer from 6.3.1 to 6.4.0 in /backend [`#2427`](https://github.com/Human-Connection/Human-Connection/pull/2427)
+- List and protocol moderation [`#1954`](https://github.com/Human-Connection/Human-Connection/pull/1954)
+- fix: Re-enable webfinger feature [`#2335`](https://github.com/Human-Connection/Human-Connection/pull/2335)
+- Close neo4j driver sessions [`#2402`](https://github.com/Human-Connection/Human-Connection/pull/2402)
+- feat: swap user name<=>handle for discriminability [`#2385`](https://github.com/Human-Connection/Human-Connection/pull/2385)
+- build(deps-dev): bump @storybook/vue from 5.2.6 to 5.2.8 in /webapp [`#2397`](https://github.com/Human-Connection/Human-Connection/pull/2397)
+- build(deps-dev): bump @storybook/addon-actions from 5.2.6 to 5.2.8 in /webapp [`#2398`](https://github.com/Human-Connection/Human-Connection/pull/2398)
+- Fix German translation of "Shouts" [`#2400`](https://github.com/Human-Connection/Human-Connection/pull/2400)
+- build(deps): bump tiptap-extensions from 1.28.4 to 1.28.5 in /webapp [`#2407`](https://github.com/Human-Connection/Human-Connection/pull/2407)
+- build(deps-dev): bump @storybook/addon-a11y from 5.2.7 to 5.2.8 in /webapp [`#2406`](https://github.com/Human-Connection/Human-Connection/pull/2406)
+- build(deps-dev): bump css-loader from 3.2.0 to 3.2.1 in /webapp [`#2405`](https://github.com/Human-Connection/Human-Connection/pull/2405)
+- build(deps-dev): bump @storybook/addon-notes from 5.2.6 to 5.2.8 in /webapp [`#2399`](https://github.com/Human-Connection/Human-Connection/pull/2399)
+- build(deps-dev): bump eslint from 6.7.1 to 6.7.2 in /webapp [`#2393`](https://github.com/Human-Connection/Human-Connection/pull/2393)
+- build(deps-dev): bump @vue/cli-shared-utils from 4.0.5 to 4.1.1 in /webapp [`#2374`](https://github.com/Human-Connection/Human-Connection/pull/2374)
+- build(deps-dev): bump eslint-plugin-jest from 23.0.5 to 23.1.1 in /webapp [`#2392`](https://github.com/Human-Connection/Human-Connection/pull/2392)
+- Terms of use extended with dot - no commercial use [`#2316`](https://github.com/Human-Connection/Human-Connection/pull/2316)
+- build(deps-dev): bump cypress-cucumber-preprocessor from 1.16.2 to 1.17.0 [`#2389`](https://github.com/Human-Connection/Human-Connection/pull/2389)
+- Lokalise: Translations update [`#2380`](https://github.com/Human-Connection/Human-Connection/pull/2380)
+- build(deps-dev): bump @storybook/addon-a11y from 5.2.6 to 5.2.7 in /webapp [`#2391`](https://github.com/Human-Connection/Human-Connection/pull/2391)
+- build(deps-dev): bump eslint-plugin-jest from 23.0.5 to 23.1.1 in /backend [`#2390`](https://github.com/Human-Connection/Human-Connection/pull/2390)
+- build(deps-dev): bump eslint from 6.7.1 to 6.7.2 in /backend [`#2388`](https://github.com/Human-Connection/Human-Connection/pull/2388)
+- build(deps-dev): bump @vue/server-test-utils from 1.0.0-beta.29 to 1.0.0-beta.30 in /webapp [`#2379`](https://github.com/Human-Connection/Human-Connection/pull/2379)
+- build(deps): bump neo4j from 3.5.12-enterprise to 3.5.13-enterprise in /neo4j [`#2377`](https://github.com/Human-Connection/Human-Connection/pull/2377)
+- build(deps-dev): bump @babel/cli from 7.7.0 to 7.7.4 in /backend [`#2366`](https://github.com/Human-Connection/Human-Connection/pull/2366)
+- build(deps-dev): bump cypress-plugin-retries from 1.4.0 to 1.5.0 [`#2360`](https://github.com/Human-Connection/Human-Connection/pull/2360)
+- No public registration in development so that backend test pass [`#2382`](https://github.com/Human-Connection/Human-Connection/pull/2382)
+- Don't remove sub-addresses in emails [`#2375`](https://github.com/Human-Connection/Human-Connection/pull/2375)
+- refactor: Remove obsolete code about invitation codes [`#2333`](https://github.com/Human-Connection/Human-Connection/pull/2333)
+- build(deps): bump @nuxtjs/apollo from 4.0.0-rc17 to 4.0.0-rc18 in /webapp [`#2373`](https://github.com/Human-Connection/Human-Connection/pull/2373)
+- build(deps): bump graphql-shield from 7.0.2 to 7.0.4 in /backend [`#2372`](https://github.com/Human-Connection/Human-Connection/pull/2372)
+- build(deps-dev): bump cypress from 3.6.1 to 3.7.0 [`#2371`](https://github.com/Human-Connection/Human-Connection/pull/2371)
+- build(deps-dev): bump @babel/core from 7.7.2 to 7.7.4 in /backend [`#2359`](https://github.com/Human-Connection/Human-Connection/pull/2359)
+- build(deps): bump apollo-server from 2.9.11 to 2.9.12 in /backend [`#2357`](https://github.com/Human-Connection/Human-Connection/pull/2357)
+- build(deps-dev): bump eslint-plugin-jest from 23.0.4 to 23.0.5 in /webapp [`#2369`](https://github.com/Human-Connection/Human-Connection/pull/2369)
+- build(deps): bump @hapi/joi from 16.1.7 to 16.1.8 in /backend [`#2368`](https://github.com/Human-Connection/Human-Connection/pull/2368)
+- build(deps-dev): bump eslint-plugin-jest from 23.0.4 to 23.0.5 in /backend [`#2365`](https://github.com/Human-Connection/Human-Connection/pull/2365)
+- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.2.0 to 7.7.4 in /backend [`#2339`](https://github.com/Human-Connection/Human-Connection/pull/2339)
+- refactor: Close session in isAuthor permission [`#2334`](https://github.com/Human-Connection/Human-Connection/pull/2334)
+- build(deps): bump date-fns from 2.7.0 to 2.8.1 in /webapp [`#2323`](https://github.com/Human-Connection/Human-Connection/pull/2323)
+- 1967 component tests content view [`#2169`](https://github.com/Human-Connection/Human-Connection/pull/2169)
+- If an admin searches for a user by email, don't crash if no user can be found [`#2295`](https://github.com/Human-Connection/Human-Connection/pull/2295)
+- Migrate styleguide icons [`#2288`](https://github.com/Human-Connection/Human-Connection/pull/2288)
+- build(deps-dev): bump eslint from 6.6.0 to 6.7.1 in /backend [`#2358`](https://github.com/Human-Connection/Human-Connection/pull/2358)
+- build(deps-dev): bump @babel/preset-env from 7.7.1 to 7.7.4 in /backend [`#2341`](https://github.com/Human-Connection/Human-Connection/pull/2341)
+- build(deps-dev): bump @babel/core from 7.7.2 to 7.7.4 in /webapp [`#2340`](https://github.com/Human-Connection/Human-Connection/pull/2340)
+- build(deps): bump date-fns from 2.7.0 to 2.8.1 in /backend [`#2322`](https://github.com/Human-Connection/Human-Connection/pull/2322)
+- build(deps): bump validator from 12.0.0 to 12.1.0 in /webapp [`#2319`](https://github.com/Human-Connection/Human-Connection/pull/2319)
+- Update to version 0.1.11 with bug fixes [`#2354`](https://github.com/Human-Connection/Human-Connection/pull/2354)
- Fix updating post by adding/changing image bug submits form [`#2350`](https://github.com/Human-Connection/Human-Connection/pull/2350)
- Add shoutedBy_some to _PostFilter [`#2353`](https://github.com/Human-Connection/Human-Connection/pull/2353)
- build(deps-dev): bump date-fns from 2.8.0 to 2.8.1 [`#2342`](https://github.com/Human-Connection/Human-Connection/pull/2342)
@@ -71,7 +192,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- build(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /webapp [`#2205`](https://github.com/Human-Connection/Human-Connection/pull/2205)
- Add locale to undefined to null [`#2233`](https://github.com/Human-Connection/Human-Connection/pull/2233)
- Update to version 0.1.10 [`#2231`](https://github.com/Human-Connection/Human-Connection/pull/2231)
+- Merge pull request #2443 from Human-Connection/2237-longer-comments [`#2237`](https://github.com/Human-Connection/Human-Connection/issues/2237)
- fix #2329: Normalize email on login in the backend [`#2329`](https://github.com/Human-Connection/Human-Connection/issues/2329)
+- Fix #2294 [`#2294`](https://github.com/Human-Connection/Human-Connection/issues/2294)
- Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042)
- Tell github-linguists to ignore snapshots [`978347b`](https://github.com/Human-Connection/Human-Connection/commit/978347ba7b5a6aa1bc915ada972ffffa2816d37c)
- Lokalise: update of webapp/locales/ru.json [`906e851`](https://github.com/Human-Connection/Human-Connection/commit/906e8518bf060134150187fb1574ac50ffd502f6)
@@ -135,8 +258,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- Update feature template [`#2116`](https://github.com/Human-Connection/Human-Connection/pull/2116)
- Update to version 0.1.9 [`#2114`](https://github.com/Human-Connection/Human-Connection/pull/2114)
- remove package-lock.json [`3cf3c31`](https://github.com/Human-Connection/Human-Connection/commit/3cf3c31808dc6ae59fb9c6ec33e9e178c5556438)
-- add current file [`26c0d4d`](https://github.com/Human-Connection/Human-Connection/commit/26c0d4d83e4418a2378e05b66b6b47461f82735f)
-- Finish portuguese translations [`15c671c`](https://github.com/Human-Connection/Human-Connection/commit/15c671c4a8aae86317896ca30601389504bce9e1)
+- Extract AvatarMenu into its own component [`994a0b0`](https://github.com/Human-Connection/Human-Connection/commit/994a0b049d1803784d9c06383872f1c9e33095a0)
+- Add notifications page with Notifications in table [`7cdc12f`](https://github.com/Human-Connection/Human-Connection/commit/7cdc12f4b9943062e15a874dd39f8a50142b6c61)
#### [v0.1.9](https://github.com/Human-Connection/Human-Connection/compare/v0.1.8...v0.1.9)
@@ -200,9 +323,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- Fix #2042 Back Link To Login Page [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042)
- Merge pull request #2043 from Human-Connection/fix-1993 [`#1993`](https://github.com/Human-Connection/Human-Connection/issues/1993)
- fix #1993 [`#1993`](https://github.com/Human-Connection/Human-Connection/issues/1993)
+- Prepare backend for next implementation step [`7b32243`](https://github.com/Human-Connection/Human-Connection/commit/7b3224327e67a2895e4bc15b8987b13c6f57f015)
- first implementation [`aeae72f`](https://github.com/Human-Connection/Human-Connection/commit/aeae72f6918861aa2a4c64d0b32c847d9e857e93)
- build(deps-dev): bump eslint-plugin-jest in /backend [`6c1bd53`](https://github.com/Human-Connection/Human-Connection/commit/6c1bd535ac482eb0a05d21e227a476800717a19e)
-- 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)
@@ -224,7 +347,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- 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)
+- Finish redesign of moderators report list [`15d28aa`](https://github.com/Human-Connection/Human-Connection/commit/15d28aa8ef84788aa640aac67838380bfacf63b7)
#### [0.1.7](https://github.com/Human-Connection/Human-Connection/compare/0.1.6...0.1.7)
diff --git a/VERSION b/VERSION
index 20f49513e..7ac4e5e38 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.11
+0.1.13
diff --git a/backend/package.json b/backend/package.json
index d1045a577..71536ac78 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -33,9 +33,9 @@
},
"dependencies": {
"@hapi/joi": "^16.1.8",
- "@sentry/node": "^5.10.1",
- "apollo-cache-inmemory": "~1.6.3",
- "apollo-client": "~2.6.4",
+ "@sentry/node": "^5.10.2",
+ "apollo-cache-inmemory": "~1.6.5",
+ "apollo-client": "~2.6.8",
"apollo-link-context": "~1.0.19",
"apollo-link-http": "~1.5.16",
"apollo-server": "~2.9.13",
@@ -63,28 +63,28 @@
"lodash": "~4.17.14",
"merge-graphql-schemas": "^1.7.3",
"metascraper": "^5.8.9",
- "metascraper-audio": "^5.8.7",
+ "metascraper-audio": "^5.8.10",
"metascraper-author": "^5.8.7",
"metascraper-clearbit-logo": "^5.3.0",
"metascraper-date": "^5.8.7",
- "metascraper-description": "^5.8.7",
- "metascraper-image": "^5.8.7",
- "metascraper-lang": "^5.8.9",
+ "metascraper-description": "^5.8.10",
+ "metascraper-image": "^5.8.10",
+ "metascraper-lang": "^5.8.10",
"metascraper-lang-detector": "^4.10.2",
- "metascraper-logo": "^5.8.7",
+ "metascraper-logo": "^5.8.10",
"metascraper-publisher": "^5.8.7",
- "metascraper-soundcloud": "^5.8.9",
- "metascraper-title": "^5.8.7",
+ "metascraper-soundcloud": "^5.8.10",
+ "metascraper-title": "^5.8.10",
"metascraper-url": "^5.8.7",
- "metascraper-video": "^5.8.9",
- "metascraper-youtube": "^5.8.9",
+ "metascraper-video": "^5.8.10",
+ "metascraper-youtube": "^5.8.10",
"minimatch": "^3.0.4",
"mustache": "^3.1.0",
"neo4j-driver": "~1.7.6",
- "neo4j-graphql-js": "^2.10.0",
- "neode": "^0.3.3",
+ "neo4j-graphql-js": "^2.10.2",
+ "neode": "^0.3.6",
"node-fetch": "~2.6.0",
- "nodemailer": "^6.4.1",
+ "nodemailer": "^6.4.2",
"nodemailer-html-to-text": "^3.1.0",
"npm-run-all": "~4.1.5",
"request": "~2.88.0",
@@ -97,11 +97,11 @@
"xregexp": "^4.2.4"
},
"devDependencies": {
- "@babel/cli": "~7.7.4",
+ "@babel/cli": "~7.7.5",
"@babel/core": "~7.7.5",
"@babel/node": "~7.7.4",
"@babel/plugin-proposal-throw-expressions": "^7.7.4",
- "@babel/preset-env": "~7.7.4",
+ "@babel/preset-env": "~7.7.6",
"@babel/register": "~7.7.0",
"apollo-server-testing": "~2.9.13",
"babel-core": "~7.0.0-0",
@@ -112,14 +112,14 @@
"eslint": "~6.7.2",
"eslint-config-prettier": "~6.7.0",
"eslint-config-standard": "~14.1.0",
- "eslint-plugin-import": "~2.18.2",
+ "eslint-plugin-import": "~2.19.1",
"eslint-plugin-jest": "~23.1.1",
"eslint-plugin-node": "~10.0.0",
- "eslint-plugin-prettier": "~3.1.1",
+ "eslint-plugin-prettier": "~3.1.2",
"eslint-plugin-promise": "~4.2.1",
"eslint-plugin-standard": "~4.0.1",
"jest": "~24.9.0",
- "nodemon": "~2.0.1",
+ "nodemon": "~2.0.2",
"prettier": "~1.19.1",
"supertest": "~4.0.2"
}
diff --git a/backend/src/bootstrap/neo4j.js b/backend/src/bootstrap/neo4j.js
index f9e3a997d..404e8a2c0 100644
--- a/backend/src/bootstrap/neo4j.js
+++ b/backend/src/bootstrap/neo4j.js
@@ -1,15 +1,17 @@
import { v1 as neo4j } from 'neo4j-driver'
import CONFIG from './../config'
-import setupNeode from './neode'
+import Neode from 'neode'
+import models from '../models'
let driver
+const defaultOptions = {
+ uri: CONFIG.NEO4J_URI,
+ username: CONFIG.NEO4J_USERNAME,
+ password: CONFIG.NEO4J_PASSWORD,
+}
export function getDriver(options = {}) {
- const {
- uri = CONFIG.NEO4J_URI,
- username = CONFIG.NEO4J_USERNAME,
- password = CONFIG.NEO4J_PASSWORD,
- } = options
+ const { uri, username, password } = { ...defaultOptions, ...options }
if (!driver) {
driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
}
@@ -17,10 +19,11 @@ export function getDriver(options = {}) {
}
let neodeInstance
-export function neode() {
+export function getNeode(options = {}) {
if (!neodeInstance) {
- const { NEO4J_URI: uri, NEO4J_USERNAME: username, NEO4J_PASSWORD: password } = CONFIG
- neodeInstance = setupNeode({ uri, username, password })
+ const { uri, username, password } = { ...defaultOptions, ...options }
+ neodeInstance = new Neode(uri, username, password).with(models)
+ return neodeInstance
}
return neodeInstance
}
diff --git a/backend/src/bootstrap/neode.js b/backend/src/bootstrap/neode.js
deleted file mode 100644
index 65a2074be..000000000
--- a/backend/src/bootstrap/neode.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import Neode from 'neode'
-import models from '../models'
-
-export default function setupNeode(options) {
- const { uri, username, password } = options
- const neodeInstance = new Neode(uri, username, password)
- neodeInstance.with(models)
- return neodeInstance
-}
diff --git a/backend/src/jwt/decode.js b/backend/src/jwt/decode.js
index 5b7881d20..5433a8c76 100644
--- a/backend/src/jwt/decode.js
+++ b/backend/src/jwt/decode.js
@@ -11,27 +11,28 @@ export default async (driver, authorizationHeader) => {
} catch (err) {
return null
}
- const query = `
- MATCH (user:User {id: $id, deleted: false, disabled: false })
- SET user.lastActiveAt = toString(datetime())
- RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
- LIMIT 1
- `
const session = driver.session()
- let result
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const updateUserLastActiveTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {id: $id, deleted: false, disabled: false })
+ SET user.lastActiveAt = toString(datetime())
+ RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
+ LIMIT 1
+ `,
+ { id },
+ )
+ return updateUserLastActiveTransactionResponse.records.map(record => record.get('user'))
+ })
try {
- result = await session.run(query, { id })
+ const [currentUser] = await writeTxResultPromise
+ if (!currentUser) return null
+ return {
+ token,
+ ...currentUser,
+ }
} finally {
session.close()
}
-
- const [currentUser] = await result.records.map(record => {
- return record.get('user')
- })
- if (!currentUser) return null
- return {
- token,
- ...currentUser,
- }
}
diff --git a/backend/src/jwt/decode.spec.js b/backend/src/jwt/decode.spec.js
index 9ea858304..7aa703d97 100644
--- a/backend/src/jwt/decode.spec.js
+++ b/backend/src/jwt/decode.spec.js
@@ -1,5 +1,5 @@
import Factory from '../seed/factories/index'
-import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
+import { getDriver, getNeode } from '../bootstrap/neo4j'
import decode from './decode'
const factory = Factory()
diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.js b/backend/src/middleware/hashtags/hashtagsMiddleware.js
index 53a8fed20..7d8593fd5 100644
--- a/backend/src/middleware/hashtags/hashtagsMiddleware.js
+++ b/backend/src/middleware/hashtags/hashtagsMiddleware.js
@@ -2,30 +2,23 @@ import extractHashtags from '../hashtags/extractHashtags'
const updateHashtagsOfPost = async (postId, hashtags, context) => {
if (!hashtags.length) return
-
- // We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
- // functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
- // and no new Hashtags and relations will be created.
- const cypherDeletePreviousRelations = `
- MATCH (p: Post { id: $postId })-[previousRelations: TAGGED]->(t: Tag)
- DELETE previousRelations
- RETURN p, t
- `
- const cypherCreateNewTagsAndRelations = `
- MATCH (p: Post { id: $postId})
- UNWIND $hashtags AS tagName
- MERGE (t: Tag { id: tagName, disabled: false, deleted: false })
- MERGE (p)-[:TAGGED]->(t)
- RETURN p, t
- `
const session = context.driver.session()
+
try {
- await session.run(cypherDeletePreviousRelations, {
- postId,
- })
- await session.run(cypherCreateNewTagsAndRelations, {
- postId,
- hashtags,
+ await session.writeTransaction(txc => {
+ return txc.run(
+ `
+ MATCH (post:Post { id: $postId})
+ OPTIONAL MATCH (post)-[previousRelations:TAGGED]->(tag:Tag)
+ DELETE previousRelations
+ WITH post
+ UNWIND $hashtags AS tagName
+ MERGE (tag:Tag {id: tagName, disabled: false, deleted: false })
+ MERGE (post)-[:TAGGED]->(tag)
+ RETURN post, tag
+ `,
+ { postId, hashtags },
+ )
})
} finally {
session.close()
diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js
index 6e97f34c4..0fa1e2dc5 100644
--- a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js
+++ b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js
@@ -1,7 +1,7 @@
import { gql } from '../../helpers/jest'
import Factory from '../../seed/factories'
import { createTestClient } from 'apollo-server-testing'
-import { neode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
let server
@@ -11,7 +11,7 @@ let hashtagingUser
let authenticatedUser
const factory = Factory()
const driver = getDriver()
-const instance = neode()
+const neode = getNeode()
const categoryIds = ['cat9']
const createPostMutation = gql`
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
@@ -36,7 +36,7 @@ beforeAll(() => {
context: () => {
return {
user: authenticatedUser,
- neode: instance,
+ neode,
driver,
}
},
@@ -48,14 +48,14 @@ beforeAll(() => {
})
beforeEach(async () => {
- hashtagingUser = await instance.create('User', {
+ hashtagingUser = await neode.create('User', {
id: 'you',
name: 'Al Capone',
slug: 'al-capone',
email: 'test@example.org',
password: '1234',
})
- await instance.create('Category', {
+ await neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js
index d09a96475..9c68d8c00 100644
--- a/backend/src/middleware/index.js
+++ b/backend/src/middleware/index.js
@@ -7,7 +7,7 @@ import sluggify from './sluggifyMiddleware'
import excerpt from './excerptMiddleware'
import xss from './xssMiddleware'
import permissions from './permissionsMiddleware'
-import user from './userMiddleware'
+import user from './user/userMiddleware'
import includedFields from './includedFieldsMiddleware'
import orderBy from './orderByMiddleware'
import validation from './validation/validationMiddleware'
diff --git a/backend/src/middleware/nodes/locations.js b/backend/src/middleware/nodes/locations.js
index 3e0ca6855..47262d7ba 100644
--- a/backend/src/middleware/nodes/locations.js
+++ b/backend/src/middleware/nodes/locations.js
@@ -38,7 +38,7 @@ const createLocation = async (session, mapboxData) => {
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
}
- let query =
+ let mutation =
'MERGE (l:Location {id: $id}) ' +
'SET l.name = $nameEN, ' +
'l.nameEN = $nameEN, ' +
@@ -53,19 +53,23 @@ const createLocation = async (session, mapboxData) => {
'l.type = $type'
if (data.lat && data.lng) {
- query += ', l.lat = $lat, l.lng = $lng'
+ mutation += ', l.lat = $lat, l.lng = $lng'
}
- query += ' RETURN l.id'
+ mutation += ' RETURN l.id'
- await session.run(query, data)
- session.close()
+ try {
+ await session.writeTransaction(transaction => {
+ return transaction.run(mutation, data)
+ })
+ } finally {
+ session.close()
+ }
}
const createOrUpdateLocations = async (userId, locationName, driver) => {
if (isEmpty(locationName)) {
return
}
-
const res = await fetch(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
locationName,
@@ -106,33 +110,44 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
if (data.context) {
await asyncForEach(data.context, async ctx => {
await createLocation(session, ctx)
-
- await session.run(
- 'MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) ' +
- 'MERGE (child)<-[:IS_IN]-(parent) ' +
- 'RETURN child.id, parent.id',
- {
- parentId: parent.id,
- childId: ctx.id,
- },
- )
-
- parent = ctx
+ try {
+ await session.writeTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
+ MERGE (child)<-[:IS_IN]-(parent)
+ RETURN child.id, parent.id
+ `,
+ {
+ parentId: parent.id,
+ childId: ctx.id,
+ },
+ )
+ })
+ parent = ctx
+ } finally {
+ session.close()
+ }
})
}
- // delete all current locations from user
- await session.run('MATCH (u:User {id: $userId})-[r:IS_IN]->(l:Location) DETACH DELETE r', {
- userId: userId,
- })
- // connect user with location
- await session.run(
- 'MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id',
- {
- userId: userId,
- locationId: data.id,
- },
- )
- session.close()
+ // delete all current locations from user and add new location
+ try {
+ await session.writeTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location)
+ DETACH DELETE relationship
+ WITH user
+ MATCH (location:Location {id: $locationId})
+ MERGE (user)-[:IS_IN]->(location)
+ RETURN location.id, user.id
+ `,
+ { userId: userId, locationId: data.id },
+ )
+ })
+ } finally {
+ session.close()
+ }
}
export default createOrUpdateLocations
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js
index ac199a67d..837193773 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.js
+++ b/backend/src/middleware/notifications/notificationsMiddleware.js
@@ -1,164 +1,121 @@
import extractMentionedUsers from './mentions/extractMentionedUsers'
+import { validateNotifyUsers } from '../validation/validationMiddleware'
-const postAuthorOfComment = async (comment, { context }) => {
- const cypherFindUser = `
- MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
- RETURN user { .id }
- `
+const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
+ const idsOfUsers = extractMentionedUsers(args.content)
+ const post = await resolve(root, args, context, resolveInfo)
+ if (post && idsOfUsers && idsOfUsers.length)
+ await notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
+ return post
+}
+
+const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
+ const { content } = args
+ let idsOfUsers = extractMentionedUsers(content)
+ const comment = await resolve(root, args, context, resolveInfo)
+ const [postAuthor] = await postAuthorOfComment(comment.id, { context })
+ idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id)
+ if (idsOfUsers && idsOfUsers.length)
+ await notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context)
+ if (context.user.id !== postAuthor.id)
+ await notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context)
+ return comment
+}
+
+const postAuthorOfComment = async (commentId, { context }) => {
const session = context.driver.session()
- let result
+ let postAuthorId
try {
- result = await session.run(cypherFindUser, {
- commentId: comment.id,
+ postAuthorId = await session.readTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (author:User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
+ RETURN author { .id } as authorId
+ `,
+ { commentId },
+ )
})
+ return postAuthorId.records.map(record => record.get('authorId'))
} finally {
session.close()
}
- const [postAuthor] = await result.records.map(record => {
- return record.get('user')
- })
- return postAuthor
}
-const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
- if (!idsOfUsers.length) return
-
- // Checked here, because it does not go through GraphQL checks at all in this file.
- const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
- if (!reasonsAllowed.includes(reason)) {
- throw new Error('Notification reason is not allowed!')
- }
- if (
- (label === 'Post' && reason !== 'mentioned_in_post') ||
- (label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
- ) {
- throw new Error('Notification does not fit the reason!')
- }
-
- let cypher
+const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
+ await validateNotifyUsers(label, reason)
+ let mentionedCypher
switch (reason) {
case 'mentioned_in_post': {
- cypher = `
+ mentionedCypher = `
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
MATCH (user: User)
WHERE user.id in $idsOfUsers
AND NOT (user)<-[:BLOCKED]-(author)
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
- SET notification.read = FALSE
- SET (
- CASE
- WHEN notification.createdAt IS NULL
- THEN notification END ).createdAt = toString(datetime())
- SET notification.updatedAt = toString(datetime())
`
break
}
case 'mentioned_in_comment': {
- cypher = `
- MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
- MATCH (user: User)
- WHERE user.id in $idsOfUsers
- AND NOT (user)<-[:BLOCKED]-(author)
- AND NOT (user)<-[:BLOCKED]-(postAuthor)
- MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
- SET notification.read = FALSE
- SET (
- CASE
- WHEN notification.createdAt IS NULL
- THEN notification END ).createdAt = toString(datetime())
- SET notification.updatedAt = toString(datetime())
- `
- break
- }
- case 'commented_on_post': {
- cypher = `
- MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
- MATCH (user: User)
- WHERE user.id in $idsOfUsers
- AND NOT (user)<-[:BLOCKED]-(author)
- AND NOT (author)<-[:BLOCKED]-(user)
- MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
- SET notification.read = FALSE
- SET (
- CASE
- WHEN notification.createdAt IS NULL
- THEN notification END ).createdAt = toString(datetime())
- SET notification.updatedAt = toString(datetime())
+ mentionedCypher = `
+ MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
+ MATCH (user: User)
+ WHERE user.id in $idsOfUsers
+ AND NOT (user)<-[:BLOCKED]-(author)
+ AND NOT (user)<-[:BLOCKED]-(postAuthor)
+ MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
`
break
}
}
+ mentionedCypher += `
+ SET notification.read = FALSE
+ SET (
+ CASE
+ WHEN notification.createdAt IS NULL
+ THEN notification END ).createdAt = toString(datetime())
+ SET notification.updatedAt = toString(datetime())
+ `
const session = context.driver.session()
try {
- await session.run(cypher, {
- id,
- idsOfUsers,
- reason,
+ await session.writeTransaction(transaction => {
+ return transaction.run(mentionedCypher, { id, idsOfUsers, reason })
})
} finally {
session.close()
}
}
-const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
- const idsOfUsers = extractMentionedUsers(args.content)
+const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
+ await validateNotifyUsers(label, reason)
+ const session = context.driver.session()
- const post = await resolve(root, args, context, resolveInfo)
-
- if (post) {
- await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
- }
-
- return post
-}
-
-const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
- 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)
- }
-
- return comment
-}
-
-const handleCreateComment = async (resolve, root, args, context, resolveInfo) => {
- const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo)
-
- if (comment) {
- const cypherFindUser = `
- MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
- RETURN user { .id }
- `
- const session = context.driver.session()
- let result
- try {
- result = await session.run(cypherFindUser, {
- commentId: comment.id,
- })
- } finally {
- session.close()
- }
- const [postAuthor] = await result.records.map(record => {
- return record.get('user')
+ try {
+ await session.writeTransaction(async transaction => {
+ await transaction.run(
+ `
+ MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
+ WHERE NOT (postAuthor)-[:BLOCKED]-(commenter)
+ MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor)
+ SET notification.read = FALSE
+ SET (
+ CASE
+ WHEN notification.createdAt IS NULL
+ THEN notification END ).createdAt = toString(datetime())
+ SET notification.updatedAt = toString(datetime())
+ `,
+ { commentId, postAuthorId, reason },
+ )
})
- if (context.user.id !== postAuthor.id) {
- await notifyUsers('Comment', comment.id, [postAuthor.id], 'commented_on_post', context)
- }
+ } finally {
+ session.close()
}
-
- return comment
}
export default {
Mutation: {
CreatePost: handleContentDataOfPost,
UpdatePost: handleContentDataOfPost,
- CreateComment: handleCreateComment,
+ CreateComment: handleContentDataOfComment,
UpdateComment: handleContentDataOfComment,
},
}
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js
index 2122d009b..c5f5990d3 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js
+++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js
@@ -1,17 +1,13 @@
import { gql } from '../../helpers/jest'
import Factory from '../../seed/factories'
import { createTestClient } from 'apollo-server-testing'
-import { neode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
-let server
-let query
-let mutate
-let notifiedUser
-let authenticatedUser
+let server, query, mutate, notifiedUser, authenticatedUser
const factory = Factory()
const driver = getDriver()
-const instance = neode()
+const neode = getNeode()
const categoryIds = ['cat9']
const createPostMutation = gql`
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
@@ -39,12 +35,13 @@ const createCommentMutation = gql`
}
`
-beforeAll(() => {
+beforeAll(async () => {
+ await factory.cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
- neode: instance,
+ neode: neode,
driver,
}
},
@@ -56,14 +53,14 @@ beforeAll(() => {
})
beforeEach(async () => {
- notifiedUser = await instance.create('User', {
+ notifiedUser = await neode.create('User', {
id: 'you',
name: 'Al Capone',
slug: 'al-capone',
email: 'test@example.org',
password: '1234',
})
- await instance.create('Category', {
+ await neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
@@ -146,7 +143,7 @@ describe('notifications', () => {
describe('commenter is not me', () => {
beforeEach(async () => {
commentContent = 'Commenters comment.'
- commentAuthor = await instance.create('User', {
+ commentAuthor = await neode.create('User', {
id: 'commentAuthor',
name: 'Mrs Comment',
slug: 'mrs-comment',
@@ -173,7 +170,6 @@ describe('notifications', () => {
],
},
})
- const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@@ -190,7 +186,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -214,7 +210,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -228,7 +224,7 @@ describe('notifications', () => {
})
beforeEach(async () => {
- postAuthor = await instance.create('User', {
+ postAuthor = await neode.create('User', {
id: 'postAuthor',
name: 'Mrs Post',
slug: 'mrs-post',
@@ -265,7 +261,7 @@ describe('notifications', () => {
],
},
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -409,7 +405,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -432,7 +428,7 @@ describe('notifications', () => {
beforeEach(async () => {
commentContent =
'One mention about me with @al-capone.'
- commentAuthor = await instance.create('User', {
+ commentAuthor = await neode.create('User', {
id: 'commentAuthor',
name: 'Mrs Comment',
slug: 'mrs-comment',
@@ -442,7 +438,7 @@ describe('notifications', () => {
})
it('sends only one notification with reason mentioned_in_comment', async () => {
- postAuthor = await instance.create('User', {
+ postAuthor = await neode.create('User', {
id: 'MrPostAuthor',
name: 'Mr Author',
slug: 'mr-author',
@@ -467,7 +463,7 @@ describe('notifications', () => {
],
},
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -501,7 +497,7 @@ describe('notifications', () => {
],
},
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
@@ -518,7 +514,7 @@ describe('notifications', () => {
await postAuthor.relateTo(notifiedUser, 'blocked')
commentContent =
'One mention about me with @al-capone.'
- commentAuthor = await instance.create('User', {
+ commentAuthor = await neode.create('User', {
id: 'commentAuthor',
name: 'Mrs Comment',
slug: 'mrs-comment',
@@ -532,7 +528,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
- const { query } = createTestClient(server)
+
await expect(
query({
query: notificationQuery,
diff --git a/backend/src/middleware/orderByMiddleware.spec.js b/backend/src/middleware/orderByMiddleware.spec.js
index a7b31da0a..129f3a8b4 100644
--- a/backend/src/middleware/orderByMiddleware.spec.js
+++ b/backend/src/middleware/orderByMiddleware.spec.js
@@ -1,6 +1,6 @@
import { gql } from '../helpers/jest'
import Factory from '../seed/factories'
-import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
+import { getNeode, getDriver } from '../bootstrap/neo4j'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../server'
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index 8e4569a52..3b42ae7fe 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -1,11 +1,11 @@
import { rule, shield, deny, allow, or } from 'graphql-shield'
-import { neode } from '../bootstrap/neo4j'
+import { getNeode } from '../bootstrap/neo4j'
import CONFIG from '../config'
const debug = !!CONFIG.DEBUG
const allowExternalErrors = true
-const instance = neode()
+const neode = getNeode()
const isAuthenticated = rule({
cache: 'contextual',
@@ -36,7 +36,7 @@ const isMyOwn = rule({
const isMySocialMedia = rule({
cache: 'no_cache',
})(async (_, args, { user }) => {
- let socialMedia = await instance.find('SocialMedia', args.id)
+ let socialMedia = await neode.find('SocialMedia', args.id)
socialMedia = await socialMedia.toJson()
return socialMedia.ownedBy.node.id === user.id
})
@@ -47,17 +47,18 @@ const isAuthor = rule({
if (!user) return false
const { id: resourceId } = args
const session = driver.session()
- try {
- const result = await session.run(
+ const authorReadTxPromise = session.readTransaction(async transaction => {
+ const authorTransactionResponse = await transaction.run(
`
- MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
- RETURN author
- `,
+ MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
+ RETURN author
+ `,
{ resourceId, userId: user.id },
)
- const [author] = result.records.map(record => {
- return record.get('author')
- })
+ return authorTransactionResponse.records.map(record => record.get('author'))
+ })
+ try {
+ const [author] = await authorReadTxPromise
return !!author
} finally {
session.close()
diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js
index 340766136..60aff961d 100644
--- a/backend/src/middleware/permissionsMiddleware.spec.js
+++ b/backend/src/middleware/permissionsMiddleware.spec.js
@@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
import createServer from '../server'
import Factory from '../seed/factories'
import { gql } from '../helpers/jest'
-import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
+import { getDriver, getNeode } from '../bootstrap/neo4j'
const factory = Factory()
const instance = getNeode()
diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js
index cda3fd335..1cd3c0b9c 100644
--- a/backend/src/middleware/sluggifyMiddleware.js
+++ b/backend/src/middleware/sluggifyMiddleware.js
@@ -4,10 +4,16 @@ const isUniqueFor = (context, type) => {
return async slug => {
const session = context.driver.session()
try {
- const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, {
- slug,
+ const existingSlug = await session.readTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH(p:${type} {slug: $slug })
+ RETURN p.slug
+ `,
+ { slug },
+ )
})
- return response.records.length === 0
+ return existingSlug.records.length === 0
} finally {
session.close()
}
diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js
index 02699f7b2..1c2e59317 100644
--- a/backend/src/middleware/slugifyMiddleware.spec.js
+++ b/backend/src/middleware/slugifyMiddleware.spec.js
@@ -1,6 +1,6 @@
import Factory from '../seed/factories'
import { gql } from '../helpers/jest'
-import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
+import { getNeode, getDriver } from '../bootstrap/neo4j'
import createServer from '../server'
import { createTestClient } from 'apollo-server-testing'
diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js
index 1c97cb874..b7c16dfd3 100644
--- a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js
+++ b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
diff --git a/backend/src/middleware/userMiddleware.js b/backend/src/middleware/user/userMiddleware.js
similarity index 75%
rename from backend/src/middleware/userMiddleware.js
rename to backend/src/middleware/user/userMiddleware.js
index fafbd44e5..2ca61e69f 100644
--- a/backend/src/middleware/userMiddleware.js
+++ b/backend/src/middleware/user/userMiddleware.js
@@ -1,10 +1,10 @@
-import createOrUpdateLocations from './nodes/locations'
+import createOrUpdateLocations from '../nodes/locations'
export default {
Mutation: {
SignupVerification: async (resolve, root, args, context, info) => {
const result = await resolve(root, args, context, info)
- await createOrUpdateLocations(args.id, args.locationName, context.driver)
+ await createOrUpdateLocations(result.id, args.locationName, context.driver)
return result
},
UpdateUser: async (resolve, root, args, context, info) => {
diff --git a/backend/src/middleware/user/userMiddleware.spec.js b/backend/src/middleware/user/userMiddleware.spec.js
new file mode 100644
index 000000000..4ca8fd89f
--- /dev/null
+++ b/backend/src/middleware/user/userMiddleware.spec.js
@@ -0,0 +1,213 @@
+import { gql } from '../../helpers/jest'
+import Factory from '../../seed/factories'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
+import { createTestClient } from 'apollo-server-testing'
+import createServer from '../../server'
+
+const factory = Factory()
+const neode = getNeode()
+const driver = getDriver()
+let authenticatedUser, mutate, variables
+
+const signupVerificationMutation = gql`
+ mutation(
+ $name: String!
+ $password: String!
+ $email: String!
+ $nonce: String!
+ $termsAndConditionsAgreedVersion: String!
+ $locationName: String
+ ) {
+ SignupVerification(
+ name: $name
+ password: $password
+ email: $email
+ nonce: $nonce
+ termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
+ locationName: $locationName
+ ) {
+ locationName
+ }
+ }
+`
+
+const updateUserMutation = gql`
+ mutation($id: ID!, $name: String!, $locationName: String) {
+ UpdateUser(id: $id, name: $name, locationName: $locationName) {
+ locationName
+ }
+ }
+`
+
+let newlyCreatedNodesWithLocales = [
+ {
+ city: {
+ lng: 41.1534,
+ nameES: 'Hamburg',
+ nameFR: 'Hamburg',
+ nameIT: 'Hamburg',
+ nameEN: 'Hamburg',
+ type: 'place',
+ namePT: 'Hamburg',
+ nameRU: 'Хамбург',
+ nameDE: 'Hamburg',
+ nameNL: 'Hamburg',
+ name: 'Hamburg',
+ namePL: 'Hamburg',
+ id: 'place.5977106083398860',
+ lat: -74.5763,
+ },
+ state: {
+ namePT: 'Nova Jérsia',
+ nameRU: 'Нью-Джерси',
+ nameDE: 'New Jersey',
+ nameNL: 'New Jersey',
+ nameES: 'Nueva Jersey',
+ name: 'New Jersey',
+ namePL: 'New Jersey',
+ nameFR: 'New Jersey',
+ nameIT: 'New Jersey',
+ id: 'region.14919479731700330',
+ nameEN: 'New Jersey',
+ type: 'region',
+ },
+ country: {
+ namePT: 'Estados Unidos',
+ nameRU: 'Соединённые Штаты Америки',
+ nameDE: 'Vereinigte Staaten',
+ nameNL: 'Verenigde Staten van Amerika',
+ nameES: 'Estados Unidos',
+ namePL: 'Stany Zjednoczone',
+ name: 'United States of America',
+ nameFR: 'États-Unis',
+ nameIT: "Stati Uniti d'America",
+ id: 'country.9053006287256050',
+ nameEN: 'United States of America',
+ type: 'country',
+ },
+ },
+]
+
+beforeAll(() => {
+ const { server } = createServer({
+ context: () => {
+ return {
+ user: authenticatedUser,
+ neode,
+ driver,
+ }
+ },
+ })
+ mutate = createTestClient(server).mutate
+})
+
+beforeEach(() => {
+ variables = {}
+ authenticatedUser = null
+})
+
+afterEach(() => {
+ factory.cleanDatabase()
+})
+
+describe('userMiddleware', () => {
+ describe('SignupVerification', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ name: 'John Doe',
+ password: '123',
+ email: 'john@example.org',
+ nonce: '123456',
+ termsAndConditionsAgreedVersion: '0.1.0',
+ locationName: 'Hamburg, New Jersey, United States of America',
+ }
+ const args = {
+ email: 'john@example.org',
+ nonce: '123456',
+ }
+ await neode.model('EmailAddress').create(args)
+ })
+ it('creates a Location node with localised city/state/country names', async () => {
+ await mutate({ mutation: signupVerificationMutation, variables })
+ const locations = await neode.cypher(
+ `MATCH (city:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city, state, country`,
+ )
+ expect(
+ locations.records.map(record => {
+ return {
+ city: record.get('city').properties,
+ state: record.get('state').properties,
+ country: record.get('country').properties,
+ }
+ }),
+ ).toEqual(newlyCreatedNodesWithLocales)
+ })
+ })
+
+ describe('UpdateUser', () => {
+ let user, userParams
+ beforeEach(async () => {
+ newlyCreatedNodesWithLocales = [
+ {
+ city: {
+ lng: 53.55,
+ nameES: 'Hamburgo',
+ nameFR: 'Hambourg',
+ nameIT: 'Amburgo',
+ nameEN: 'Hamburg',
+ type: 'region',
+ namePT: 'Hamburgo',
+ nameRU: 'Гамбург',
+ nameDE: 'Hamburg',
+ nameNL: 'Hamburg',
+ namePL: 'Hamburg',
+ name: 'Hamburg',
+ id: 'region.10793468240398860',
+ lat: 10,
+ },
+ country: {
+ namePT: 'Alemanha',
+ nameRU: 'Германия',
+ nameDE: 'Deutschland',
+ nameNL: 'Duitsland',
+ nameES: 'Alemania',
+ name: 'Germany',
+ namePL: 'Niemcy',
+ nameFR: 'Allemagne',
+ nameIT: 'Germania',
+ id: 'country.10743216036480410',
+ nameEN: 'Germany',
+ type: 'country',
+ },
+ },
+ ]
+ userParams = {
+ id: 'updating-user',
+ }
+ user = await factory.create('User', userParams)
+ authenticatedUser = await user.toJson()
+ })
+
+ it('creates a Location node with localised city/state/country names', async () => {
+ variables = {
+ ...variables,
+ id: 'updating-user',
+ name: 'Updating user',
+ locationName: 'Hamburg, Germany',
+ }
+ await mutate({ mutation: updateUserMutation, variables })
+ const locations = await neode.cypher(
+ `MATCH (city:Location)-[:IS_IN]->(country:Location) return city, country`,
+ )
+ expect(
+ locations.records.map(record => {
+ return {
+ city: record.get('city').properties,
+ country: record.get('country').properties,
+ }
+ }),
+ ).toEqual(newlyCreatedNodesWithLocales)
+ })
+ })
+})
diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js
index f36458e61..948e1a73a 100644
--- a/backend/src/middleware/validation/validationMiddleware.js
+++ b/backend/src/middleware/validation/validationMiddleware.js
@@ -4,7 +4,7 @@ const COMMENT_MIN_LENGTH = 1
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
const NO_CATEGORIES_ERR_MESSAGE =
'You cannot save a post without at least one category or more than three'
-
+const USERNAME_MIN_LENGTH = 3
const validateCreateComment = async (resolve, root, args, context, info) => {
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
const { postId } = args
@@ -14,14 +14,15 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
}
const session = context.driver.session()
try {
- const postQueryRes = await session.run(
- `
- MATCH (post:Post {id: $postId})
- RETURN post`,
- {
- postId,
- },
- )
+ const postQueryRes = await session.readTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (post:Post {id: $postId})
+ RETURN post
+ `,
+ { postId },
+ )
+ })
const [post] = postQueryRes.records.map(record => {
return record.get('post')
})
@@ -72,8 +73,8 @@ const validateReview = async (resolve, root, args, context, info) => {
const { user, driver } = context
if (resourceId === user.id) throw new Error('You cannot review yourself!')
const session = driver.session()
- const reportReadTxPromise = session.writeTransaction(async txc => {
- const validateReviewTransactionResponse = await txc.run(
+ const reportReadTxPromise = session.readTransaction(async transaction => {
+ const validateReviewTransactionResponse = await transaction.run(
`
MATCH (resource {id: $resourceId})
WHERE resource:User OR resource:Post OR resource:Comment
@@ -115,12 +116,31 @@ const validateReview = async (resolve, root, args, context, info) => {
return resolve(root, args, context, info)
}
+export const validateNotifyUsers = async (label, reason) => {
+ const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
+ if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!')
+ if (
+ (label === 'Post' && reason !== 'mentioned_in_post') ||
+ (label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
+ ) {
+ throw new Error('Notification does not fit the reason!')
+ }
+}
+
+const validateUpdateUser = async (resolve, root, params, context, info) => {
+ const { name } = params
+ if (typeof name === 'string' && name.trim().length < USERNAME_MIN_LENGTH)
+ throw new UserInputError(`Username must be at least ${USERNAME_MIN_LENGTH} character long!`)
+ return resolve(root, params, context, info)
+}
+
export default {
Mutation: {
CreateComment: validateCreateComment,
UpdateComment: validateUpdateComment,
CreatePost: validatePost,
UpdatePost: validateUpdatePost,
+ UpdateUser: validateUpdateUser,
fileReport: validateReport,
review: validateReview,
},
diff --git a/backend/src/middleware/validation/validationMiddleware.spec.js b/backend/src/middleware/validation/validationMiddleware.spec.js
index 97bb6254b..d093f939a 100644
--- a/backend/src/middleware/validation/validationMiddleware.spec.js
+++ b/backend/src/middleware/validation/validationMiddleware.spec.js
@@ -1,6 +1,6 @@
import { gql } from '../../helpers/jest'
import Factory from '../../seed/factories'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server'
@@ -71,6 +71,14 @@ const reviewMutation = gql`
}
}
`
+
+const updateUserMutation = gql`
+ mutation($id: ID!, $name: String) {
+ UpdateUser(id: $id, name: $name) {
+ name
+ }
+ }
+`
beforeAll(() => {
const { server } = createServer({
context: () => {
@@ -397,4 +405,33 @@ describe('validateReview', () => {
})
})
})
+
+ describe('validateUpdateUser', () => {
+ let userParams, variables, updatingUser
+
+ beforeEach(async () => {
+ userParams = {
+ id: 'updating-user',
+ name: 'John Doe',
+ }
+
+ variables = {
+ id: 'updating-user',
+ name: 'John Doughnut',
+ }
+ updatingUser = await factory.create('User', userParams)
+ authenticatedUser = await updatingUser.toJson()
+ })
+
+ it('with name too short', async () => {
+ variables = {
+ ...variables,
+ name: ' ',
+ }
+ await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
+ data: { UpdateUser: null },
+ errors: [{ message: 'Username must be at least 3 character long!' }],
+ })
+ })
+ })
})
diff --git a/backend/src/models/User.spec.js b/backend/src/models/User.spec.js
index 7c4a26c55..433cc5a6f 100644
--- a/backend/src/models/User.spec.js
+++ b/backend/src/models/User.spec.js
@@ -1,8 +1,8 @@
import Factory from '../seed/factories'
-import { neode } from '../bootstrap/neo4j'
+import { getNeode } from '../bootstrap/neo4j'
const factory = Factory()
-const instance = neode()
+const neode = getNeode()
afterEach(async () => {
await factory.cleanDatabase()
@@ -10,7 +10,7 @@ afterEach(async () => {
describe('role', () => {
it('defaults to `user`', async () => {
- const user = await instance.create('User', { name: 'John' })
+ const user = await neode.create('User', { name: 'John' })
await expect(user.toJson()).resolves.toEqual(
expect.objectContaining({
role: 'user',
@@ -21,7 +21,7 @@ describe('role', () => {
describe('slug', () => {
it('normalizes to lowercase letters', async () => {
- const user = await instance.create('User', { slug: 'Matt' })
+ const user = await neode.create('User', { slug: 'Matt' })
await expect(user.toJson()).resolves.toEqual(
expect.objectContaining({
slug: 'matt',
@@ -30,9 +30,9 @@ describe('slug', () => {
})
it('must be unique', async done => {
- await instance.create('User', { slug: 'Matt' })
+ await neode.create('User', { slug: 'Matt' })
try {
- await expect(instance.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
+ await expect(neode.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
done()
} catch (error) {
throw new Error(`
@@ -54,7 +54,7 @@ describe('slug', () => {
describe('characters', () => {
const createUser = attrs => {
- return instance.create('User', attrs).then(user => user.toJson())
+ return neode.create('User', attrs).then(user => user.toJson())
}
it('-', async () => {
@@ -70,15 +70,11 @@ describe('slug', () => {
})
it(' ', async () => {
- await expect(createUser({ slug: 'matt rider' })).rejects.toThrow(
- /fails to match the required pattern/,
- )
+ await expect(createUser({ slug: 'matt rider' })).rejects.toThrow('ERROR_VALIDATION')
})
it('ä', async () => {
- await expect(createUser({ slug: 'mätt' })).rejects.toThrow(
- /fails to match the required pattern/,
- )
+ await expect(createUser({ slug: 'mätt' })).rejects.toThrow('ERROR_VALIDATION')
})
})
})
diff --git a/backend/src/schema/resolvers/comments.js b/backend/src/schema/resolvers/comments.js
index 97b461511..864d9412c 100644
--- a/backend/src/schema/resolvers/comments.js
+++ b/backend/src/schema/resolvers/comments.js
@@ -5,6 +5,7 @@ export default {
Mutation: {
CreateComment: async (object, params, context, resolveInfo) => {
const { postId } = params
+ const { user, driver } = context
// Adding relationship from comment to post by passing in the postId,
// but we do not want to create the comment with postId as an attribute
// because we use relationships for this. So, we are deleting it from params
@@ -12,26 +13,28 @@ export default {
delete params.postId
params.id = params.id || uuid()
- const session = context.driver.session()
+ const session = driver.session()
+
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const createCommentTransactionResponse = await transaction.run(
+ `
+ MATCH (post:Post {id: $postId})
+ MATCH (author:User {id: $userId})
+ WITH post, author
+ CREATE (comment:Comment {params})
+ SET comment.createdAt = toString(datetime())
+ SET comment.updatedAt = toString(datetime())
+ MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
+ RETURN comment
+ `,
+ { userId: user.id, postId, params },
+ )
+ return createCommentTransactionResponse.records.map(
+ record => record.get('comment').properties,
+ )
+ })
try {
- const createCommentCypher = `
- MATCH (post:Post {id: $postId})
- MATCH (author:User {id: $userId})
- WITH post, author
- CREATE (comment:Comment {params})
- SET comment.createdAt = toString(datetime())
- SET comment.updatedAt = toString(datetime())
- MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
- RETURN comment
- `
- const transactionRes = await session.run(createCommentCypher, {
- userId: context.user.id,
- postId,
- params,
- })
-
- const [comment] = transactionRes.records.map(record => record.get('comment').properties)
-
+ const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
@@ -39,15 +42,22 @@ export default {
},
UpdateComment: async (_parent, params, context, _resolveInfo) => {
const session = context.driver.session()
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const updateCommentTransactionResponse = await transaction.run(
+ `
+ MATCH (comment:Comment {id: $params.id})
+ SET comment += $params
+ SET comment.updatedAt = toString(datetime())
+ RETURN comment
+ `,
+ { params },
+ )
+ return updateCommentTransactionResponse.records.map(
+ record => record.get('comment').properties,
+ )
+ })
try {
- const updateCommentCypher = `
- MATCH (comment:Comment {id: $params.id})
- SET comment += $params
- SET comment.updatedAt = toString(datetime())
- RETURN comment
- `
- const transactionRes = await session.run(updateCommentCypher, { params })
- const [comment] = transactionRes.records.map(record => record.get('comment').properties)
+ const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
@@ -55,18 +65,23 @@ export default {
},
DeleteComment: async (_parent, args, context, _resolveInfo) => {
const session = context.driver.session()
- try {
- const transactionRes = await session.run(
- `
- MATCH (comment:Comment {id: $commentId})
- SET comment.deleted = TRUE
- SET comment.content = 'UNAVAILABLE'
- SET comment.contentExcerpt = 'UNAVAILABLE'
- RETURN comment
- `,
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const deleteCommentTransactionResponse = await transaction.run(
+ `
+ MATCH (comment:Comment {id: $commentId})
+ SET comment.deleted = TRUE
+ SET comment.content = 'UNAVAILABLE'
+ SET comment.contentExcerpt = 'UNAVAILABLE'
+ RETURN comment
+ `,
{ commentId: args.id },
)
- const [comment] = transactionRes.records.map(record => record.get('comment').properties)
+ return deleteCommentTransactionResponse.records.map(
+ record => record.get('comment').properties,
+ )
+ })
+ try {
+ const [comment] = await writeTxResultPromise
return comment
} finally {
session.close()
diff --git a/backend/src/schema/resolvers/comments.spec.js b/backend/src/schema/resolvers/comments.spec.js
index d2692aa8a..f96a60514 100644
--- a/backend/src/schema/resolvers/comments.spec.js
+++ b/backend/src/schema/resolvers/comments.spec.js
@@ -2,7 +2,7 @@ import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
const driver = getDriver()
const neode = getNeode()
@@ -10,7 +10,8 @@ const factory = Factory()
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
-beforeAll(() => {
+beforeAll(async () => {
+ await factory.cleanDatabase()
const { server } = createServer({
context: () => {
return {
@@ -19,8 +20,7 @@ beforeAll(() => {
}
},
})
- const client = createTestClient(server)
- mutate = client.mutate
+ mutate = createTestClient(server).mutate
})
beforeEach(async () => {
@@ -100,6 +100,7 @@ describe('CreateComment', () => {
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
{
data: { CreateComment: { content: "I'm authorised to comment" } },
+ errors: undefined,
},
)
})
@@ -108,6 +109,7 @@ describe('CreateComment', () => {
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
{
data: { CreateComment: { author: { name: 'Author' } } },
+ errors: undefined,
},
)
})
@@ -157,6 +159,7 @@ describe('UpdateComment', () => {
it('updates the comment', async () => {
const expected = {
data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } },
+ errors: undefined,
}
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
expected,
@@ -172,6 +175,7 @@ describe('UpdateComment', () => {
createdAt: expect.any(String),
},
},
+ errors: undefined,
}
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
expected,
diff --git a/backend/src/schema/resolvers/donations.spec.js b/backend/src/schema/resolvers/donations.spec.js
index 9e701059d..d8dd5db06 100644
--- a/backend/src/schema/resolvers/donations.spec.js
+++ b/backend/src/schema/resolvers/donations.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
let mutate, query, authenticatedUser, variables
diff --git a/backend/src/schema/resolvers/emails.spec.js b/backend/src/schema/resolvers/emails.spec.js
index 156007435..82ce43337 100644
--- a/backend/src/schema/resolvers/emails.spec.js
+++ b/backend/src/schema/resolvers/emails.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
+import { getDriver, getNeode } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
diff --git a/backend/src/schema/resolvers/follow.js b/backend/src/schema/resolvers/follow.js
index ada417cff..0416fe3d2 100644
--- a/backend/src/schema/resolvers/follow.js
+++ b/backend/src/schema/resolvers/follow.js
@@ -1,4 +1,4 @@
-import { neode as getNeode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
const neode = getNeode()
diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js
index 8402842e2..ff884666e 100644
--- a/backend/src/schema/resolvers/follow.spec.js
+++ b/backend/src/schema/resolvers/follow.spec.js
@@ -1,6 +1,6 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
-import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
+import { getDriver, getNeode } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { gql } from '../../helpers/jest'
diff --git a/backend/src/schema/resolvers/helpers/Resolver.js b/backend/src/schema/resolvers/helpers/Resolver.js
index 03c0d4176..64ba60f5e 100644
--- a/backend/src/schema/resolvers/helpers/Resolver.js
+++ b/backend/src/schema/resolvers/helpers/Resolver.js
@@ -1,9 +1,9 @@
-import { neode } from '../../../bootstrap/neo4j'
+import log from './databaseLogger'
export const undefinedToNullResolver = list => {
const resolvers = {}
list.forEach(key => {
- resolvers[key] = async (parent, params, context, resolveInfo) => {
+ resolvers[key] = async parent => {
return typeof parent[key] === 'undefined' ? null : parent[key]
}
})
@@ -11,7 +11,6 @@ export const undefinedToNullResolver = list => {
}
export default function Resolver(type, options = {}) {
- const instance = neode()
const {
idAttribute = 'id',
undefinedToNull = [],
@@ -22,32 +21,49 @@ export default function Resolver(type, options = {}) {
} = options
const _hasResolver = (resolvers, { key, connection }, { returnType }) => {
- return async (parent, params, context, resolveInfo) => {
+ return async (parent, params, { driver, cypherParams }, resolveInfo) => {
if (typeof parent[key] !== 'undefined') return parent[key]
const id = parent[idAttribute]
- const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related`
- const result = await instance.cypher(statement, { id })
- let response = result.records.map(r => r.get('related').properties)
- if (returnType === 'object') response = response[0] || null
- return response
+ const session = driver.session()
+ const readTxResultPromise = session.readTransaction(async txc => {
+ const cypher = `
+ MATCH(:${type} {${idAttribute}: $id})${connection}
+ RETURN related {.*} as related
+ `
+ const result = await txc.run(cypher, { id, cypherParams })
+ log(result)
+ return result.records.map(r => r.get('related'))
+ })
+ try {
+ let response = await readTxResultPromise
+ if (returnType === 'object') response = response[0] || null
+ return response
+ } finally {
+ session.close()
+ }
}
}
const booleanResolver = obj => {
const resolvers = {}
for (const [key, condition] of Object.entries(obj)) {
- resolvers[key] = async (parent, params, { cypherParams }, resolveInfo) => {
+ resolvers[key] = async (parent, params, { cypherParams, driver }, resolveInfo) => {
if (typeof parent[key] !== 'undefined') return parent[key]
- const result = await instance.cypher(
- `
- ${condition.replace('this', 'this {id: $parent.id}')} as ${key}`,
- {
- parent,
- cypherParams,
- },
- )
- const [record] = result.records
- return record.get(key)
+ const id = parent[idAttribute]
+ const session = driver.session()
+ const readTxResultPromise = session.readTransaction(async txc => {
+ const nodeCondition = condition.replace('this', 'this {id: $id}')
+ const cypher = `${nodeCondition} as ${key}`
+ const result = await txc.run(cypher, { id, cypherParams })
+ log(result)
+ const [response] = result.records.map(r => r.get(key))
+ return response
+ })
+ try {
+ return await readTxResultPromise
+ } finally {
+ session.close()
+ }
}
}
return resolvers
@@ -56,16 +72,25 @@ export default function Resolver(type, options = {}) {
const countResolver = obj => {
const resolvers = {}
for (const [key, connection] of Object.entries(obj)) {
- resolvers[key] = async (parent, params, context, resolveInfo) => {
+ resolvers[key] = async (parent, params, { driver, cypherParams }, resolveInfo) => {
if (typeof parent[key] !== 'undefined') return parent[key]
- const id = parent[idAttribute]
- const statement = `
- MATCH(u:${type} {${idAttribute}: {id}})${connection}
- RETURN COUNT(DISTINCT(related)) as count
- `
- const result = await instance.cypher(statement, { id })
- const [response] = result.records.map(r => r.get('count').toNumber())
- return response
+ const session = driver.session()
+ const readTxResultPromise = session.readTransaction(async txc => {
+ const id = parent[idAttribute]
+ const cypher = `
+ MATCH(u:${type} {${idAttribute}: $id})${connection}
+ RETURN COUNT(DISTINCT(related)) as count
+ `
+ const result = await txc.run(cypher, { id, cypherParams })
+ log(result)
+ const [response] = result.records.map(r => r.get('count').toNumber())
+ return response
+ })
+ try {
+ return await readTxResultPromise
+ } finally {
+ session.close()
+ }
}
}
return resolvers
diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.js b/backend/src/schema/resolvers/helpers/createPasswordReset.js
index 41214b501..dec55c893 100644
--- a/backend/src/schema/resolvers/helpers/createPasswordReset.js
+++ b/backend/src/schema/resolvers/helpers/createPasswordReset.js
@@ -5,24 +5,29 @@ export default async function createPasswordReset(options) {
const normalizedEmail = normalizeEmail(email)
const session = driver.session()
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 createPasswordResetTxPromise = session.writeTransaction(async transaction => {
+ const createPasswordResetTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email:$email})
+ CREATE(passwordReset:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL})
+ MERGE (user)-[:REQUESTED]->(passwordReset)
+ RETURN email, passwordReset, user
+ `,
+ {
+ issuedAt: issuedAt.toISOString(),
+ nonce,
+ email: normalizedEmail,
+ },
+ )
+ return createPasswordResetTransactionResponse.records.map(record => {
+ const { email } = record.get('email').properties
+ const { nonce } = record.get('passwordReset').properties
+ const { name } = record.get('user').properties
+ return { email, nonce, name }
+ })
})
- const records = 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 }
- })
- return records[0] || {}
+ const [records] = await createPasswordResetTxPromise
+ return records || {}
} finally {
session.close()
}
diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js b/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js
deleted file mode 100644
index a566e225a..000000000
--- a/backend/src/schema/resolvers/helpers/createPasswordReset.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-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/databaseLogger.js b/backend/src/schema/resolvers/helpers/databaseLogger.js
new file mode 100644
index 000000000..1e97b4d72
--- /dev/null
+++ b/backend/src/schema/resolvers/helpers/databaseLogger.js
@@ -0,0 +1,15 @@
+import Debug from 'debug'
+const debugCypher = Debug('human-connection:neo4j:cypher')
+const debugStats = Debug('human-connection:neo4j:stats')
+
+export default function log(response) {
+ const { statement, counters, resultConsumedAfter, resultAvailableAfter } = response.summary
+ const { text, parameters } = statement
+ debugCypher('%s', text)
+ debugCypher('%o', parameters)
+ debugStats('%o', counters)
+ debugStats('%o', {
+ resultConsumedAfter: resultConsumedAfter.toNumber(),
+ resultAvailableAfter: resultAvailableAfter.toNumber(),
+ })
+}
diff --git a/backend/src/schema/resolvers/helpers/existingEmailAddress.js b/backend/src/schema/resolvers/helpers/existingEmailAddress.js
index ee1a6af82..960b2066f 100644
--- a/backend/src/schema/resolvers/helpers/existingEmailAddress.js
+++ b/backend/src/schema/resolvers/helpers/existingEmailAddress.js
@@ -1,25 +1,29 @@
import { UserInputError } from 'apollo-server'
export default async function alreadyExistingMail({ args, context }) {
- const cypher = `
- MATCH (email:EmailAddress {email: $email})
- OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
- RETURN email, user
- `
- let transactionRes
const session = context.driver.session()
try {
- transactionRes = await session.run(cypher, { email: args.email })
+ const existingEmailAddressTxPromise = session.writeTransaction(async transaction => {
+ const existingEmailAddressTransactionResponse = await transaction.run(
+ `
+ MATCH (email:EmailAddress {email: $email})
+ OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
+ RETURN email, user
+ `,
+ { email: args.email },
+ )
+ return existingEmailAddressTransactionResponse.records.map(record => {
+ return {
+ alreadyExistingEmail: record.get('email').properties,
+ user: record.get('user') && record.get('user').properties,
+ }
+ })
+ })
+ const [emailBelongsToUser] = await existingEmailAddressTxPromise
+ const { alreadyExistingEmail, user } = emailBelongsToUser || {}
+ if (user) throw new UserInputError('A user account with this email already exists.')
+ return alreadyExistingEmail
} finally {
session.close()
}
- const [result] = transactionRes.records.map(record => {
- return {
- alreadyExistingEmail: record.get('email').properties,
- user: record.get('user') && record.get('user').properties,
- }
- })
- const { alreadyExistingEmail, user } = result || {}
- if (user) throw new UserInputError('A user account with this email already exists.')
- return alreadyExistingEmail
}
diff --git a/backend/src/schema/resolvers/locations.spec.js b/backend/src/schema/resolvers/locations.spec.js
index 51dafcc2e..f4a846afd 100644
--- a/backend/src/schema/resolvers/locations.spec.js
+++ b/backend/src/schema/resolvers/locations.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
diff --git a/backend/src/schema/resolvers/moderation.spec.js b/backend/src/schema/resolvers/moderation.spec.js
index 5e280a6f5..f76cbdf46 100644
--- a/backend/src/schema/resolvers/moderation.spec.js
+++ b/backend/src/schema/resolvers/moderation.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
const factory = Factory()
diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js
index 7f9c52e1e..31369a8c7 100644
--- a/backend/src/schema/resolvers/notifications.js
+++ b/backend/src/schema/resolvers/notifications.js
@@ -1,3 +1,5 @@
+import log from './helpers/databaseLogger'
+
const resourceTypes = ['Post', 'Comment']
const transformReturnType = record => {
@@ -42,16 +44,29 @@ export default {
}
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : ''
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : ''
- const cypher = `
- MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
- ${whereClause}
- RETURN resource, notification, user
- ${orderByClause}
- ${offset} ${limit}
- `
+
+ const readTxResultPromise = session.readTransaction(async transaction => {
+ const notificationsTransactionResponse = await transaction.run(
+ `
+ MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
+ ${whereClause}
+ WITH user, notification, resource,
+ [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors,
+ [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] as posts
+ WITH resource, user, notification, authors, posts,
+ resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} as finalResource
+ RETURN notification {.*, from: finalResource, to: properties(user)}
+ ${orderByClause}
+ ${offset} ${limit}
+ `,
+ { id: currentUser.id },
+ )
+ log(notificationsTransactionResponse)
+ return notificationsTransactionResponse.records.map(record => record.get('notification'))
+ })
try {
- const result = await session.run(cypher, { id: currentUser.id })
- return result.records.map(transformReturnType)
+ const notifications = await readTxResultPromise
+ return notifications
} finally {
session.close()
}
@@ -61,15 +76,21 @@ export default {
markAsRead: async (parent, args, context, resolveInfo) => {
const { user: currentUser } = context
const session = context.driver.session()
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const markNotificationAsReadTransactionResponse = await transaction.run(
+ `
+ MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
+ SET notification.read = TRUE
+ RETURN resource, notification, user
+ `,
+ { resourceId: args.id, id: currentUser.id },
+ )
+ log(markNotificationAsReadTransactionResponse)
+ return markNotificationAsReadTransactionResponse.records.map(transformReturnType)
+ })
try {
- const cypher = `
- MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
- SET notification.read = TRUE
- RETURN resource, notification, user
- `
- const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id })
- const notifications = await result.records.map(transformReturnType)
- return notifications[0]
+ const [notifications] = await writeTxResultPromise
+ return notifications
} finally {
session.close()
}
diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js
index 24b8280bd..89bbd2528 100644
--- a/backend/src/schema/resolvers/notifications.spec.js
+++ b/backend/src/schema/resolvers/notifications.spec.js
@@ -184,6 +184,7 @@ describe('given some notifications', () => {
data: {
notifications: expect.arrayContaining(expected),
},
+ errors: undefined,
})
})
})
@@ -233,7 +234,10 @@ describe('given some notifications', () => {
`
await expect(
mutate({ mutation: deletePostMutation, variables: { id: 'p3' } }),
- ).resolves.toMatchObject({ data: { DeletePost: { id: 'p3', deleted: true } } })
+ ).resolves.toMatchObject({
+ data: { DeletePost: { id: 'p3', deleted: true } },
+ errors: undefined,
+ })
authenticatedUser = await user.toJson()
}
@@ -242,11 +246,12 @@ describe('given some notifications', () => {
query({ query: notificationQuery, variables: { ...variables, read: false } }),
).resolves.toMatchObject({
data: { notifications: [expect.any(Object), expect.any(Object)] },
+ errors: undefined,
})
await deletePostAction()
await expect(
query({ query: notificationQuery, variables: { ...variables, read: false } }),
- ).resolves.toMatchObject({ data: { notifications: [] } })
+ ).resolves.toMatchObject({ data: { notifications: [] }, errors: undefined })
})
})
})
diff --git a/backend/src/schema/resolvers/passwordReset.js b/backend/src/schema/resolvers/passwordReset.js
index dfbfe8183..74c71e011 100644
--- a/backend/src/schema/resolvers/passwordReset.js
+++ b/backend/src/schema/resolvers/passwordReset.js
@@ -12,25 +12,29 @@ export default {
const stillValid = new Date()
stillValid.setDate(stillValid.getDate() - 1)
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
- const cypher = `
- MATCH (pr:PasswordReset {nonce: $nonce})
- MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
- WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
- SET pr.usedAt = datetime()
- SET u.encryptedPassword = $encryptedNewPassword
- RETURN pr
- `
const session = driver.session()
try {
- const transactionRes = await session.run(cypher, {
- stillValid,
- email,
- nonce,
- encryptedNewPassword,
+ const passwordResetTxPromise = session.writeTransaction(async transaction => {
+ const passwordResetTransactionResponse = await transaction.run(
+ `
+ MATCH (passwordReset:PasswordReset {nonce: $nonce})
+ MATCH (email:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(user:User)-[:REQUESTED]->(passwordReset)
+ WHERE duration.between(passwordReset.issuedAt, datetime()).days <= 0 AND passwordReset.usedAt IS NULL
+ SET passwordReset.usedAt = datetime()
+ SET user.encryptedPassword = $encryptedNewPassword
+ RETURN passwordReset
+ `,
+ {
+ stillValid,
+ email,
+ nonce,
+ encryptedNewPassword,
+ },
+ )
+ return passwordResetTransactionResponse.records.map(record => record.get('passwordReset'))
})
- const [reset] = transactionRes.records.map(record => record.get('pr'))
- const response = !!(reset && reset.properties.usedAt)
- return response
+ const [reset] = await passwordResetTxPromise
+ return !!(reset && reset.properties.usedAt)
} finally {
session.close()
}
diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js
index 97aa6a020..be3c8c085 100644
--- a/backend/src/schema/resolvers/passwordReset.spec.js
+++ b/backend/src/schema/resolvers/passwordReset.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createPasswordReset from './helpers/createPasswordReset'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
@@ -14,14 +14,11 @@ let authenticatedUser
let variables
const getAllPasswordResets = async () => {
- const session = driver.session()
- try {
- const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
- const resets = transactionRes.records.map(record => record.get('r'))
- return resets
- } finally {
- session.close()
- }
+ const passwordResetQuery = await neode.cypher(
+ 'MATCH (passwordReset:PasswordReset) RETURN passwordReset',
+ )
+ const resets = passwordResetQuery.records.map(record => record.get('passwordReset'))
+ return resets
}
beforeEach(() => {
diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js
index b37a4abd5..6ae3a81d9 100644
--- a/backend/src/schema/resolvers/posts.js
+++ b/backend/src/schema/resolvers/posts.js
@@ -57,17 +57,20 @@ export default {
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
const { postId, data } = params
const session = context.driver.session()
- try {
- const transactionRes = await session.run(
- `MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
- RETURN COUNT(DISTINCT emoted) as emotionsCount
- `,
+ const readTxResultPromise = session.readTransaction(async transaction => {
+ const emotionsCountTransactionResponse = await transaction.run(
+ `
+ MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
+ RETURN COUNT(DISTINCT emoted) as emotionsCount
+ `,
{ postId, data },
)
-
- const [emotionsCount] = transactionRes.records.map(record => {
- return record.get('emotionsCount').low
- })
+ return emotionsCountTransactionResponse.records.map(
+ record => record.get('emotionsCount').low,
+ )
+ })
+ try {
+ const [emotionsCount] = await readTxResultPromise
return emotionsCount
} finally {
session.close()
@@ -76,16 +79,18 @@ export default {
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
const { postId } = params
const session = context.driver.session()
- try {
- const transactionRes = await session.run(
- `MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
- RETURN collect(emoted.emotion) as emotion`,
+ const readTxResultPromise = session.readTransaction(async transaction => {
+ const emotionsTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
+ RETURN collect(emoted.emotion) as emotion
+ `,
{ userId: context.user.id, postId },
)
-
- const [emotions] = transactionRes.records.map(record => {
- return record.get('emotion')
- })
+ return emotionsTransactionResponse.records.map(record => record.get('emotion'))
+ })
+ try {
+ const [emotions] = await readTxResultPromise
return emotions
} finally {
session.close()
@@ -98,25 +103,29 @@ export default {
delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
params.id = params.id || uuid()
- const createPostCypher = `CREATE (post:Post {params})
- SET post.createdAt = toString(datetime())
- SET post.updatedAt = toString(datetime())
- WITH post
- MATCH (author:User {id: $userId})
- MERGE (post)<-[:WROTE]-(author)
- WITH post
- UNWIND $categoryIds AS categoryId
- MATCH (category:Category {id: categoryId})
- MERGE (post)-[:CATEGORIZED]->(category)
- RETURN post`
-
- const createPostVariables = { userId: context.user.id, categoryIds, params }
-
const session = context.driver.session()
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const createPostTransactionResponse = await transaction.run(
+ `
+ CREATE (post:Post {params})
+ SET post.createdAt = toString(datetime())
+ SET post.updatedAt = toString(datetime())
+ WITH post
+ MATCH (author:User {id: $userId})
+ MERGE (post)<-[:WROTE]-(author)
+ WITH post
+ UNWIND $categoryIds AS categoryId
+ MATCH (category:Category {id: categoryId})
+ MERGE (post)-[:CATEGORIZED]->(category)
+ RETURN post
+ `,
+ { userId: context.user.id, categoryIds, params },
+ )
+ return createPostTransactionResponse.records.map(record => record.get('post').properties)
+ })
try {
- const transactionRes = await session.run(createPostCypher, createPostVariables)
- const posts = transactionRes.records.map(record => record.get('post').properties)
- return posts[0]
+ const [post] = await writeTxResultPromise
+ return post
} catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Post with this slug already exists!')
@@ -129,38 +138,44 @@ export default {
const { categoryIds } = params
delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
- let updatePostCypher = `MATCH (post:Post {id: $params.id})
- SET post += $params
- SET post.updatedAt = toString(datetime())
- WITH post
- `
-
const session = context.driver.session()
- try {
- if (categoryIds && categoryIds.length) {
- const cypherDeletePreviousRelations = `
+ let updatePostCypher = `
+ MATCH (post:Post {id: $params.id})
+ SET post += $params
+ SET post.updatedAt = toString(datetime())
+ WITH post
+ `
+
+ if (categoryIds && categoryIds.length) {
+ const cypherDeletePreviousRelations = `
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
DELETE previousRelations
RETURN post, category
- `
+ `
- await session.run(cypherDeletePreviousRelations, { params })
+ await session.writeTransaction(transaction => {
+ return transaction.run(cypherDeletePreviousRelations, { params })
+ })
- updatePostCypher += `
+ updatePostCypher += `
UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId})
MERGE (post)-[:CATEGORIZED]->(category)
WITH post
`
- }
+ }
- updatePostCypher += `RETURN post`
- const updatePostVariables = { categoryIds, params }
-
- const transactionRes = await session.run(updatePostCypher, updatePostVariables)
- const [post] = transactionRes.records.map(record => {
- return record.get('post').properties
+ updatePostCypher += `RETURN post`
+ const updatePostVariables = { categoryIds, params }
+ try {
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const updatePostTransactionResponse = await transaction.run(
+ updatePostCypher,
+ updatePostVariables,
+ )
+ return updatePostTransactionResponse.records.map(record => record.get('post').properties)
})
+ const [post] = await writeTxResultPromise
return post
} finally {
session.close()
@@ -169,23 +184,25 @@ export default {
DeletePost: async (object, args, context, resolveInfo) => {
const session = context.driver.session()
- try {
- // we cannot set slug to 'UNAVAILABE' because of unique constraints
- const transactionRes = await session.run(
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const deletePostTransactionResponse = await transaction.run(
`
- MATCH (post:Post {id: $postId})
- OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
- SET post.deleted = TRUE
- SET post.content = 'UNAVAILABLE'
- SET post.contentExcerpt = 'UNAVAILABLE'
- SET post.title = 'UNAVAILABLE'
- SET comment.deleted = TRUE
- REMOVE post.image
- RETURN post
- `,
+ MATCH (post:Post {id: $postId})
+ OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
+ SET post.deleted = TRUE
+ SET post.content = 'UNAVAILABLE'
+ SET post.contentExcerpt = 'UNAVAILABLE'
+ SET post.title = 'UNAVAILABLE'
+ SET comment.deleted = TRUE
+ REMOVE post.image
+ RETURN post
+ `,
{ postId: args.id },
)
- const [post] = transactionRes.records.map(record => record.get('post').properties)
+ return deletePostTransactionResponse.records.map(record => record.get('post').properties)
+ })
+ try {
+ const [post] = await writeTxResultPromise
return post
} finally {
session.close()
@@ -195,21 +212,24 @@ export default {
const { to, data } = params
const { user } = context
const session = context.driver.session()
- try {
- const transactionRes = await session.run(
- `MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
- MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
- RETURN userFrom, postTo, emotedRelation`,
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const addPostEmotionsTransactionResponse = await transaction.run(
+ `
+ MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
+ MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
+ RETURN userFrom, postTo, emotedRelation`,
{ user, to, data },
)
-
- const [emoted] = transactionRes.records.map(record => {
+ return addPostEmotionsTransactionResponse.records.map(record => {
return {
from: { ...record.get('userFrom').properties },
to: { ...record.get('postTo').properties },
...record.get('emotedRelation').properties,
}
})
+ })
+ try {
+ const [emoted] = await writeTxResultPromise
return emoted
} finally {
session.close()
@@ -219,20 +239,25 @@ export default {
const { to, data } = params
const { id: from } = context.user
const session = context.driver.session()
- try {
- const transactionRes = await session.run(
- `MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
- DELETE emotedRelation
- RETURN userFrom, postTo`,
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const removePostEmotionsTransactionResponse = await transaction.run(
+ `
+ MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
+ DELETE emotedRelation
+ RETURN userFrom, postTo
+ `,
{ from, to, data },
)
- const [emoted] = transactionRes.records.map(record => {
+ return removePostEmotionsTransactionResponse.records.map(record => {
return {
from: { ...record.get('userFrom').properties },
to: { ...record.get('postTo').properties },
emotion: data.emotion,
}
})
+ })
+ try {
+ const [emoted] = await writeTxResultPromise
return emoted
} finally {
session.close()
@@ -344,21 +369,28 @@ export default {
relatedContributions: async (parent, params, context, resolveInfo) => {
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
const { id } = parent
- const statement = `
- MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
- WHERE NOT post.deleted AND NOT post.disabled
- RETURN DISTINCT post
- LIMIT 10
- `
- let relatedContributions
const session = context.driver.session()
+
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const relatedContributionsTransactionResponse = await transaction.run(
+ `
+ MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
+ WHERE NOT post.deleted AND NOT post.disabled
+ RETURN DISTINCT post
+ LIMIT 10
+ `,
+ { id },
+ )
+ return relatedContributionsTransactionResponse.records.map(
+ record => record.get('post').properties,
+ )
+ })
try {
- const result = await session.run(statement, { id })
- relatedContributions = result.records.map(r => r.get('post').properties)
+ const relatedContributions = await writeTxResultPromise
+ return relatedContributions
} finally {
session.close()
}
- return relatedContributions
},
},
}
diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js
index 98475b182..dcbd16d5d 100644
--- a/backend/src/schema/resolvers/posts.spec.js
+++ b/backend/src/schema/resolvers/posts.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
const driver = getDriver()
@@ -383,7 +383,10 @@ describe('UpdatePost', () => {
})
it('updates a post', async () => {
- const expected = { data: { UpdatePost: { id: 'p9876', content: 'New content' } } }
+ const expected = {
+ data: { UpdatePost: { id: 'p9876', content: 'New content' } },
+ errors: undefined,
+ }
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
expected,
)
@@ -394,6 +397,7 @@ describe('UpdatePost', () => {
data: {
UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) },
},
+ errors: undefined,
}
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
expected,
@@ -421,6 +425,7 @@ describe('UpdatePost', () => {
categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]),
},
},
+ errors: undefined,
}
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
expected,
@@ -441,6 +446,7 @@ describe('UpdatePost', () => {
categories: expect.arrayContaining([{ id: 'cat27' }]),
},
},
+ errors: undefined,
}
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
expected,
@@ -722,6 +728,7 @@ describe('UpdatePost', () => {
},
],
},
+ errors: undefined,
}
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js
index 206c8db74..1a6bda1c8 100644
--- a/backend/src/schema/resolvers/registration.js
+++ b/backend/src/schema/resolvers/registration.js
@@ -1,12 +1,12 @@
import { UserInputError } from 'apollo-server'
-import { neode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
import fileUpload from './fileUpload'
import encryptPassword from '../../helpers/encryptPassword'
import generateNonce from './helpers/generateNonce'
import existingEmailAddress from './helpers/existingEmailAddress'
import normalizeEmail from './helpers/normalizeEmail'
-const instance = neode()
+const neode = getNeode()
export default {
Mutation: {
@@ -16,7 +16,7 @@ export default {
let emailAddress = await existingEmailAddress({ args, context })
if (emailAddress) return emailAddress
try {
- emailAddress = await instance.create('EmailAddress', args)
+ emailAddress = await neode.create('EmailAddress', args)
return emailAddress.toJson()
} catch (e) {
throw new UserInputError(e.message)
@@ -32,7 +32,7 @@ export default {
let { nonce, email } = args
email = normalizeEmail(email)
- const result = await instance.cypher(
+ const result = await neode.cypher(
`
MATCH(email:EmailAddress {nonce: {nonce}, email: {email}})
WHERE NOT (email)-[:BELONGS_TO]->()
@@ -40,12 +40,12 @@ export default {
`,
{ nonce, email },
)
- const emailAddress = await instance.hydrateFirst(result, 'email', instance.model('Email'))
+ const emailAddress = await neode.hydrateFirst(result, 'email', neode.model('EmailAddress'))
if (!emailAddress) throw new UserInputError('Invalid email or nonce')
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
args = await encryptPassword(args)
try {
- const user = await instance.create('User', args)
+ const user = await neode.create('User', args)
await Promise.all([
user.relateTo(emailAddress, 'primaryEmail'),
emailAddress.relateTo(user, 'belongsTo'),
diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js
index 35b16b9bb..8f3a7ac39 100644
--- a/backend/src/schema/resolvers/registration.spec.js
+++ b/backend/src/schema/resolvers/registration.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
+import { getDriver, getNeode } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js
index a1d98bb41..0565c4d8a 100644
--- a/backend/src/schema/resolvers/reports.js
+++ b/backend/src/schema/resolvers/reports.js
@@ -1,3 +1,5 @@
+import log from './helpers/databaseLogger'
+
const transformReturnType = record => {
return {
...record.get('report').properties,
@@ -11,12 +13,11 @@ const transformReturnType = record => {
export default {
Mutation: {
fileReport: async (_parent, params, context, _resolveInfo) => {
- let createdRelationshipWithNestedAttributes
const { resourceId, reasonCategory, reasonDescription } = params
const { driver, user } = context
const session = driver.session()
- const reportWriteTxResultPromise = session.writeTransaction(async txc => {
- const reportTransactionResponse = await txc.run(
+ const reportWriteTxResultPromise = session.writeTransaction(async transaction => {
+ const reportTransactionResponse = await transaction.run(
`
MATCH (submitter:User {id: $submitterId})
MATCH (resource {id: $resourceId})
@@ -36,23 +37,23 @@ export default {
reasonDescription,
},
)
+ log(reportTransactionResponse)
return reportTransactionResponse.records.map(transformReturnType)
})
try {
- const txResult = await reportWriteTxResultPromise
- if (!txResult[0]) return null
- createdRelationshipWithNestedAttributes = txResult[0]
+ const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise
+ if (!createdRelationshipWithNestedAttributes) return null
+ return createdRelationshipWithNestedAttributes
} finally {
session.close()
}
- return createdRelationshipWithNestedAttributes
},
},
Query: {
reports: async (_parent, params, context, _resolveInfo) => {
const { driver } = context
const session = driver.session()
- let reports, orderByClause, filterClause
+ let orderByClause, filterClause
switch (params.orderBy) {
case 'createdAt_asc':
orderByClause = 'ORDER BY report.createdAt ASC'
@@ -81,8 +82,8 @@ export default {
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
- const reportReadTxPromise = session.readTransaction(async tx => {
- const allReportsTransactionResponse = await tx.run(
+ const reportReadTxPromise = session.readTransaction(async transaction => {
+ const allReportsTransactionResponse = await transaction.run(
`
MATCH (report:Report)-[:BELONGS_TO]->(resource)
WHERE (resource:User OR resource:Post OR resource:Comment)
@@ -100,16 +101,15 @@ export default {
${offset} ${limit}
`,
)
+ log(allReportsTransactionResponse)
return allReportsTransactionResponse.records.map(record => record.get('report'))
})
try {
- const txResult = await reportReadTxPromise
- if (!txResult[0]) return null
- reports = txResult
+ const reports = await reportReadTxPromise
+ return reports
} finally {
session.close()
}
- return reports
},
},
Report: {
@@ -118,23 +118,23 @@ export default {
const session = context.driver.session()
const { id } = parent
let filed
- const readTxPromise = session.readTransaction(async tx => {
- const allReportsTransactionResponse = await tx.run(
+ const readTxPromise = session.readTransaction(async transaction => {
+ const filedReportsTransactionResponse = await transaction.run(
`
- MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id})
- RETURN filed, submitter
+ MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id})
+ RETURN filed, submitter
`,
{ id },
)
- return allReportsTransactionResponse.records.map(record => ({
+ log(filedReportsTransactionResponse)
+ return filedReportsTransactionResponse.records.map(record => ({
submitter: record.get('submitter').properties,
filed: record.get('filed').properties,
}))
})
try {
- const txResult = await readTxPromise
- if (!txResult[0]) return null
- filed = txResult.map(reportedRecord => {
+ const filedReports = await readTxPromise
+ filed = filedReports.map(reportedRecord => {
const { submitter, filed } = reportedRecord
const relationshipWithNestedAttributes = {
...filed,
@@ -152,8 +152,8 @@ export default {
const session = context.driver.session()
const { id } = parent
let reviewed
- const readTxPromise = session.readTransaction(async tx => {
- const allReportsTransactionResponse = await tx.run(
+ const readTxPromise = session.readTransaction(async transaction => {
+ const reviewedReportsTransactionResponse = await transaction.run(
`
MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User)
RETURN moderator, review
@@ -161,14 +161,15 @@ export default {
`,
{ id },
)
- return allReportsTransactionResponse.records.map(record => ({
+ log(reviewedReportsTransactionResponse)
+ return reviewedReportsTransactionResponse.records.map(record => ({
review: record.get('review').properties,
moderator: record.get('moderator').properties,
}))
})
try {
- const txResult = await readTxPromise
- reviewed = txResult.map(reportedRecord => {
+ const reviewedReports = await readTxPromise
+ reviewed = reviewedReports.map(reportedRecord => {
const { review, moderator } = reportedRecord
const relationshipWithNestedAttributes = {
...review,
diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js
index c0a9d3afb..8b1bb925d 100644
--- a/backend/src/schema/resolvers/reports.spec.js
+++ b/backend/src/schema/resolvers/reports.spec.js
@@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
+import { getDriver, getNeode } from '../../bootstrap/neo4j'
const factory = Factory()
const instance = getNeode()
@@ -21,7 +21,6 @@ describe('file a report on a resource', () => {
id
createdAt
updatedAt
- disable
closed
rule
resource {
@@ -489,7 +488,6 @@ describe('file a report on a resource', () => {
id
createdAt
updatedAt
- disable
closed
resource {
__typename
@@ -624,7 +622,6 @@ describe('file a report on a resource', () => {
id: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
- disable: false,
closed: false,
resource: {
__typename: 'User',
@@ -645,7 +642,6 @@ describe('file a report on a resource', () => {
id: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
- disable: false,
closed: false,
resource: {
__typename: 'Post',
@@ -666,7 +662,6 @@ describe('file a report on a resource', () => {
id: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
- disable: false,
closed: false,
resource: {
__typename: 'Comment',
diff --git a/backend/src/schema/resolvers/rewards.js b/backend/src/schema/resolvers/rewards.js
index 74c7860e4..44bdab770 100644
--- a/backend/src/schema/resolvers/rewards.js
+++ b/backend/src/schema/resolvers/rewards.js
@@ -1,11 +1,11 @@
-import { neode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
import { UserInputError } from 'apollo-server'
-const instance = neode()
+const neode = getNeode()
const getUserAndBadge = async ({ badgeKey, userId }) => {
- const user = await instance.first('User', 'id', userId)
- const badge = await instance.first('Badge', 'id', badgeKey)
+ const user = await neode.first('User', 'id', userId)
+ const badge = await neode.first('Badge', 'id', badgeKey)
if (!user) throw new UserInputError("Couldn't find a user with that id")
if (!badge) throw new UserInputError("Couldn't find a badge with that id")
return { user, badge }
@@ -24,18 +24,19 @@ export default {
const { user } = await getUserAndBadge(params)
const session = context.driver.session()
try {
- // silly neode cannot remove relationships
- await session.run(
- `
- MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
- DELETE reward
- RETURN rewardedUser
- `,
- {
- badgeKey,
- userId,
- },
- )
+ await session.writeTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
+ DELETE reward
+ RETURN rewardedUser
+ `,
+ {
+ badgeKey,
+ userId,
+ },
+ )
+ })
} finally {
session.close()
}
diff --git a/backend/src/schema/resolvers/rewards.spec.js b/backend/src/schema/resolvers/rewards.spec.js
index 2dcdd5b53..e6f67ecab 100644
--- a/backend/src/schema/resolvers/rewards.spec.js
+++ b/backend/src/schema/resolvers/rewards.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
const factory = Factory()
diff --git a/backend/src/schema/resolvers/shout.js b/backend/src/schema/resolvers/shout.js
index ada1172a4..70ebdf7ae 100644
--- a/backend/src/schema/resolvers/shout.js
+++ b/backend/src/schema/resolvers/shout.js
@@ -1,3 +1,5 @@
+import log from './helpers/databaseLogger'
+
export default {
Mutation: {
shout: async (_object, params, context, _resolveInfo) => {
@@ -5,22 +7,24 @@ export default {
const session = context.driver.session()
try {
- const transactionRes = await session.run(
- `MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
- WHERE $type IN labels(node) AND NOT userWritten.id = $userId
- MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
- RETURN COUNT(relation) > 0 as isShouted`,
- {
- id,
- type,
- userId: context.user.id,
- },
- )
-
- const [isShouted] = transactionRes.records.map(record => {
- return record.get('isShouted')
+ const shoutWriteTxResultPromise = session.writeTransaction(async transaction => {
+ const shoutTransactionResponse = await transaction.run(
+ `
+ MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
+ WHERE $type IN labels(node) AND NOT userWritten.id = $userId
+ MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
+ RETURN COUNT(relation) > 0 as isShouted
+ `,
+ {
+ id,
+ type,
+ userId: context.user.id,
+ },
+ )
+ log(shoutTransactionResponse)
+ return shoutTransactionResponse.records.map(record => record.get('isShouted'))
})
-
+ const [isShouted] = await shoutWriteTxResultPromise
return isShouted
} finally {
session.close()
@@ -31,20 +35,24 @@ export default {
const { id, type } = params
const session = context.driver.session()
try {
- const transactionRes = await session.run(
- `MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
- WHERE $type IN labels(node)
- DELETE relation
- RETURN COUNT(relation) > 0 as isShouted`,
- {
- id,
- type,
- userId: context.user.id,
- },
- )
- const [isShouted] = transactionRes.records.map(record => {
- return record.get('isShouted')
+ const unshoutWriteTxResultPromise = session.writeTransaction(async transaction => {
+ const unshoutTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
+ WHERE $type IN labels(node)
+ DELETE relation
+ RETURN COUNT(relation) > 0 as isShouted
+ `,
+ {
+ id,
+ type,
+ userId: context.user.id,
+ },
+ )
+ log(unshoutTransactionResponse)
+ return unshoutTransactionResponse.records.map(record => record.get('isShouted'))
})
+ const [isShouted] = await unshoutWriteTxResultPromise
return isShouted
} finally {
session.close()
diff --git a/backend/src/schema/resolvers/shout.spec.js b/backend/src/schema/resolvers/shout.spec.js
index f39e4d137..e747946aa 100644
--- a/backend/src/schema/resolvers/shout.spec.js
+++ b/backend/src/schema/resolvers/shout.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
let mutate, query, authenticatedUser, variables
diff --git a/backend/src/schema/resolvers/socialMedia.js b/backend/src/schema/resolvers/socialMedia.js
index 49aa6788d..c206778e5 100644
--- a/backend/src/schema/resolvers/socialMedia.js
+++ b/backend/src/schema/resolvers/socialMedia.js
@@ -1,14 +1,14 @@
-import { neode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
import Resolver from './helpers/Resolver'
-const instance = neode()
+const neode = getNeode()
export default {
Mutation: {
CreateSocialMedia: async (object, params, context, resolveInfo) => {
const [user, socialMedia] = await Promise.all([
- instance.find('User', context.user.id),
- instance.create('SocialMedia', params),
+ neode.find('User', context.user.id),
+ neode.create('SocialMedia', params),
])
await socialMedia.relateTo(user, 'ownedBy')
const response = await socialMedia.toJson()
@@ -16,14 +16,14 @@ export default {
return response
},
UpdateSocialMedia: async (object, params, context, resolveInfo) => {
- const socialMedia = await instance.find('SocialMedia', params.id)
+ const socialMedia = await neode.find('SocialMedia', params.id)
await socialMedia.update({ url: params.url })
const response = await socialMedia.toJson()
return response
},
DeleteSocialMedia: async (object, { id }, context, resolveInfo) => {
- const socialMedia = await instance.find('SocialMedia', id)
+ const socialMedia = await neode.find('SocialMedia', id)
if (!socialMedia) return null
await socialMedia.delete()
return socialMedia.toJson()
diff --git a/backend/src/schema/resolvers/socialMedia.spec.js b/backend/src/schema/resolvers/socialMedia.spec.js
index 092139747..8f6d91d43 100644
--- a/backend/src/schema/resolvers/socialMedia.spec.js
+++ b/backend/src/schema/resolvers/socialMedia.spec.js
@@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
import createServer from '../../server'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
const driver = getDriver()
const factory = Factory()
-const instance = neode()
+const neode = getNeode()
describe('SocialMedia', () => {
let socialMediaAction, someUser, ownerNode, owner
@@ -27,15 +27,15 @@ describe('SocialMedia', () => {
const newUrl = 'https://twitter.com/bullerby'
const setUpSocialMedia = async () => {
- const socialMediaNode = await instance.create('SocialMedia', { url })
+ const socialMediaNode = await neode.create('SocialMedia', { url })
await socialMediaNode.relateTo(ownerNode, 'ownedBy')
return socialMediaNode.toJson()
}
beforeEach(async () => {
- const someUserNode = await instance.create('User', userParams)
+ const someUserNode = await neode.create('User', userParams)
someUser = await someUserNode.toJson()
- ownerNode = await instance.create('User', ownerParams)
+ ownerNode = await neode.create('User', ownerParams)
owner = await ownerNode.toJson()
socialMediaAction = async (user, mutation, variables) => {
diff --git a/backend/src/schema/resolvers/statistics.js b/backend/src/schema/resolvers/statistics.js
index 07b9e4cea..7ca9239f3 100644
--- a/backend/src/schema/resolvers/statistics.js
+++ b/backend/src/schema/resolvers/statistics.js
@@ -1,8 +1,10 @@
+import log from './helpers/databaseLogger'
+
export default {
Query: {
statistics: async (_parent, _args, { driver }) => {
const session = driver.session()
- const response = {}
+ const counts = {}
try {
const mapping = {
countUsers: 'User',
@@ -13,27 +15,28 @@ export default {
countFollows: 'FOLLOWS',
countShouts: 'SHOUTED',
}
- const cypher = `
- CALL apoc.meta.stats() YIELD labels, relTypesCount
- RETURN labels, relTypesCount
- `
- const result = await session.run(cypher)
- const [statistics] = await result.records.map(record => {
- return {
- ...record.get('labels'),
- ...record.get('relTypesCount'),
- }
+ const statisticsReadTxResultPromise = session.readTransaction(async transaction => {
+ const statisticsTransactionResponse = await transaction.run(
+ `
+ CALL apoc.meta.stats() YIELD labels, relTypesCount
+ RETURN labels, relTypesCount
+ `,
+ )
+ log(statisticsTransactionResponse)
+ return statisticsTransactionResponse.records.map(record => {
+ return {
+ ...record.get('labels'),
+ ...record.get('relTypesCount'),
+ }
+ })
})
+ const [statistics] = await statisticsReadTxResultPromise
Object.keys(mapping).forEach(key => {
const stat = statistics[mapping[key]]
- response[key] = stat ? stat.toNumber() : 0
+ counts[key] = stat ? stat.toNumber() : 0
})
-
- /*
- * Note: invites count is calculated this way because invitation codes are not in use yet
- */
- response.countInvites = response.countEmails - response.countUsers
- return response
+ counts.countInvites = counts.countEmails - counts.countUsers
+ return counts
} finally {
session.close()
}
diff --git a/backend/src/schema/resolvers/statistics.spec.js b/backend/src/schema/resolvers/statistics.spec.js
index 7ffa8ebd0..48baf00cd 100644
--- a/backend/src/schema/resolvers/statistics.spec.js
+++ b/backend/src/schema/resolvers/statistics.spec.js
@@ -1,7 +1,7 @@
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
let query, authenticatedUser
diff --git a/backend/src/schema/resolvers/user_management.js b/backend/src/schema/resolvers/user_management.js
index 4c4c3fc90..4d035d9fa 100644
--- a/backend/src/schema/resolvers/user_management.js
+++ b/backend/src/schema/resolvers/user_management.js
@@ -1,10 +1,11 @@
import encode from '../../jwt/encode'
import bcrypt from 'bcryptjs'
import { AuthenticationError } from 'apollo-server'
-import { neode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
import normalizeEmail from './helpers/normalizeEmail'
+import log from './helpers/databaseLogger'
-const instance = neode()
+const neode = getNeode()
export default {
Query: {
@@ -13,7 +14,7 @@ export default {
},
currentUser: async (object, params, ctx, resolveInfo) => {
if (!ctx.user) return null
- const user = await instance.find('User', ctx.user.id)
+ const user = await neode.find('User', ctx.user.id)
return user.toJson()
},
},
@@ -25,17 +26,18 @@ export default {
email = normalizeEmail(email)
const session = driver.session()
try {
- const result = await session.run(
- `
- MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
- RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1
- `,
- { userEmail: email },
- )
- const [currentUser] = await result.records.map(record => {
- return record.get('user')
+ const loginReadTxResultPromise = session.readTransaction(async transaction => {
+ const loginTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
+ RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1
+ `,
+ { userEmail: email },
+ )
+ log(loginTransactionResponse)
+ return loginTransactionResponse.records.map(record => record.get('user'))
})
-
+ const [currentUser] = await loginReadTxResultPromise
if (
currentUser &&
(await bcrypt.compareSync(password, currentUser.encryptedPassword)) &&
@@ -53,7 +55,7 @@ export default {
}
},
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
- const currentUser = await instance.find('User', user.id)
+ const currentUser = await neode.find('User', user.id)
const encryptedPassword = currentUser.get('encryptedPassword')
if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) {
diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js
index e67b90c8d..3527e5dc2 100644
--- a/backend/src/schema/resolvers/user_management.spec.js
+++ b/backend/src/schema/resolvers/user_management.spec.js
@@ -5,7 +5,7 @@ import { gql } from '../../helpers/jest'
import { createTestClient } from 'apollo-server-testing'
import createServer, { context } from '../../server'
import encode from '../../jwt/encode'
-import { neode as getNeode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
const factory = Factory()
const neode = getNeode()
diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js
index c44e3f44b..be9a69e80 100644
--- a/backend/src/schema/resolvers/users.js
+++ b/backend/src/schema/resolvers/users.js
@@ -1,10 +1,11 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
import fileUpload from './fileUpload'
-import { neode } from '../../bootstrap/neo4j'
+import { getNeode } from '../../bootstrap/neo4j'
import { UserInputError, ForbiddenError } from 'apollo-server'
import Resolver from './helpers/Resolver'
+import log from './helpers/databaseLogger'
-const instance = neode()
+const neode = getNeode()
export const getBlockedUsers = async context => {
const { neode } = context
@@ -73,7 +74,7 @@ export default {
block: async (object, args, context, resolveInfo) => {
const { user: currentUser } = context
if (currentUser.id === args.id) return null
- await instance.cypher(
+ await neode.cypher(
`
MATCH(u:User {id: $currentUser.id})-[r:FOLLOWS]->(b:User {id: $args.id})
DELETE r
@@ -81,8 +82,8 @@ export default {
{ currentUser, args },
)
const [user, blockedUser] = await Promise.all([
- instance.find('User', currentUser.id),
- instance.find('User', args.id),
+ neode.find('User', currentUser.id),
+ neode.find('User', args.id),
])
await user.relateTo(blockedUser, 'blocked')
return blockedUser.toJson()
@@ -90,82 +91,99 @@ export default {
unblock: async (object, args, context, resolveInfo) => {
const { user: currentUser } = context
if (currentUser.id === args.id) return null
- await instance.cypher(
+ await neode.cypher(
`
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(b:User {id: $args.id})
DELETE r
`,
{ currentUser, args },
)
- const blockedUser = await instance.find('User', args.id)
+ const blockedUser = await neode.find('User', args.id)
return blockedUser.toJson()
},
- UpdateUser: async (object, args, context, resolveInfo) => {
- const { termsAndConditionsAgreedVersion } = args
+ UpdateUser: async (_parent, params, context, _resolveInfo) => {
+ const { termsAndConditionsAgreedVersion } = params
if (termsAndConditionsAgreedVersion) {
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
if (!regEx.test(termsAndConditionsAgreedVersion)) {
throw new ForbiddenError('Invalid version format!')
}
- args.termsAndConditionsAgreedAt = new Date().toISOString()
+ params.termsAndConditionsAgreedAt = new Date().toISOString()
}
- args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
+ params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
+ const session = context.driver.session()
+
+ const writeTxResultPromise = session.writeTransaction(async transaction => {
+ const updateUserTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {id: $params.id})
+ SET user += $params
+ SET user.updatedAt = toString(datetime())
+ RETURN user
+ `,
+ { params },
+ )
+ return updateUserTransactionResponse.records.map(record => record.get('user').properties)
+ })
try {
- const user = await instance.find('User', args.id)
- if (!user) return null
- await user.update({ ...args, updatedAt: new Date().toISOString() })
- return user.toJson()
- } catch (e) {
- throw new UserInputError(e.message)
+ const [user] = await writeTxResultPromise
+ return user
+ } catch (error) {
+ throw new UserInputError(error.message)
+ } finally {
+ session.close()
}
},
DeleteUser: async (object, params, context, resolveInfo) => {
const { resource } = params
const session = context.driver.session()
-
- let user
try {
if (resource && resource.length) {
- await Promise.all(
- resource.map(async node => {
- await session.run(
+ await session.writeTransaction(transaction => {
+ resource.map(node => {
+ return transaction.run(
`
- MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
- OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
- SET resource.deleted = true
- SET resource.content = 'UNAVAILABLE'
- SET resource.contentExcerpt = 'UNAVAILABLE'
- SET comment.deleted = true
- RETURN author`,
+ MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
+ OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
+ SET resource.deleted = true
+ SET resource.content = 'UNAVAILABLE'
+ SET resource.contentExcerpt = 'UNAVAILABLE'
+ SET comment.deleted = true
+ RETURN author
+ `,
{
userId: context.user.id,
},
)
- }),
- )
+ })
+ })
}
- // we cannot set slug to 'UNAVAILABE' because of unique constraints
- const transactionResult = await session.run(
- `
- MATCH (user:User {id: $userId})
- SET user.deleted = true
- SET user.name = 'UNAVAILABLE'
- SET user.about = 'UNAVAILABLE'
- WITH user
- OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
- DETACH DELETE email
- WITH user
- OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
- DETACH DELETE socialMedia
- RETURN user`,
- { userId: context.user.id },
- )
- user = transactionResult.records.map(r => r.get('user').properties)[0]
+ const deleteUserTxResultPromise = session.writeTransaction(async transaction => {
+ const deleteUserTransactionResponse = await transaction.run(
+ `
+ MATCH (user:User {id: $userId})
+ SET user.deleted = true
+ SET user.name = 'UNAVAILABLE'
+ SET user.about = 'UNAVAILABLE'
+ WITH user
+ OPTIONAL MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress)
+ DETACH DELETE email
+ WITH user
+ OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
+ DETACH DELETE socialMedia
+ RETURN user
+ `,
+ { userId: context.user.id },
+ )
+ log(deleteUserTransactionResponse)
+ return deleteUserTransactionResponse.records.map(record => record.get('user').properties)
+ })
+ const [user] = await deleteUserTxResultPromise
+ return user
} finally {
session.close()
}
- return user
},
},
User: {
@@ -173,7 +191,7 @@ export default {
if (typeof parent.email !== 'undefined') return parent.email
const { id } = parent
const statement = `MATCH(u:User {id: {id}})-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e`
- const result = await instance.cypher(statement, { id })
+ const result = await neode.cypher(statement, { id })
const [{ email }] = result.records.map(r => r.get('e').properties)
return email
},
diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js
index 483c70214..5d1ebd8e2 100644
--- a/backend/src/schema/resolvers/users.spec.js
+++ b/backend/src/schema/resolvers/users.spec.js
@@ -1,6 +1,6 @@
import Factory from '../../seed/factories'
import { gql } from '../../helpers/jest'
-import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
@@ -68,6 +68,7 @@ describe('User', () => {
it('is permitted', async () => {
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
data: { User: [{ name: 'Johnny' }] },
+ errors: undefined,
})
})
@@ -90,8 +91,7 @@ describe('User', () => {
})
describe('UpdateUser', () => {
- let userParams
- let variables
+ let userParams, variables
beforeEach(async () => {
userParams = {
@@ -111,16 +111,23 @@ describe('UpdateUser', () => {
})
const updateUserMutation = gql`
- mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) {
+ mutation(
+ $id: ID!
+ $name: String
+ $termsAndConditionsAgreedVersion: String
+ $locationName: String
+ ) {
UpdateUser(
id: $id
name: $name
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
+ locationName: $locationName
) {
id
name
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
+ locationName
}
}
`
@@ -152,7 +159,7 @@ describe('UpdateUser', () => {
authenticatedUser = await user.toJson()
})
- it('name within specifications', async () => {
+ it('updates the name', async () => {
const expected = {
data: {
UpdateUser: {
@@ -160,36 +167,13 @@ describe('UpdateUser', () => {
name: 'John Doughnut',
},
},
+ errors: undefined,
}
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
expected,
)
})
- it('with `null` as name', async () => {
- const variables = {
- id: 'u47',
- name: null,
- }
- const { errors } = await mutate({ mutation: updateUserMutation, variables })
- expect(errors[0]).toHaveProperty(
- 'message',
- 'child "name" fails because ["name" contains an invalid value, "name" must be a string]',
- )
- })
-
- it('with too short name', async () => {
- const variables = {
- id: 'u47',
- name: ' ',
- }
- const { errors } = await mutate({ mutation: updateUserMutation, variables })
- expect(errors[0]).toHaveProperty(
- 'message',
- 'child "name" fails because ["name" length must be at least 3 characters long]',
- )
- })
-
describe('given a new agreed version of terms and conditions', () => {
beforeEach(async () => {
variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' }
@@ -202,6 +186,7 @@ describe('UpdateUser', () => {
termsAndConditionsAgreedAt: expect.any(String),
}),
},
+ errors: undefined,
}
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
@@ -222,6 +207,7 @@ describe('UpdateUser', () => {
termsAndConditionsAgreedAt: null,
}),
},
+ errors: undefined,
}
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
@@ -238,6 +224,14 @@ describe('UpdateUser', () => {
const { errors } = await mutate({ mutation: updateUserMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
})
+
+ it('supports updating location', async () => {
+ variables = { ...variables, locationName: 'Hamburg, New Jersey, United States of America' }
+ await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
+ data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States of America' } },
+ errors: undefined,
+ })
+ })
})
})
@@ -372,6 +366,7 @@ describe('DeleteUser', () => {
],
},
},
+ errors: undefined,
}
await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject(
expectedResponse,
@@ -418,6 +413,7 @@ describe('DeleteUser', () => {
],
},
},
+ errors: undefined,
}
await expect(
mutate({ mutation: deleteUserMutation, variables }),
@@ -465,6 +461,7 @@ describe('DeleteUser', () => {
],
},
},
+ errors: undefined,
}
await expect(
mutate({ mutation: deleteUserMutation, variables }),
@@ -511,6 +508,7 @@ describe('DeleteUser', () => {
],
},
},
+ errors: undefined,
}
await expect(
mutate({ mutation: deleteUserMutation, variables }),
diff --git a/backend/src/schema/resolvers/users/blockedUsers.spec.js b/backend/src/schema/resolvers/users/blockedUsers.spec.js
index e0ab00448..11bcb823d 100644
--- a/backend/src/schema/resolvers/users/blockedUsers.spec.js
+++ b/backend/src/schema/resolvers/users/blockedUsers.spec.js
@@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
import createServer from '../../../server'
import Factory from '../../../seed/factories'
import { gql } from '../../../helpers/jest'
-import { neode, getDriver } from '../../../bootstrap/neo4j'
+import { getNeode, getDriver } from '../../../bootstrap/neo4j'
const driver = getDriver()
const factory = Factory()
-const instance = neode()
+const neode = getNeode()
let currentUser
let blockedUser
@@ -20,7 +20,7 @@ beforeEach(() => {
return {
user: authenticatedUser,
driver,
- neode: instance,
+ neode,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
@@ -55,11 +55,11 @@ describe('blockedUsers', () => {
describe('authenticated and given a blocked user', () => {
beforeEach(async () => {
- currentUser = await instance.create('User', {
+ currentUser = await neode.create('User', {
name: 'Current User',
id: 'u1',
})
- blockedUser = await instance.create('User', {
+ blockedUser = await neode.create('User', {
name: 'Blocked User',
id: 'u2',
})
@@ -113,7 +113,7 @@ describe('block', () => {
describe('authenticated', () => {
beforeEach(async () => {
- currentUser = await instance.create('User', {
+ currentUser = await neode.create('User', {
name: 'Current User',
id: 'u1',
})
@@ -138,7 +138,7 @@ describe('block', () => {
describe('given a to-be-blocked user', () => {
beforeEach(async () => {
- blockedUser = await instance.create('User', {
+ blockedUser = await neode.create('User', {
name: 'Blocked User',
id: 'u2',
})
@@ -181,11 +181,11 @@ describe('block', () => {
let postQuery
beforeEach(async () => {
- const post1 = await instance.create('Post', {
+ const post1 = await neode.create('Post', {
id: 'p12',
title: 'A post written by the current user',
})
- const post2 = await instance.create('Post', {
+ const post2 = await neode.create('Post', {
id: 'p23',
title: 'A post written by the blocked user',
})
@@ -323,7 +323,7 @@ describe('unblock', () => {
describe('authenticated', () => {
beforeEach(async () => {
- currentUser = await instance.create('User', {
+ currentUser = await neode.create('User', {
name: 'Current User',
id: 'u1',
})
@@ -348,7 +348,7 @@ describe('unblock', () => {
describe('given another user', () => {
beforeEach(async () => {
- blockedUser = await instance.create('User', {
+ blockedUser = await neode.create('User', {
name: 'Blocked User',
id: 'u2',
})
diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js
index 441fe47d5..8b80a4b4f 100644
--- a/backend/src/seed/factories/index.js
+++ b/backend/src/seed/factories/index.js
@@ -1,4 +1,4 @@
-import { getDriver, neode } from '../../bootstrap/neo4j'
+import { getDriver, getNeode } from '../../bootstrap/neo4j'
import createBadge from './badges.js'
import createUser from './users.js'
import createPost from './posts.js'
@@ -29,17 +29,23 @@ const factories = {
export const cleanDatabase = async (options = {}) => {
const { driver = getDriver() } = options
- const cypher = 'MATCH (n) DETACH DELETE n'
const session = driver.session()
try {
- return await session.run(cypher)
+ await session.writeTransaction(transaction => {
+ return transaction.run(
+ `
+ MATCH (everything)
+ DETACH DELETE everything
+ `,
+ )
+ })
} finally {
session.close()
}
}
export default function Factory(options = {}) {
- const { neo4jDriver = getDriver(), neodeInstance = neode() } = options
+ const { neo4jDriver = getDriver(), neodeInstance = getNeode() } = options
const result = {
neo4jDriver,
diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js
index 2e10b0472..475a7b54f 100644
--- a/backend/src/seed/seed-db.js
+++ b/backend/src/seed/seed-db.js
@@ -3,7 +3,7 @@ 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 { getNeode, getDriver } from '../bootstrap/neo4j'
import { gql } from '../helpers/jest'
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
diff --git a/backend/src/server.js b/backend/src/server.js
index 053a3e4b3..122f23683 100644
--- a/backend/src/server.js
+++ b/backend/src/server.js
@@ -3,7 +3,7 @@ import helmet from 'helmet'
import { ApolloServer } from 'apollo-server-express'
import CONFIG, { requiredConfigs } from './config'
import middleware from './middleware'
-import { neode as getNeode, getDriver } from './bootstrap/neo4j'
+import { getNeode, getDriver } from './bootstrap/neo4j'
import decode from './jwt/decode'
import schema from './schema'
import webfinger from './activitypub/routes/webfinger'
@@ -38,6 +38,12 @@ const createServer = options => {
schema: middleware(schema),
debug: !!CONFIG.DEBUG,
tracing: !!CONFIG.DEBUG,
+ formatError: error => {
+ if (error.message === 'ERROR_VALIDATION') {
+ return new Error(error.originalError.details.map(d => d.message))
+ }
+ return error
+ },
}
const server = new ApolloServer(Object.assign({}, defaults, options))
diff --git a/backend/yarn.lock b/backend/yarn.lock
index cba2455d1..25e711898 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -33,10 +33,10 @@
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc"
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
-"@babel/cli@~7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b"
- integrity sha512-O7mmzaWdm+VabWQmxuM8hqNrWGGihN83KfhPUzp2lAW4kzIMwBxujXkZbD4fMwKMYY9FXTbDvXsJqU+5XHXi4A==
+"@babel/cli@~7.7.5":
+ version "7.7.5"
+ resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.5.tgz#25702cc65418efc06989af3727897b9f4c8690b6"
+ integrity sha512-y2YrMGXM3NUyu1Myg0pxg+Lx6g8XhEyvLHYNRwTBV6fDek3H7Io6b7N/LXscLs4HWn4HxMdy7f2rM1rTMp2mFg==
dependencies:
commander "^4.0.1"
convert-source-map "^1.1.0"
@@ -184,6 +184,18 @@
"@babel/types" "^7.7.4"
lodash "^4.17.13"
+"@babel/helper-module-transforms@^7.7.5":
+ version "7.7.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835"
+ integrity sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.7.4"
+ "@babel/helper-simple-access" "^7.7.4"
+ "@babel/helper-split-export-declaration" "^7.7.4"
+ "@babel/template" "^7.7.4"
+ "@babel/types" "^7.7.4"
+ lodash "^4.17.13"
+
"@babel/helper-optimise-call-expression@^7.7.4":
version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz#034af31370d2995242aa4df402c3b7794b2dcdf2"
@@ -502,21 +514,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-amd@^7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71"
- integrity sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ==
+"@babel/plugin-transform-modules-amd@^7.7.5":
+ version "7.7.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c"
+ integrity sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==
dependencies:
- "@babel/helper-module-transforms" "^7.7.4"
+ "@babel/helper-module-transforms" "^7.7.5"
"@babel/helper-plugin-utils" "^7.0.0"
babel-plugin-dynamic-import-node "^2.3.0"
-"@babel/plugin-transform-modules-commonjs@^7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3"
- integrity sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA==
+"@babel/plugin-transform-modules-commonjs@^7.7.5":
+ version "7.7.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345"
+ integrity sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==
dependencies:
- "@babel/helper-module-transforms" "^7.7.4"
+ "@babel/helper-module-transforms" "^7.7.5"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-simple-access" "^7.7.4"
babel-plugin-dynamic-import-node "^2.3.0"
@@ -576,10 +588,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-regenerator@^7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0"
- integrity sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw==
+"@babel/plugin-transform-regenerator@^7.7.5":
+ version "7.7.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9"
+ integrity sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==
dependencies:
regenerator-transform "^0.14.0"
@@ -635,10 +647,10 @@
"@babel/helper-create-regexp-features-plugin" "^7.7.4"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/preset-env@~7.7.4":
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8"
- integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==
+"@babel/preset-env@~7.7.6":
+ version "7.7.6"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2"
+ integrity sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ==
dependencies:
"@babel/helper-module-imports" "^7.7.4"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -668,8 +680,8 @@
"@babel/plugin-transform-function-name" "^7.7.4"
"@babel/plugin-transform-literals" "^7.7.4"
"@babel/plugin-transform-member-expression-literals" "^7.7.4"
- "@babel/plugin-transform-modules-amd" "^7.7.4"
- "@babel/plugin-transform-modules-commonjs" "^7.7.4"
+ "@babel/plugin-transform-modules-amd" "^7.7.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.7.5"
"@babel/plugin-transform-modules-systemjs" "^7.7.4"
"@babel/plugin-transform-modules-umd" "^7.7.4"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4"
@@ -677,7 +689,7 @@
"@babel/plugin-transform-object-super" "^7.7.4"
"@babel/plugin-transform-parameters" "^7.7.4"
"@babel/plugin-transform-property-literals" "^7.7.4"
- "@babel/plugin-transform-regenerator" "^7.7.4"
+ "@babel/plugin-transform-regenerator" "^7.7.5"
"@babel/plugin-transform-reserved-words" "^7.7.4"
"@babel/plugin-transform-shorthand-properties" "^7.7.4"
"@babel/plugin-transform-spread" "^7.7.4"
@@ -687,7 +699,7 @@
"@babel/plugin-transform-unicode-regex" "^7.7.4"
"@babel/types" "^7.7.4"
browserslist "^4.6.0"
- core-js-compat "^3.1.1"
+ core-js-compat "^3.4.7"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
semver "^5.5.0"
@@ -1022,10 +1034,10 @@
url-regex "~4.1.1"
video-extensions "~1.1.0"
-"@metascraper/helpers@^5.8.7":
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.8.7.tgz#b05f83f2a90001f7753c18a8b1bb978bd7c2f9d9"
- integrity sha512-gDErMAA3d1CdkGxvAG4cDi7D2+fReZpD6lzYNJ/gsq45U3Pdz7ltsAvbp4amK92bGKYYPZtnUq85Wrr+Q+e06Q==
+"@metascraper/helpers@^5.8.10", "@metascraper/helpers@^5.8.7":
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.8.10.tgz#efaae1d57afca6db1f0846852fe88d1608601f13"
+ integrity sha512-o7vrlNC+wzfArTkQcQfHKT4iHUYEQYs6hoORTWN7A1dj5v8P1wl5oOs0oAc7MNGJ3nWnex3/bq/5SUWV301Arg==
dependencies:
audio-extensions "0.0.0"
chrono-node "~1.3.11"
@@ -1101,56 +1113,56 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
-"@sentry/apm@5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.10.1.tgz#2ec20cef0f87f9f638ff78dd5092e1e9d36c4b7d"
- integrity sha512-VSFK8giRG5/lN0YSaOw8+Cru/8MVevmoHZ5JC9iDIt0H6sGTUjOBKIqTZ0eq2Y99Vn0N9dkxjeT0rOIvsrg0gA==
+"@sentry/apm@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.10.2.tgz#41a401b3964b68514439f8a595b12c6fd05ab21a"
+ integrity sha512-rPeAFsD/6ontvs7bsuHh+XAg1ohWo04ms08SNWqEvLRQJx7WfiWnjziyC0S3dXIYZDGdhruSsqQJPJN8r6Aj5g==
dependencies:
- "@sentry/hub" "5.10.1"
- "@sentry/minimal" "5.10.1"
+ "@sentry/hub" "5.10.2"
+ "@sentry/minimal" "5.10.2"
"@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.1"
+ "@sentry/utils" "5.10.2"
tslib "^1.9.3"
-"@sentry/core@5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.1.tgz#356551f111d4df38e60852607cc8cde0ed8ccc76"
- integrity sha512-MbiasA/cuMB0+9zVBGi5YLWRj7CdFQJOM29Vp8rm3xMaQDH0KHarpny1gOgMiLu/O/r8itjiZwKu+9pxOWGbeA==
+"@sentry/core@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.2.tgz#1cb64489e6f8363c3249415b49d3f1289814825f"
+ integrity sha512-sKVeFH3v8K8xw2vM5MKMnnyAAwih+JSE3pbNL0CcCCA+/SwX+3jeAo2BhgXev2SAR/TjWW+wmeC9TdIW7KyYbg==
dependencies:
- "@sentry/hub" "5.10.1"
- "@sentry/minimal" "5.10.1"
+ "@sentry/hub" "5.10.2"
+ "@sentry/minimal" "5.10.2"
"@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.1"
+ "@sentry/utils" "5.10.2"
tslib "^1.9.3"
-"@sentry/hub@5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.1.tgz#3be4a0705cd0cd074be0aab0dc418ecb72885989"
- integrity sha512-g+P+0cj6vKdf6Ct4S47MxHwSMIjtIadOwBhb4Lqwij5YPtQ4LpVr10peKbE+FMMvCNQSvQnJEhTDko+AE7AoYw==
+"@sentry/hub@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.2.tgz#25d9f36b8f7c5cb65cf486737fa61dc9bf69b7e3"
+ integrity sha512-hSlZIiu3hcR/I5yEhlpN9C0nip+U7hiRzRzUQaBiHO4YG4TC58NqnOPR89D/ekiuHIXzFpjW9OQmqtAMRoSUYA==
dependencies:
"@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.1"
+ "@sentry/utils" "5.10.2"
tslib "^1.9.3"
-"@sentry/minimal@5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.1.tgz#37104f81ef3b333c0f9e77ac94bfed348070dea3"
- integrity sha512-oKrLvKaah0xGVIYbS1I7dVbo73aWssfiT2ypl9DYt8MAFiwfiiXz68FlG4z9dPZ2jSz9Jm2SAYHFaYLvU26TBQ==
+"@sentry/minimal@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.2.tgz#267c2f3aa6877a0fe7a86971942e83f3ee616580"
+ integrity sha512-GalixiM9sckYfompH5HHTp9XT2BcjawBkcl1DMEKUBEi37+kUq0bivOBmnN1G/I4/wWOUdnAI/kagDWaWpbZPg==
dependencies:
- "@sentry/hub" "5.10.1"
+ "@sentry/hub" "5.10.2"
"@sentry/types" "5.10.0"
tslib "^1.9.3"
-"@sentry/node@^5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.10.1.tgz#cafbf3b0918c98fb9f99803ffe50056e32194bef"
- integrity sha512-kard7OXQDvYqmQD93bOkYhznqrbsiFNZ6+dIi13eo/kc2Au+v1Th1mGvr9JDRE/X07z6vJMYMiorKd351G3p/A==
+"@sentry/node@^5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.10.2.tgz#1f5d6deefb2c1549ddb542c10952cccf5f9a4ac2"
+ integrity sha512-1ib1hAhVtmfXOThpcCfR4S6wFopd6lHqgOMrAUPo9saHy8zseZPRC7iTWGoSPy2RMwjrURAk54VvFnLe7G+PdQ==
dependencies:
- "@sentry/apm" "5.10.1"
- "@sentry/core" "5.10.1"
- "@sentry/hub" "5.10.1"
+ "@sentry/apm" "5.10.2"
+ "@sentry/core" "5.10.2"
+ "@sentry/hub" "5.10.2"
"@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.1"
+ "@sentry/utils" "5.10.2"
cookie "^0.3.1"
https-proxy-agent "^3.0.0"
lru_map "^0.3.3"
@@ -1161,10 +1173,10 @@
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.10.0.tgz#4f0ba31b6e4d5371112c38279f11f66c73b43746"
integrity sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==
-"@sentry/utils@5.10.1":
- version "5.10.1"
- resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.1.tgz#eeb3ede85a9b5b1cd1aad7e3157052bee0d42551"
- integrity sha512-zdv03sINfJ8QXSHP49845qhkbdNUrX20AagUY+Arq2zxmM4XxnRVA7dtWDkyy55bTt0ziRuSikBxR3266t8mDg==
+"@sentry/utils@5.10.2":
+ version "5.10.2"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.2.tgz#261f575079d30aaf604e59f5f4de0aa21db22252"
+ integrity sha512-UcbbaFpYrGSV448lQ16Cr+W/MPuKUflQQUdrMCt5vgaf5+M7kpozlcji4GGGZUCXIA7oRP93ABoXj55s1OM9zw==
dependencies:
"@sentry/types" "5.10.0"
tslib "^1.9.3"
@@ -1602,37 +1614,37 @@ apollo-cache-control@^0.8.8:
apollo-server-env "^2.4.3"
graphql-extensions "^0.10.7"
-apollo-cache-inmemory@~1.6.3:
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
- integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==
+apollo-cache-inmemory@~1.6.5:
+ version "1.6.5"
+ resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a"
+ integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA==
dependencies:
- apollo-cache "^1.3.2"
- apollo-utilities "^1.3.2"
+ apollo-cache "^1.3.4"
+ apollo-utilities "^1.3.3"
optimism "^0.10.0"
ts-invariant "^0.4.0"
- tslib "^1.9.3"
+ tslib "^1.10.0"
-apollo-cache@1.3.2, apollo-cache@^1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.2.tgz#df4dce56240d6c95c613510d7e409f7214e6d26a"
- integrity sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg==
+apollo-cache@1.3.4, apollo-cache@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42"
+ integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA==
dependencies:
- apollo-utilities "^1.3.2"
- tslib "^1.9.3"
+ apollo-utilities "^1.3.3"
+ tslib "^1.10.0"
-apollo-client@~2.6.4:
- version "2.6.4"
- resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.4.tgz#872c32927263a0d34655c5ef8a8949fbb20b6140"
- integrity sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==
+apollo-client@~2.6.8:
+ version "2.6.8"
+ resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537"
+ integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw==
dependencies:
"@types/zen-observable" "^0.8.0"
- apollo-cache "1.3.2"
+ apollo-cache "1.3.4"
apollo-link "^1.0.0"
- apollo-utilities "1.3.2"
+ apollo-utilities "1.3.3"
symbol-observable "^1.0.2"
ts-invariant "^0.4.0"
- tslib "^1.9.3"
+ tslib "^1.10.0"
zen-observable "^0.8.0"
apollo-datasource@^0.6.3:
@@ -1835,15 +1847,15 @@ apollo-tracing@^0.8.8:
apollo-server-env "^2.4.3"
graphql-extensions "^0.10.7"
-apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
- integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
+apollo-utilities@1.3.3, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c"
+ integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw==
dependencies:
"@wry/equality" "^0.1.2"
fast-json-stable-stringify "^2.0.0"
ts-invariant "^0.4.0"
- tslib "^1.9.3"
+ tslib "^1.10.0"
aproba@^1.0.3:
version "1.2.0"
@@ -1918,6 +1930,15 @@ array-unique@^0.3.2:
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
+array.prototype.flat@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.2.tgz#8f3c71d245ba349b6b64b4078f76f5576f1fd723"
+ integrity sha512-VXjh7lAL4KXKF2hY4FnEW9eRW6IhdvFW1sN/JwLbmECbCgACCnBHNyP3lFiYuttr0jxRN9Bsc5+G27dMseSWqQ==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.15.0"
+ function-bind "^1.1.1"
+
arrify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
@@ -2233,14 +2254,14 @@ browser-resolve@^1.11.3:
dependencies:
resolve "1.1.7"
-browserslist@^4.6.0, browserslist@^4.6.6:
- version "4.6.6"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
- integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
+browserslist@^4.6.0, browserslist@^4.8.2:
+ version "4.8.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289"
+ integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==
dependencies:
- caniuse-lite "^1.0.30000984"
- electron-to-chromium "^1.3.191"
- node-releases "^1.1.25"
+ caniuse-lite "^1.0.30001015"
+ electron-to-chromium "^1.3.322"
+ node-releases "^1.1.42"
bser@^2.0.0:
version "2.1.0"
@@ -2319,10 +2340,10 @@ camelize@1.0.0:
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
-caniuse-lite@^1.0.30000984:
- version "1.0.30000989"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
- integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
+caniuse-lite@^1.0.30001015:
+ version "1.0.30001015"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0"
+ integrity sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2683,12 +2704,12 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-core-js-compat@^3.1.1:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.2.1.tgz#0cbdbc2e386e8e00d3b85dc81c848effec5b8150"
- integrity sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==
+core-js-compat@^3.4.7:
+ version "3.4.8"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.8.tgz#f72e6a4ed76437ea710928f44615f926a81607d5"
+ integrity sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ==
dependencies:
- browserslist "^4.6.6"
+ browserslist "^4.8.2"
semver "^6.3.0"
core-js@^2.4.0, core-js@^2.6.5:
@@ -2874,7 +2895,7 @@ date-fns@2.8.1:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.8.1.tgz#2109362ccb6c87c3ca011e9e31f702bc09e4123b"
integrity sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg==
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -3156,10 +3177,10 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-electron-to-chromium@^1.3.191:
- version "1.3.237"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.237.tgz#39c5d1da59d6fd16ff705b97e772bb3b5dfda7e4"
- integrity sha512-SPAFjDr/7iiVK2kgTluwxela6eaWjjFkS9rO/iYpB/KGXgccUom5YC7OIf19c8m8GGptWxLU0Em8xM64A/N7Fg==
+electron-to-chromium@^1.3.322:
+ version "1.3.322"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8"
+ integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==
emoji-regex@^7.0.1:
version "7.0.3"
@@ -3219,6 +3240,22 @@ es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.7.0:
is-regex "^1.0.4"
object-keys "^1.0.12"
+es-abstract@^1.15.0:
+ version "1.16.3"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.3.tgz#52490d978f96ff9f89ec15b5cf244304a5bca161"
+ integrity sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==
+ dependencies:
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+ is-callable "^1.1.4"
+ is-regex "^1.0.4"
+ object-inspect "^1.7.0"
+ object-keys "^1.1.1"
+ string.prototype.trimleft "^2.1.0"
+ string.prototype.trimright "^2.1.0"
+
es-to-primitive@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
@@ -3228,6 +3265,15 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es-to-primitive@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+ integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+ dependencies:
+ is-callable "^1.1.4"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.2"
+
es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.46:
version "0.10.50"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
@@ -3313,12 +3359,12 @@ eslint-import-resolver-node@^0.3.2:
debug "^2.6.9"
resolve "^1.5.0"
-eslint-module-utils@^2.4.0:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c"
- integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==
+eslint-module-utils@^2.4.1:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c"
+ integrity sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw==
dependencies:
- debug "^2.6.8"
+ debug "^2.6.9"
pkg-dir "^2.0.0"
eslint-plugin-es@^2.0.0:
@@ -3329,22 +3375,23 @@ eslint-plugin-es@^2.0.0:
eslint-utils "^1.4.2"
regexpp "^3.0.0"
-eslint-plugin-import@~2.18.2:
- version "2.18.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
- integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
+eslint-plugin-import@~2.19.1:
+ version "2.19.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz#5654e10b7839d064dd0d46cd1b88ec2133a11448"
+ integrity sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw==
dependencies:
array-includes "^3.0.3"
+ array.prototype.flat "^1.2.1"
contains-path "^0.1.0"
debug "^2.6.9"
doctrine "1.5.0"
eslint-import-resolver-node "^0.3.2"
- eslint-module-utils "^2.4.0"
+ eslint-module-utils "^2.4.1"
has "^1.0.3"
minimatch "^3.0.4"
object.values "^1.1.0"
read-pkg-up "^2.0.0"
- resolve "^1.11.0"
+ resolve "^1.12.0"
eslint-plugin-jest@~23.1.1:
version "23.1.1"
@@ -3365,10 +3412,10 @@ eslint-plugin-node@~10.0.0:
resolve "^1.10.1"
semver "^6.1.0"
-eslint-plugin-prettier@~3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba"
- integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA==
+eslint-plugin-prettier@~3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
+ integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==
dependencies:
prettier-linter-helpers "^1.0.0"
@@ -4205,6 +4252,11 @@ has-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
+has-symbols@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+ integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -5758,12 +5810,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.8.7:
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.8.7.tgz#ce27b1f4056c1d1cbaa2cec0e819c3704f38fff4"
- integrity sha512-ew9KZKOIl3u0500j7qIR/ZNiVtSohuyyiIWSxJVEeeguEOwAhMpOrpYAEkvKRo5CB89F2PNBIsXJIzMC4BWFrw==
+metascraper-audio@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.8.10.tgz#bc7bc0471ee178ab747baec4fb9bf7443078980d"
+ integrity sha512-uR4PCG7mxz7GLZ3I3x83sTCAaD/+MMTSf5rtP+shfdGJCm6h3mNmUpZm6hlBunmBx/PpDpwdI34rkl2A8SUjnQ==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
metascraper-author@^5.8.7:
version "5.8.7"
@@ -5787,19 +5839,19 @@ metascraper-date@^5.8.7:
dependencies:
"@metascraper/helpers" "^5.8.7"
-metascraper-description@^5.8.7:
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.8.7.tgz#e85ce218daf33b74813b1523ad7dc7dc3fb128af"
- integrity sha512-KOv5gnQVvGF1CgpUczu7KJm76rWJ7SH5UFcqFST60hRNgR9xy0y3aHbVDOhZkjNN4UKqnxMF6XTS/WaQxCK/AA==
+metascraper-description@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.8.10.tgz#1b69f59fa76263fcd2c15f8ce73052b81900177a"
+ integrity sha512-0stYkl5OPpM0yM6Dl3WcXxLjl2gY5k77E4seeHOqHAUx1EKXNgrSrtO0I3PX9p6vcxP+WBtK6zlqHYU4qAMlSA==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
-metascraper-image@^5.8.7:
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.8.7.tgz#d24697c5b5a6ba688948c48fadcb5fffeb6c703d"
- integrity sha512-OMK+PFnHeavCSuEJY5tFkG5tdl/luYmPys7PKkJIwC8A8q5qoAC0InIUu+c0SDrdf4nzOj083DZTp32YQxYF5A==
+metascraper-image@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.8.10.tgz#fe21811ca88eef13e64812462fb5a21ee48933dc"
+ integrity sha512-WOPnTupaDEl58iZp0M6kFlUcRSRQFSPWATPUi3AeW31VJM2sepxmJlqc5qVFTen/Lm+kI23firrvEg5N8tFUVA==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
metascraper-lang-detector@^4.10.2:
version "4.10.2"
@@ -5810,19 +5862,19 @@ metascraper-lang-detector@^4.10.2:
franc "~4.0.0"
iso-639-3 "~1.1.0"
-metascraper-lang@^5.8.9:
- version "5.8.9"
- resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.8.9.tgz#589bac0fdc523b5b6e6317a7b6295474eedfb872"
- integrity sha512-VMiU+T9LFsra/bBc0w0+fw6lk8Snb/ULoIvHUF0+5wvkv4KzQicc0z1lTAL/28Et2Xa+R5Km5A9Ts7LYuQRqVw==
+metascraper-lang@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.8.10.tgz#b8827282dea500b68e49ebbe8b0081fb6b6584d5"
+ integrity sha512-qydko4UkLGqTimKzT+AkcIaXOo7/GkHGtclGiLae80lHeKzI5NG7kYN4eMv1r4BfBkcluSNeJ/P532T6ZD2Y1Q==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
-metascraper-logo@^5.8.7:
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.8.7.tgz#5efb7e6c5f91ccad812e2d9ec3facfef179f40b6"
- integrity sha512-QudGVJBBeXLWU54Xw2PmnsTf+qPUnbyYaOl4aFLg2wkLLza1GbuvOYGMiH9Y8k0WcRoesi9sQk+P0a/611blew==
+metascraper-logo@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.8.10.tgz#8e0dc0296d71db03307584ecdb57cd3fcbad1d4b"
+ integrity sha512-l5LkzZcVzrKclzf3JGx2cnCtPI/8Rf+EQV/SfXUqz7FUwgfT3uzRw9wBbqP25056ukh6aOuywGClTdnEu2PJcw==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
metascraper-publisher@^5.8.7:
version "5.8.7"
@@ -5831,20 +5883,20 @@ metascraper-publisher@^5.8.7:
dependencies:
"@metascraper/helpers" "^5.8.7"
-metascraper-soundcloud@^5.8.9:
- version "5.8.9"
- resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.8.9.tgz#5d02538078114c5ab25c46df4afc3f45a94b3d7c"
- integrity sha512-0otAe2E4N/KN2UqopJAM9NFZfSMyll2Q0XKhicfV/d+6Q1ERT7LWA/vwhBmxFwQzzX2mxZ8JFKeXUf6OZqEvVg==
+metascraper-soundcloud@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.8.10.tgz#c281a35e2e7289006bd304dfb4074f01451e7f26"
+ integrity sha512-IBGGBFrzRiS1bTyR9+eJwv+fPvC8KoggpAZnGPABep4ZhfajblI3B+8U1kIXHMaFR4b1BaD4d+tWh3gNLZCjwQ==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
tldts "~5.6.2"
-metascraper-title@^5.8.7:
- version "5.8.7"
- resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.8.7.tgz#aecbbd9515bd74d2aeafa587c83447d926508ba0"
- integrity sha512-u+5KeJbsFKpi+pMnG71Gd49OLDQpkjiGIRTddhCZQhb45qHoTlGKN1nZuQ8nqJI6+ARWicFqtquomkaRXfBEnw==
+metascraper-title@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.8.10.tgz#c25dc8e8ad7073c18c8d25db0b855f62d3d986bd"
+ integrity sha512-CauBJmLYtS+AZ9KJfnfJHp/tzUTo9yup56P/7aaOBcfVA5PWg3xdI1lVXJegmiTsBCyDEzWRVJ41f/ZlMjbAsg==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
lodash "~4.17.15"
metascraper-url@^5.8.7:
@@ -5854,20 +5906,20 @@ metascraper-url@^5.8.7:
dependencies:
"@metascraper/helpers" "^5.8.7"
-metascraper-video@^5.8.9:
- version "5.8.9"
- resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.9.tgz#23c0fe71fae5088bc8e11bfa537eff80658aa6d9"
- integrity sha512-xaimkGz1Txsd9qHUN2U5HyFMP8tkrb5LuW8bCo+0kdTu5c00HGurvs0/BpWrTW/CzUQBNl/uEybeDXm8J++03g==
+metascraper-video@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.10.tgz#c43bdc3d4dc7ff97b94d45e0050fb50091da27be"
+ integrity sha512-ofO7OLt73iMZM6IkA3iHtD1EzbEeiTYJK/xKBp+Awyl/dLUWKfsFjOAjkz9XDzLANRT+7+rwzqQmc+a2/rBVVg==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
lodash "~4.17.15"
-metascraper-youtube@^5.8.9:
- version "5.8.9"
- resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.8.9.tgz#595f5e384e0db519378ca2023bd8aa6603866c9d"
- integrity sha512-Zuew1tLSC14ceL9ZaNvlQ4GmFopbYDalr8gL+Ofo4ha4jKyX58VaPQtmIgASAJv/jlOXd9zCwEdhNw8/YyZZWw==
+metascraper-youtube@^5.8.10:
+ version "5.8.10"
+ resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.8.10.tgz#c2b84b9faf8d4bd326a0a048e61cdbeefc7263ab"
+ integrity sha512-2QLqIqc8FWGJmGEwvoWDdEZnSCLg5lzH/3qZm0P9joFGA6WWrfpaONCVW4M72xfVHv/WwEblKZERzlbJNEhGVg==
dependencies:
- "@metascraper/helpers" "^5.8.7"
+ "@metascraper/helpers" "^5.8.10"
get-video-id "~3.1.4"
is-reachable "~4.0.0"
p-locate "~4.1.0"
@@ -6100,7 +6152,7 @@ neo-async@^2.6.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
-neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6:
+neo4j-driver@^1.7.3, neo4j-driver@^1.7.6, neo4j-driver@~1.7.6:
version "1.7.6"
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49"
integrity sha512-6c3ALO3vYDfUqNoCy8OFzq+fQ7q/ab3LCuJrmm8P04M7RmyRCCnUtJ8IzSTGbiZvyhcehGK+azNDAEJhxPV/hA==
@@ -6109,10 +6161,10 @@ neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6:
text-encoding-utf-8 "^1.0.2"
uri-js "^4.2.2"
-neo4j-graphql-js@^2.10.0:
- version "2.10.0"
- resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.10.0.tgz#4298793756d839dedb98bc3e50a2bd40a311874d"
- integrity sha512-jRdIyw+DHg9gfB6pWKb1ZHMR9rXIl7qf51efjUHIRHRbVR3RCcw1cKyONkq4LE8v2bHc7QDrKwJs+GQ1SRxDug==
+neo4j-graphql-js@^2.10.2:
+ version "2.10.2"
+ resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.10.2.tgz#e67d1aab6441b28f276adf0f6d655720983b9b84"
+ integrity sha512-CgtKEgrWgSJBjuKQ5CEPt4tcG1z14oAB3UWQjX8scDlUag0iWofgzpPlrc3brn+RitfeEc3FuMSru8E9dVDJPg==
dependencies:
"@babel/runtime" "^7.5.5"
"@babel/runtime-corejs2" "^7.5.5"
@@ -6122,14 +6174,14 @@ neo4j-graphql-js@^2.10.0:
lodash "^4.17.15"
neo4j-driver "^1.7.3"
-neode@^0.3.3:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.3.tgz#a539830cce6f6e4825462f6cb03f2969a0003f1b"
- integrity sha512-pArHG1hD2kVwrzLlz6B1+IgdOJRQj/BgR6KzH6DlVzSA6geoZRe68fbpvmOJtzyPU7iuUYxXVk87PpPM1A7dlg==
+neode@^0.3.6:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.6.tgz#7daf791eff6d170e52c338ea2e5cca6fdc6bfbe3"
+ integrity sha512-jCskCPobtHpsIIYQD72h5lRjMJEX70KwIeqgpt1VOLI+d1zJZvUlDkcOKgarAW0fmwtHIrPOP6mLPe5G/ZG9+g==
dependencies:
"@hapi/joi" "^15.1.0"
dotenv "^4.0.0"
- neo4j-driver "^1.7.5"
+ neo4j-driver "^1.7.6"
uuid "^3.3.2"
next-tick@^1.0.0:
@@ -6204,12 +6256,12 @@ node-pre-gyp@^0.12.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.1.25:
- version "1.1.28"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.28.tgz#503c3c70d0e4732b84e7aaa2925fbdde10482d4a"
- integrity sha512-AQw4emh6iSXnCpDiFe0phYcThiccmkNWMZnFZ+lDJjAP8J0m2fVd59duvUUyuTirQOhIAajTFkzG6FHCLBO59g==
+node-releases@^1.1.42:
+ version "1.1.42"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7"
+ integrity sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==
dependencies:
- semver "^5.3.0"
+ semver "^6.3.0"
nodemailer-html-to-text@^3.1.0:
version "3.1.0"
@@ -6218,15 +6270,15 @@ nodemailer-html-to-text@^3.1.0:
dependencies:
html-to-text "^5.1.1"
-nodemailer@^6.4.1:
- version "6.4.1"
- resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.1.tgz#f70b40355b7b08f1f80344b353970a4f8f664370"
- integrity sha512-mSQAzMim8XIC1DemK9TifDTIgASfoJEllG5aC1mEtZeZ+FQyrSOdGBRth6JRA1ERzHQCET3QHVSd9Kc6mh356g==
+nodemailer@^6.4.2:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.2.tgz#7147550e32cdc37453380ab78d2074533966090a"
+ integrity sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ==
-nodemon@~2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.1.tgz#cec436f8153ad5d3e6c27c304849a06cabea71cc"
- integrity sha512-UC6FVhNLXjbbV4UzaXA3wUdbEkUZzLGgMGzmxvWAex5nzib/jhcSHVFlQODdbuUHq8SnnZ4/EABBAbC3RplvPg==
+nodemon@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0"
+ integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==
dependencies:
chokidar "^3.2.2"
debug "^3.2.6"
@@ -6382,7 +6434,12 @@ object-hash@^2.0.0:
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.0.tgz#7c4cc341eb8b53367312a7c546142f00c9e0ea20"
integrity sha512-I7zGBH0rDKwVGeGZpZoFaDhIwvJa3l1CZE+8VchylXbInNiCj7sxxea9P5dTM4ftKR5//nrqxrdeGSTWL2VpBA==
-object-keys@^1.0.11, object-keys@^1.0.12:
+object-inspect@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
+ integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
+
+object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -7259,7 +7316,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0:
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
@@ -7452,6 +7509,11 @@ serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.17.1"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@@ -7788,6 +7850,22 @@ string.prototype.padend@^3.0.0:
es-abstract "^1.4.3"
function-bind "^1.0.2"
+string.prototype.trimleft@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
+ integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==
+ dependencies:
+ define-properties "^1.1.3"
+ function-bind "^1.1.1"
+
+string.prototype.trimright@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58"
+ integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==
+ dependencies:
+ define-properties "^1.1.3"
+ function-bind "^1.1.1"
+
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -8172,7 +8250,7 @@ ts-invariant@^0.4.0:
dependencies:
tslib "^1.9.3"
-tslib@1.10.0, tslib@^1.9.0, tslib@^1.9.3:
+tslib@1.10.0, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
diff --git a/cypress.env.template.json b/cypress.env.template.json
deleted file mode 100644
index 8eda47154..000000000
--- a/cypress.env.template.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "BACKEND_HOST": "http://localhost:4000",
- "NEO4J_URI": "bolt://localhost:7687",
- "NEO4J_USERNAME": "neo4j",
- "NEO4J_PASSWORD": "letmein"
-}
diff --git a/cypress/README.md b/cypress/README.md
index 2adcff925..662d0b51c 100644
--- a/cypress/README.md
+++ b/cypress/README.md
@@ -16,12 +16,7 @@ First, you have to tell cypress how to connect to your local neo4j database
among other things. You can copy our template configuration and change the new
file according to your needs.
-Make sure you are at the root level of the project. Then:
-```bash
-# in the top level folder Human-Connection/
-$ cp cypress.env.template.json cypress.env.json
-```
-To start the services that are required for cypress testing, run this:
+To start the services that are required for cypress testing, run:
```bash
# in the top level folder Human-Connection/
diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js
index 814159a34..a680986f4 100644
--- a/cypress/integration/common/post.js
+++ b/cypress/integration/common/post.js
@@ -3,6 +3,14 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps";
const narratorAvatar =
"https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg";
+When("I type in a comment with {int} characters", size => {
+ var c="";
+ for (var i = 0; i < size; i++) {
+ c += "c"
+ }
+ cy.get(".editor .ProseMirror").type(c);
+});
+
Then("I click on the {string} button", text => {
cy.get("button")
.contains(text)
@@ -23,6 +31,16 @@ Then("I should see my comment", () => {
.should("contain", "today at");
});
+Then("I should see the entirety of my comment", () => {
+ cy.get("div.comment")
+ .should("not.contain", "show more")
+});
+
+Then("I should see an abreviated version of my comment", () => {
+ cy.get("div.comment")
+ .should("contain", "show more")
+});
+
Then("the editor should be cleared", () => {
cy.get(".ProseMirror p").should("have.class", "is-empty");
});
diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js
index 9f62a2818..25f4c6e35 100644
--- a/cypress/integration/common/report.js
+++ b/cypress/integration/common/report.js
@@ -1,5 +1,6 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
import { VERSION } from '../../constants/terms-and-conditions-version.js'
+import { gql } from '../../../backend/src/helpers/jest'
/* global cy */
@@ -128,7 +129,7 @@ Given('somebody reported the following posts:', table => {
cy.factory()
.create('User', submitter)
.authenticateAs(submitter)
- .mutate(`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ .mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
id
}
diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature
index e7e462824..50284d6f5 100644
--- a/cypress/integration/post/Comment.feature
+++ b/cypress/integration/post/Comment.feature
@@ -20,3 +20,19 @@ Feature: Post Comment
Then my comment should be successfully created
And I should see my comment
And the editor should be cleared
+
+ Scenario: View medium length comments
+ Given I visit "post/bWBjpkTKZp/101-essays"
+ And I type in a comment with 305 characters
+ And I click on the "Comment" button
+ Then my comment should be successfully created
+ And I should see the entirety of my comment
+ And the editor should be cleared
+
+ Scenario: View long comments
+ Given I visit "post/bWBjpkTKZp/101-essays"
+ And I type in a comment with 1205 characters
+ And I click on the "Comment" button
+ Then my comment should be successfully created
+ And I should see an abreviated version of my comment
+ And the editor should be cleared
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index f8bc76d50..f52b38faf 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -18,8 +18,8 @@ import helpers from "./helpers";
import users from "../fixtures/users.json";
import { GraphQLClient, request } from 'graphql-request'
import { gql } from '../../backend/src/helpers/jest'
+import config from '../../backend/src/config'
-const backendHost = Cypress.env('BACKEND_HOST')
const switchLang = name => {
cy.get(".locale-menu").click();
cy.contains(".locale-menu-popover a", name).click();
@@ -31,7 +31,7 @@ const authenticatedHeaders = async (variables) => {
login(email: $email, password: $password)
}
`
- const response = await request(backendHost, mutation, variables)
+ const response = await request(config.GRAPHQL_URI, mutation, variables)
return { authorization: `Bearer ${response.login}` }
}
@@ -100,8 +100,7 @@ Cypress.Commands.add(
'authenticateAs',
async ({email, password}) => {
const headers = await authenticatedHeaders({ email, password })
- console.log(headers)
- return new GraphQLClient(backendHost, { headers })
+ return new GraphQLClient(config.GRAPHQL_URI, { headers })
}
)
diff --git a/cypress/support/factories.js b/cypress/support/factories.js
index da67debd5..234584e09 100644
--- a/cypress/support/factories.js
+++ b/cypress/support/factories.js
@@ -1,16 +1,10 @@
import Factory from '../../backend/src/seed/factories'
-import { getDriver, neode as getNeode } from '../../backend/src/bootstrap/neo4j'
-import setupNeode from '../../backend/src/bootstrap/neode'
+import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
import neode from 'neode'
-const backendHost = Cypress.env('SEED_SERVER_HOST')
-const neo4jConfigs = {
- uri: Cypress.env('NEO4J_URI'),
- username: Cypress.env('NEO4J_USERNAME'),
- password: Cypress.env('NEO4J_PASSWORD')
-}
-const neo4jDriver = getDriver(neo4jConfigs)
-const factoryOptions = { seedServerHost: backendHost, neo4jDriver, neodeInstance: setupNeode(neo4jConfigs)}
+const neo4jDriver = getDriver()
+const neodeInstance = getNeode()
+const factoryOptions = { neo4jDriver, neodeInstance }
const factory = Factory(factoryOptions)
beforeEach(async () => {
@@ -18,7 +12,7 @@ beforeEach(async () => {
})
Cypress.Commands.add('neode', () => {
- return setupNeode(neo4jConfigs)
+ return neodeInstance
})
Cypress.Commands.add(
'first',
diff --git a/locale/ru.json b/locale/ru.json
new file mode 100644
index 000000000..75483edfb
--- /dev/null
+++ b/locale/ru.json
@@ -0,0 +1,814 @@
+{
+ "actions": {
+ "cancel": "Отменить",
+ "create": "Создать",
+ "delete": "Удалить",
+ "edit": "Редактировать",
+ "loading": "загрузка",
+ "loadMore": "Загрузить ещё",
+ "save": "Сохранить"
+ },
+ "admin": {
+ "categories": {
+ "categoryName": "Имя",
+ "name": "Категории",
+ "postCount": "Посты"
+ },
+ "dashboard": {
+ "comments": "Комментарии",
+ "follows": "Подписки",
+ "invites": "Приглашения",
+ "name": "Панель управления",
+ "notifications": "Уведомления",
+ "organizations": "Организации",
+ "posts": "Посты",
+ "projects": "Проекты",
+ "shouts": "Выкрики",
+ "users": "Пользователи"
+ },
+ "donations": {
+ "goal": "Необходимы ежемесячные пожертвования",
+ "name": "Информация о пожертвованиях",
+ "progress": "Пожертвования собраны",
+ "successfulUpdate": "Информация о пожертвованиях успешно обновлена!"
+ },
+ "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": {
+ "categories": "Выберите от одной то трех категорий",
+ "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 лет или более",
+ "no-commercial": "У меня нет коммерческих намерений, и я не представляю коммерческое предприятие или организацию.",
+ "no-political": "Я не от имени какой-либо партии или политической организации в сети.",
+ "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": "Язык",
+ "languageSelectText": "Выберите язык",
+ "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": "Пользователь"
+ }
+ },
+ "donations": {
+ "amount-of-total": "{amount} из {total} € собрано",
+ "donate-now": "Пожертвуйте сейчас",
+ "donations-for": "Пожертвования для"
+ },
+ "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": "Другие фильтры"
+ },
+ "language": {
+ "all": "Все",
+ "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": {
+ "author": "Автор",
+ "content": "Содержа́ние",
+ "decideButton": "Подтвердить",
+ "decided": "Решил",
+ "decideModal": {
+ "cancel": "Отменить",
+ "Comment": {
+ "disable": {
+ "message": "Вы действительно хотите, чтобы комментарий \"{name}<\/b>\" остановиться и отключен<\/b>?",
+ "title": "Окончательно отключить комментарий"
+ },
+ "enable": {
+ "message": "Вы действительно хотите, чтобы комментарий \"{name}<\/b>\" остановиться и включен<\/b>?",
+ "title": "Окончательно включить комментарий"
+ }
+ },
+ "Post": {
+ "disable": {
+ "message": "Вы действительно хотите, чтобы пост \"{name}<\/b>\" остановиться и отключен<\/b>?",
+ "title": "Окончательно отключить пост"
+ },
+ "enable": {
+ "message": "Вы действительно хотите, чтобы пост \"{name}<\/b>\" остановиться и включен<\/b>?",
+ "title": "Окончательно включить пост"
+ }
+ },
+ "submit": "Подтвердить решение",
+ "User": {
+ "disable": {
+ "message": "Вы действительно хотите, чтобы пользователь \"{name}<\/b>\" остановиться и отключен<\/b>?",
+ "title": "Окончательно отключить пользователя"
+ },
+ "enable": {
+ "message": "Вы уверены, что хотите поделиться пользователем \"{name}<\/b>\"?",
+ "title": "Окончательно включить пост"
+ }
+ }
+ },
+ "decision": "Решение",
+ "DecisionSuccess": "Решил успешно!",
+ "disabled": "Отключен",
+ "disabledAt": "Отключено на",
+ "disabledBy": "Отключил(а)",
+ "empty": "Поздравляю, модерировать нечего.",
+ "enabled": "Включен",
+ "enabledAt": "Включено на",
+ "enabledBy": "Включено с",
+ "filterLabel": {
+ "all": "Все",
+ "closed": "Закрыто",
+ "reviewed": "Рассмотренный",
+ "unreviewed": "Нерассмотренный"
+ },
+ "moreDetails": "Посмотреть подробности",
+ "name": "Отчеты",
+ "noDecision": "Нет решения!",
+ "numberOfUsers": "{count} пользователи",
+ "previousDecision": "Предыдущее решение:",
+ "reasonCategory": "Категория",
+ "reasonDescription": "Описание",
+ "reportedOn": "Дата",
+ "reporter": "Сообщил(а)",
+ "status": "Текущее состояние",
+ "submitter": "Сообщил(а)"
+ }
+ },
+ "notifications": {
+ "comment": "Комментарий",
+ "content": "Контент",
+ "empty": "Извините, на данный момент у вас нет уведомлений.",
+ "filterLabel": {
+ "all": "Все",
+ "read": "Прочитанные",
+ "unread": "Непрочитанные"
+ },
+ "pageLink": "Все уведомления",
+ "post": "Пост",
+ "reason": {
+ "commented_on_post": "Комментарий к посту...",
+ "mentioned_in_comment": "Упоминание в комментарии....",
+ "mentioned_in_post": "Упоминание в посте...."
+ },
+ "title": "Уведомления",
+ "user": "Пользователь"
+ },
+ "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} человек... ::: и ещё {number} человека... ::: и ещё {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": "Псевдоним",
+ "unblock": "Разблокировать"
+ },
+ "empty": "Вы пока никого не блокировали.",
+ "explanation": {
+ "closing": "На данный момент этого должно быть достаточно, чтобы заблокированные пользователи больше вас не беспокоили.",
+ "intro": "Если блокируете другого пользователя, происходит следующее:",
+ "notifications": "Заблокированные пользователи больше не будут получать уведомления об упоминаниях в ваших постах.",
+ "search": "Посты заблокированных пользователей не отображаются в результатах поиска.",
+ "their-perspective": "И наоборот — заблокированный пользователь больше не видит ваши посты в своей ленте.",
+ "your-perspective": "Посты заблокированного пользователя не отображаются в персональной ленте."
+ },
+ "how-to": "Вы можете блокировать других пользователей на странице их профиля с помощью меню профиля.",
+ "name": "Заблокированные пользователи",
+ "unblock": "Разблокировать пользователей",
+ "unblocked": "{name} - снова разблокирован"
+ },
+ "data": {
+ "labelBio": "О себе",
+ "labelCity": "Город или регион",
+ "labelName": "Имя",
+ "labelSlug": "Уникальное имя пользователя",
+ "name": "Персональные данные",
+ "namePlaceholder": "Маша Медведева",
+ "success": "Персональные данные были успешно обновлены!"
+ },
+ "delete": {
+ "name": "Удалить аккаунт"
+ },
+ "deleteUserAccount": {
+ "accountDescription": "Обратите внимание, что ваши посты и комментарии важны для сообщества. Если вы все равно хотите их удалить, то вы должны отметить соответствующие опции ниже.",
+ "accountWarning": "Вы НЕ СМОЖЕТЕ<\/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": "Мои организации"
+ },
+ "privacy": {
+ "make-shouts-public": "Публиковать в моем публичном профиле статьи в которых я участвовал",
+ "name": "Конфиденциальность",
+ "success-update": "Настройки приватности сохранены"
+ },
+ "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": "Ссылка на профиль социальной сети",
+ "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": "Сделано с ❤",
+ "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). Вы можете найти их здесь: https:\/\/support.human-connection.org\/kb\/ <\/a>",
+ "title": "Помощь и вопросы"
+ },
+ "moderation": {
+ "description": "Пока наши финансовые возможности не позволяют нам реализовать полноценную систему модерации, поэтому мы осуществляем упрощенную модерацию собственными силами и с помощью волонтёров. Мы специально обучаем этих модераторов, поэтому только они принимают соответствующие решения. Модераторы действуют анонимно. Вы можете сообщать нам о постах, комментариях и пользователях (например, если они предоставляют информацию в своем профиле или имеют изображения, которые нарушают настоящие Условия использования). При обращении вы можете указать причину и дать краткое пояснение. Мы рассмотрим обращение и применим санкции в случае необходимости, например, путем блокировки постов, комментариев или пользователей. К сожалению, в настоящее время ни вы ни пострадавший пользователь не получите от нас обратной связи, но мы планируем ряд улучшений в этом направлении. Несмотря на это, мы оставляем за собой право на применение санкций по причинам, которые не могут быть или ещё не указаны в нашем Кодексе поведения или настоящих Условиях использования.",
+ "title": "Модерация"
+ },
+ "newTermsAndConditions": "Новые условия и положения",
+ "no-commercial-use": {
+ "description": "Использование Human Connection сети не допускается в коммерческих целях. Это включает, но не ограничивается рекламой продуктов с коммерческими целями, размещением партнерских ссылок, прямым привлечением пожертвований или предоставлением финансовой поддержки для целей, которые не признаются благотворительными для целей налогообложения.",
+ "title": "Нет коммерческого использования"
+ },
+ "privacy-statement": {
+ "description": "Наша сеть — это социальная сеть знаний и действий. Поэтому для нас особенно важно, чтобы как можно больше контента было общедоступным. В процессе развития нашей сети будет добавлено больше возможностей для управления видимостью личных данных. Об этих новых функциях мы сообщим дополнительно. В противном случае вы должны думать о том, какие личные данные вы раскрываете о себе (или других). Это особенно актуально для содержания постов и комментариев, поскольку они имеют в основном общедоступный характер. Позже появятся возможности ограничения видимости вашего профиля. Часть условий использования — это наша политика конфиденциальности, которая информирует вас об обработке персональных данных в нашей сети: https:\/\/human-connection.org\/datenschutz\/#netzwerk<\/a> или https:\/\/human-connection.org\/datenschutz<\/a>. Наше заявление о конфиденциальности корректируется в соответствии с законодательством и характеристиками нашей сети и является действительной в настоящей версии.",
+ "title": "Заявление о конфиденциальности"
+ },
+ "terms-of-service": {
+ "description": "Следующие условия использования являются основой для использования нашей сети. При регистрации вы должны принять их, а мы при необходимости сообщим вам об изменениях. Сеть Human Connection работает в Германии и поэтому регулируется немецким законодательством. Место юрисдикции - Kirchheim \/ Teck. Подробности в выходных данных: https:\/\/human-connection.org\/en\/imprint<\/a>.",
+ "title": "Условия обслуживания"
+ },
+ "termsAndConditionsConfirmed": "Я прочитал(а) и подтверждаю Условия и положения<\/a>.",
+ "termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
+ "termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!",
+ "use-and-license": {
+ "description": "Если размещаемый в сети контент защищен правами на интеллектуальную собственность, вы предоставляете нам неисключительную, передаваемую, сублицензируемую и всемирную лицензию на использование этого контента для публикации в нашей сети. Эта лицензия заканчивается, как только вы удаляете свой контент или учетную запись. Помните, что другие пользователи могут продолжать делиться вашим контентом, и мы не можем его удалить.",
+ "title": "Использование и лицензия"
+ }
+ },
+ "user": {
+ "avatar": {
+ "submitted": "Успешная загрузка!"
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index d20232f20..33c094201 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "human-connection",
- "version": "0.1.11",
+ "version": "0.1.13",
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
"author": "Human Connection gGmbh",
"license": "MIT",
@@ -21,17 +21,17 @@
"version": "auto-changelog -p"
},
"devDependencies": {
- "@babel/core": "^7.7.2",
- "@babel/preset-env": "^7.7.4",
+ "@babel/core": "^7.7.5",
+ "@babel/preset-env": "^7.7.6",
"@babel/register": "^7.7.4",
"auto-changelog": "^1.16.2",
"bcryptjs": "^2.4.3",
"codecov": "^3.6.1",
"cross-env": "^6.0.3",
"cucumber": "^6.0.5",
- "cypress": "^3.7.0",
+ "cypress": "^3.8.0",
"cypress-cucumber-preprocessor": "^1.18.0",
- "cypress-file-upload": "^3.5.0",
+ "cypress-file-upload": "^3.5.1",
"cypress-plugin-retries": "^1.5.0",
"date-fns": "^2.8.1",
"dotenv": "^8.2.0",
diff --git a/scripts/docker_push.sh b/scripts/docker_push.sh
index 58f593a16..b342278b9 100755
--- a/scripts/docker_push.sh
+++ b/scripts/docker_push.sh
@@ -1,11 +1,10 @@
#!/usr/bin/env bash
ROOT_DIR=$(dirname "$0")/..
-DOCKER_CLI_EXPERIMENTAL=enabled
# BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)}
IFS='.' read -r major minor patch < $ROOT_DIR/VERSION
apps=(nitro-web nitro-backend neo4j maintenance)
-tags=(latest $major $major.$minor $major.$minor.$patch)
+tags=($major $major.$minor $major.$minor.$patch)
# These three docker images have already been built by now:
# docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT --target production -t humanconnection/nitro-backend:latest $ROOT_DIR/backend
@@ -17,13 +16,17 @@ echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
for app in "${apps[@]}"
do
+ SOURCE="humanconnection/${app}:latest"
+ echo "docker push $SOURCE"
+ docker push $SOURCE
+
for tag in "${tags[@]}"
do
- SOURCE="humanconnection/${app}:latest"
TARGET="humanconnection/${app}:${tag}"
- if docker manifest inspect $TARGET &> /dev/null; then
- echo "Docker image ${TARGET} already present, skipping ..."
+ if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $TARGET >/dev/null; then
+ echo "docker image ${TARGET} already present, skipping ..."
else
+ echo -e "docker tag $SOURCE $TARGET\ndocker push $TARGET"
docker tag $SOURCE $TARGET
docker push $TARGET
fi
diff --git a/webapp/Dockerfile b/webapp/Dockerfile
index 37a31d6f4..91f19a486 100644
--- a/webapp/Dockerfile
+++ b/webapp/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:13.1.0-alpine as base
+FROM node:13.3.0-alpine as base
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
EXPOSE 3000
diff --git a/webapp/Dockerfile.maintenance b/webapp/Dockerfile.maintenance
index 7195d0f1c..adcaca13a 100644
--- a/webapp/Dockerfile.maintenance
+++ b/webapp/Dockerfile.maintenance
@@ -1,4 +1,4 @@
-FROM node:13.1.0-alpine as build
+FROM node:13.3.0-alpine as build
LABEL Description="Maintenance page of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
EXPOSE 3000
diff --git a/webapp/README.md b/webapp/README.md
index 185557fc7..897bb56ca 100644
--- a/webapp/README.md
+++ b/webapp/README.md
@@ -6,14 +6,15 @@
```bash
# install all dependencies
+$ cd webapp/
$ yarn install
```
Copy:
```text
+# in webapp/
cp .env.template .env
-cp cypress.env.template.json cypress.env.json
```
Configure the files according to your needs and your local setup.
diff --git a/webapp/components/ContentMenu/ContentMenu.spec.js b/webapp/components/ContentMenu/ContentMenu.spec.js
index 8f93aa4a4..7894dea0e 100644
--- a/webapp/components/ContentMenu/ContentMenu.spec.js
+++ b/webapp/components/ContentMenu/ContentMenu.spec.js
@@ -22,10 +22,6 @@ describe('ContentMenu.vue', () => {
locale: () => 'en',
},
$router: {
- resolve: jest.fn(obj => {
- obj.href = '/post/edit/d23a4265-f5f7-4e17-9f86-85f714b4b9f8'
- return obj
- }),
push: jest.fn(),
},
}
@@ -76,7 +72,7 @@ describe('ContentMenu.vue', () => {
.at(0)
.find('span.ds-menu-item-link')
.attributes('to'),
- ).toBe('/post/edit/d23a4265-f5f7-4e17-9f86-85f714b4b9f8')
+ ).toBe('/post-edit-id')
})
it('can delete the contribution', () => {
diff --git a/webapp/components/ContentMenu/ContentMenu.vue b/webapp/components/ContentMenu/ContentMenu.vue
index d4c567437..25192c21e 100644
--- a/webapp/components/ContentMenu/ContentMenu.vue
+++ b/webapp/components/ContentMenu/ContentMenu.vue
@@ -17,7 +17,7 @@
@click.stop.prevent="openItem(item.route, toggleMenu)"
>
- {{ item.route.name }}
+ {{ item.route.label }}
@@ -58,17 +58,15 @@ export default {
if (this.resourceType === 'contribution') {
if (this.isOwner) {
routes.push({
- name: this.$t(`post.menu.edit`),
- path: this.$router.resolve({
- name: 'post-edit-id',
- params: {
- id: this.resource.id,
- },
- }).href,
+ label: this.$t(`post.menu.edit`),
+ name: 'post-edit-id',
+ params: {
+ id: this.resource.id,
+ },
icon: 'edit',
})
routes.push({
- name: this.$t(`post.menu.delete`),
+ label: this.$t(`post.menu.delete`),
callback: () => {
this.openModal('confirm', 'delete')
},
@@ -79,7 +77,7 @@ export default {
if (this.isAdmin) {
if (!this.resource.pinnedBy) {
routes.push({
- name: this.$t(`post.menu.pin`),
+ label: this.$t(`post.menu.pin`),
callback: () => {
this.$emit('pinPost', this.resource)
},
@@ -87,7 +85,7 @@ export default {
})
} else {
routes.push({
- name: this.$t(`post.menu.unpin`),
+ label: this.$t(`post.menu.unpin`),
callback: () => {
this.$emit('unpinPost', this.resource)
},
@@ -99,14 +97,14 @@ export default {
if (this.isOwner && this.resourceType === 'comment') {
routes.push({
- name: this.$t(`comment.menu.edit`),
+ label: this.$t(`comment.menu.edit`),
callback: () => {
this.$emit('showEditCommentMenu', true)
},
icon: 'edit',
})
routes.push({
- name: this.$t(`comment.menu.delete`),
+ label: this.$t(`comment.menu.delete`),
callback: () => {
this.openModal('confirm', 'delete')
},
@@ -116,7 +114,7 @@ export default {
if (!this.isOwner) {
routes.push({
- name: this.$t(`report.${this.resourceType}.title`),
+ label: this.$t(`report.${this.resourceType}.title`),
callback: () => {
this.openModal('report')
},
@@ -127,7 +125,7 @@ export default {
if (!this.isOwner && this.isModerator) {
if (!this.resource.disabled) {
routes.push({
- name: this.$t(`disable.${this.resourceType}.title`),
+ label: this.$t(`disable.${this.resourceType}.title`),
callback: () => {
this.openModal('disable')
},
@@ -135,7 +133,7 @@ export default {
})
} else {
routes.push({
- name: this.$t(`release.${this.resourceType}.title`),
+ label: this.$t(`release.${this.resourceType}.title`),
callback: () => {
this.openModal('release')
},
@@ -147,14 +145,14 @@ export default {
if (this.resourceType === 'user') {
if (this.isOwner) {
routes.push({
- name: this.$t(`settings.name`),
+ label: this.$t(`settings.name`),
path: '/settings',
icon: 'edit',
})
} else {
if (this.resource.isBlocked) {
routes.push({
- name: this.$t(`settings.blocked-users.unblock`),
+ label: this.$t(`settings.blocked-users.unblock`),
callback: () => {
this.$emit('unblock', this.resource)
},
@@ -162,7 +160,7 @@ export default {
})
} else {
routes.push({
- name: this.$t(`settings.blocked-users.block`),
+ label: this.$t(`settings.blocked-users.block`),
callback: () => {
this.$emit('block', this.resource)
},
@@ -186,7 +184,7 @@ export default {
if (route.callback) {
route.callback()
} else {
- this.$router.push(route.path)
+ this.$router.push(route)
}
toggleMenu()
},
diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue
index ec9fe9616..eeba47b72 100644
--- a/webapp/components/ContributionForm/ContributionForm.vue
+++ b/webapp/components/ContributionForm/ContributionForm.vue
@@ -38,23 +38,21 @@
{{ form.title.length }}/{{ formSchema.title.max }}
-
-
-
-
- {{ contentLength }}
-
-
-
- {{ contentLength }}
-
-
-
+
+
+
+ {{ contentLength }}
+
+
+
+ {{ contentLength }}
+
+
diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue
index 234d94d2d..6c8a1908a 100644
--- a/webapp/components/Editor/Editor.vue
+++ b/webapp/components/Editor/Editor.vue
@@ -24,7 +24,6 @@
import { Editor, EditorContent } from 'tiptap'
import { History } from 'tiptap-extensions'
import linkify from 'linkify-it'
-import stringHash from 'string-hash'
import { replace, build } from 'xregexp/xregexp-all.js'
import * as key from '../../constants/keycodes'
@@ -108,17 +107,6 @@ export default {
},
},
watch: {
- value: {
- immediate: true,
- handler: function(content, old) {
- const contentHash = stringHash(content)
- if (!content || contentHash === this.lastValueHash) {
- return
- }
- this.lastValueHash = contentHash
- this.$nextTick(() => this.editor.setContent(content))
- },
- },
placeholder: {
immediate: true,
handler: function(val) {
@@ -129,7 +117,7 @@ export default {
},
},
},
- created() {
+ mounted() {
this.editor = new Editor({
content: this.value || '',
doc: this.doc,
@@ -247,11 +235,7 @@ export default {
},
onUpdate(e) {
const content = e.getHTML()
- const contentHash = stringHash(content)
- if (contentHash !== this.lastValueHash) {
- this.lastValueHash = contentHash
- this.$emit('input', content)
- }
+ this.$emit('input', content)
},
toggleLinkInput(attrs, element) {
if (!this.isLinkInputActive && attrs && element) {
diff --git a/webapp/components/Embed/EmbedComponent.vue b/webapp/components/Embed/EmbedComponent.vue
index 5dc8ad00c..f1790304e 100644
--- a/webapp/components/Embed/EmbedComponent.vue
+++ b/webapp/components/Embed/EmbedComponent.vue
@@ -46,7 +46,7 @@
diff --git a/webapp/components/PostCard/PostCard.vue b/webapp/components/PostCard/PostCard.vue
index 79ba9e8f0..d5afe90e1 100644
--- a/webapp/components/PostCard/PostCard.vue
+++ b/webapp/components/PostCard/PostCard.vue
@@ -141,19 +141,10 @@ export default {
this.$emit('unpinPost', post)
},
},
- mounted() {
- const width = this.$el.offsetWidth
- const height = Math.min(width / this.post.imageAspectRatio, 2000)
- const imageElement = this.$el.querySelector('.ds-card-image')
-
- if (imageElement) {
- imageElement.style.height = `${height}px`
- }
- },
}
-