mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' into migrate-styleguide-buttons
This commit is contained in:
commit
c83f158ca4
@ -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
|
||||
|
||||
135
CHANGELOG.md
135
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)
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@ -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 <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
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 <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
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,
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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) => {
|
||||
213
backend/src/middleware/user/userMiddleware.spec.js
Normal file
213
backend/src/middleware/user/userMiddleware.spec.js
Normal file
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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',
|
||||
}),
|
||||
],
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
15
backend/src/schema/resolvers/helpers/databaseLogger.js
Normal file
15
backend/src/schema/resolvers/helpers/databaseLogger.js
Normal file
@ -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(),
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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 }),
|
||||
|
||||
@ -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',
|
||||
})
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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==
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"BACKEND_HOST": "http://localhost:4000",
|
||||
"NEO4J_URI": "bolt://localhost:7687",
|
||||
"NEO4J_USERNAME": "neo4j",
|
||||
"NEO4J_PASSWORD": "letmein"
|
||||
}
|
||||
@ -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/
|
||||
|
||||
@ -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");
|
||||
});
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
814
locale/ru.json
Normal file
814
locale/ru.json
Normal file
@ -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": "На адрес <b>{email}<\/b>было отправлено электронное письмо с дальнейшими инструкциями"
|
||||
},
|
||||
"title": "Сбросить пароль"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"create-user-account": {
|
||||
"error": "Не удалось создать учетную запись!",
|
||||
"help": "Может быть, подтверждение было недействительным? В случае возникновения проблем, не стесняйтесь обращаться за помощью, отправив нам письмо по электронной почте:",
|
||||
"success": "Учетная запись успешно создана!",
|
||||
"title": "Создать учетную запись"
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "Я прочитал и понял <a href=\"https:\/\/human-connection.org\/datenschutz\/\" target=\"_blank\"><ds-text bold color=\"primary\" >Заявление о конфиденциальности<\/ds-text><\/a>",
|
||||
"description": "Для начала работы введите свой адрес электронной почты:",
|
||||
"errors": {
|
||||
"email-exists": "Уже есть учетная запись пользователя с этим адресом электронной почты!",
|
||||
"invalid-invitation-token": "Похоже, что приглашение уже было использовано. Ссылку из приглашения можно использовать только один раз."
|
||||
},
|
||||
"invitation-code": "Код приглашения: <b>{code}<\/b>",
|
||||
"minimum-age": "Мне 18 лет или более",
|
||||
"no-commercial": "У меня нет коммерческих намерений, и я не представляю коммерческое предприятие или организацию.",
|
||||
"no-political": "Я не от имени какой-либо партии или политической организации в сети.",
|
||||
"submit": "Создать учетную запись",
|
||||
"success": "Письмо со ссылкой для завершения регистрации было отправлено на <b> {email} <\/b>",
|
||||
"terms-and-condition": "Принимаю <a href=\"\/terms-and-conditions\"><ds-text bold color=\"primary\" >Условия и положения<\/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": "Вы уверены, что хотите удалить комментарий \"<b>{name}<\/b>\"?",
|
||||
"success": "Комментарий успешно удален!",
|
||||
"title": "Удалить комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"message": "Вы уверены, что хотите удалить пост \"<b>{name}<\/b>\"?",
|
||||
"success": "Пост успешно удален!",
|
||||
"title": "Удалить пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Удалить"
|
||||
},
|
||||
"disable": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"message": "Вы действительно хотите отключить комментарий от «<b>{name}<\/b>»?",
|
||||
"title": "Отключить комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"message": "Вы действительно хотите отключить пост «<b>{name}<\/b>»?",
|
||||
"title": "Отключить пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Отключить",
|
||||
"success": "Успешно отключен",
|
||||
"user": {
|
||||
"message": "Вы действительно хотите отключить пользователя «<b>{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": "Вы действительно хотите, чтобы комментарий \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить комментарий"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы действительно хотите, чтобы комментарий \"<b>{name}<\/b>\" остановиться и <b>включен<\/b>?",
|
||||
"title": "Окончательно включить комментарий"
|
||||
}
|
||||
},
|
||||
"Post": {
|
||||
"disable": {
|
||||
"message": "Вы действительно хотите, чтобы пост \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить пост"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы действительно хотите, чтобы пост \"<b>{name}<\/b>\" остановиться и <b>включен<\/b>?",
|
||||
"title": "Окончательно включить пост"
|
||||
}
|
||||
},
|
||||
"submit": "Подтвердить решение",
|
||||
"User": {
|
||||
"disable": {
|
||||
"message": "Вы действительно хотите, чтобы пользователь \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить пользователя"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы уверены, что хотите поделиться пользователем \"<b>{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": "Вы уверены, что хотите показать комментарий \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы уверены, что хотите показать пост \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Показать",
|
||||
"success": "Успешно показан!",
|
||||
"user": {
|
||||
"error": "Вы уже сообщили о пользователе!",
|
||||
"message": "Вы уверены, что хотите показать пользователя \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать пользователя",
|
||||
"type": "Пользователь"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы действительно хотите сообщить о посте \"<b> {name} <\/b>\"?",
|
||||
"title": "Пожаловаться на комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы действительно хотите сообщить о посте \"<b>{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": "Вы действительно хотите сообщить о пользователе \"<b>{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>НЕ СМОЖЕТЕ<\/b> восстановить свой аккаунт, посты или комментарии после удаления.",
|
||||
"commentedCount": "Удалить мои комментарии: {count}",
|
||||
"contributionsCount": "Удалить мои посты: {count}",
|
||||
"name": "Удалить данные",
|
||||
"pleaseConfirm": "<b class='is-danger'>Разрушительное действие!<\/b> Введите <b>{confirm}<\/b> для подтверждения.",
|
||||
"success": "Аккаунт успешно удален!"
|
||||
},
|
||||
"download": {
|
||||
"name": "Скачать данные"
|
||||
},
|
||||
"email": {
|
||||
"change-successful": "Адрес электронной почты был успешно изменен.",
|
||||
"labelEmail": "Адрес электронной почты",
|
||||
"labelNewEmail": "Новый адрес электронной почты",
|
||||
"labelNonce": "Введите свой код",
|
||||
"name": "Электронная почта",
|
||||
"submitted": "Электронное письмо с подтверждением отправлено на <b>{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": "<a href=\"https:\/\/human-connection.org\/events\/\" target=\"_blank\" > https:\/\/human-connection.org\/events\/ <\/a>",
|
||||
"title": "Кроме того, мы регулярно проводим мероприятия, где вы также можете\\nподелиться своими впечатлениями и задать вопросы. Информацию о текущих событиях можно найти здесь:"
|
||||
},
|
||||
"agree": "Я согласен(на)!",
|
||||
"code-of-conduct": {
|
||||
"description": "Наш кодекс поведения служит руководством для личного поведения и взаимодействия друг с другом. Каждый пользователь социальной сети Human Connection, который пишет статьи, комментирует или вступает в контакт с другими пользователями, даже за пределами сети, признает эти правила поведения обязательными. <a href=\"https:\/\/alpha.human-connection.org\/code-of-conduct\" target=\"_blank\"> https:\/\/alpha.human-connection.org\/code-of-conduct<\/a>",
|
||||
"title": "Кодекс поведения"
|
||||
},
|
||||
"errors-and-feedback": {
|
||||
"description": "Мы прилагаем все усилия для обеспечения безопасности и доступности нашей сети и данных. Каждый новый выпуск программного обеспечения проходит как автоматическое, так и ручное тестирование. Однако могут возникнуть непредвиденные ошибки. Поэтому мы благодарны за любые обнаруженные ошибки. Вы можете сообщить о любых обнаруженных ошибках, отправив электронное письмо в службу поддержки по адресу support@human-connection.org",
|
||||
"title": "Ошибки и обратная связь"
|
||||
},
|
||||
"help-and-questions": {
|
||||
"description": "Для справки и вопросов мы собрали для вас исчерпывающую подборку часто задаваемых вопросов и ответов (FAQ). Вы можете найти их здесь: <a href=\"https:\/\/support.human-connection.org\/kb\/\" target=\"_blank\" > https:\/\/support.human-connection.org\/kb\/ <\/a>",
|
||||
"title": "Помощь и вопросы"
|
||||
},
|
||||
"moderation": {
|
||||
"description": "Пока наши финансовые возможности не позволяют нам реализовать полноценную систему модерации, поэтому мы осуществляем упрощенную модерацию собственными силами и с помощью волонтёров. Мы специально обучаем этих модераторов, поэтому только они принимают соответствующие решения. Модераторы действуют анонимно. Вы можете сообщать нам о постах, комментариях и пользователях (например, если они предоставляют информацию в своем профиле или имеют изображения, которые нарушают настоящие Условия использования). При обращении вы можете указать причину и дать краткое пояснение. Мы рассмотрим обращение и применим санкции в случае необходимости, например, путем блокировки постов, комментариев или пользователей. К сожалению, в настоящее время ни вы ни пострадавший пользователь не получите от нас обратной связи, но мы планируем ряд улучшений в этом направлении. Несмотря на это, мы оставляем за собой право на применение санкций по причинам, которые не могут быть или ещё не указаны в нашем Кодексе поведения или настоящих Условиях использования.",
|
||||
"title": "Модерация"
|
||||
},
|
||||
"newTermsAndConditions": "Новые условия и положения",
|
||||
"no-commercial-use": {
|
||||
"description": "Использование Human Connection сети не допускается в коммерческих целях. Это включает, но не ограничивается рекламой продуктов с коммерческими целями, размещением партнерских ссылок, прямым привлечением пожертвований или предоставлением финансовой поддержки для целей, которые не признаются благотворительными для целей налогообложения.",
|
||||
"title": "Нет коммерческого использования"
|
||||
},
|
||||
"privacy-statement": {
|
||||
"description": "Наша сеть — это социальная сеть знаний и действий. Поэтому для нас особенно важно, чтобы как можно больше контента было общедоступным. В процессе развития нашей сети будет добавлено больше возможностей для управления видимостью личных данных. Об этих новых функциях мы сообщим дополнительно. В противном случае вы должны думать о том, какие личные данные вы раскрываете о себе (или других). Это особенно актуально для содержания постов и комментариев, поскольку они имеют в основном общедоступный характер. Позже появятся возможности ограничения видимости вашего профиля. Часть условий использования — это наша политика конфиденциальности, которая информирует вас об обработке персональных данных в нашей сети: <a href=\"https:\/\/human-connection.org\/datenschutz\/#netzwerk\" target=\"_blank\">https:\/\/human-connection.org\/datenschutz\/#netzwerk<\/a> или <a href=\"https:\/\/human-connection.org\/datenschutz\/\" target=\"_blank\">https:\/\/human-connection.org\/datenschutz<\/a>. Наше заявление о конфиденциальности корректируется в соответствии с законодательством и характеристиками нашей сети и является действительной в настоящей версии.",
|
||||
"title": "Заявление о конфиденциальности"
|
||||
},
|
||||
"terms-of-service": {
|
||||
"description": "Следующие условия использования являются основой для использования нашей сети. При регистрации вы должны принять их, а мы при необходимости сообщим вам об изменениях. Сеть Human Connection работает в Германии и поэтому регулируется немецким законодательством. Место юрисдикции - Kirchheim \/ Teck. Подробности в выходных данных: <a href=\"https:\/\/human-connection.org\/en\/imprint\" target=\"_blank\" >https:\/\/human-connection.org\/en\/imprint<\/a>.",
|
||||
"title": "Условия обслуживания"
|
||||
},
|
||||
"termsAndConditionsConfirmed": "Я прочитал(а) и подтверждаю <a href=\"\/terms-and-conditions\" target=\"_blank\">Условия и положения<\/a>.",
|
||||
"termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
|
||||
"termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!",
|
||||
"use-and-license": {
|
||||
"description": "Если размещаемый в сети контент защищен правами на интеллектуальную собственность, вы предоставляете нам неисключительную, передаваемую, сублицензируемую и всемирную лицензию на использование этого контента для публикации в нашей сети. Эта лицензия заканчивается, как только вы удаляете свой контент или учетную запись. Помните, что другие пользователи могут продолжать делиться вашим контентом, и мы не можем его удалить.",
|
||||
"title": "Использование и лицензия"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Успешная загрузка!"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
package.json
10
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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||
>
|
||||
<base-icon :name="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
</div>
|
||||
@ -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()
|
||||
},
|
||||
|
||||
@ -38,23 +38,21 @@
|
||||
</ds-chip>
|
||||
<ds-chip v-else size="base">{{ form.title.length }}/{{ formSchema.title.max }}</ds-chip>
|
||||
</ds-text>
|
||||
<client-only>
|
||||
<hc-editor
|
||||
:users="users"
|
||||
:value="form.content"
|
||||
:hashtags="hashtags"
|
||||
@input="updateEditorContent"
|
||||
/>
|
||||
<ds-text align="right">
|
||||
<ds-chip v-if="errors && errors.content" color="danger" size="base">
|
||||
{{ contentLength }}
|
||||
<ds-icon name="warning"></ds-icon>
|
||||
</ds-chip>
|
||||
<ds-chip v-else size="base">
|
||||
{{ contentLength }}
|
||||
</ds-chip>
|
||||
</ds-text>
|
||||
</client-only>
|
||||
<hc-editor
|
||||
:users="users"
|
||||
:value="form.content"
|
||||
:hashtags="hashtags"
|
||||
@input="updateEditorContent"
|
||||
/>
|
||||
<ds-text align="right">
|
||||
<ds-chip v-if="errors && errors.content" color="danger" size="base">
|
||||
{{ contentLength }}
|
||||
<ds-icon name="warning"></ds-icon>
|
||||
</ds-chip>
|
||||
<ds-chip v-else size="base">
|
||||
{{ contentLength }}
|
||||
</ds-chip>
|
||||
</ds-text>
|
||||
<ds-space margin-bottom="small" />
|
||||
<hc-categories-select model="categoryIds" :existingCategoryIds="form.categoryIds" />
|
||||
<ds-text align="right">
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { allowEmbedIframesMutation } from '~/graphql/User.js'
|
||||
import { updateUserMutation } from '~/graphql/User.js'
|
||||
|
||||
export default {
|
||||
name: 'embed-component',
|
||||
@ -129,7 +129,7 @@ export default {
|
||||
async updateEmbedSettings(allowEmbedIframes) {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: allowEmbedIframesMutation(),
|
||||
mutation: updateUserMutation(),
|
||||
variables: {
|
||||
id: this.currentUser.id,
|
||||
allowEmbedIframes,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { config, shallowMount } from '@vue/test-utils'
|
||||
import MasonryGridItem from './MasonryGridItem'
|
||||
|
||||
const localVue = global.localVue
|
||||
@ -8,41 +8,24 @@ config.stubs['ds-grid-item'] = '<span><slot /></span>'
|
||||
describe('MasonryGridItem', () => {
|
||||
let wrapper
|
||||
|
||||
describe('given an imageAspectRatio', () => {
|
||||
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
|
||||
const propsData = { imageAspectRatio: 2 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(13)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => {
|
||||
const propsData = { imageAspectRatio: 1.1 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(15)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.7 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(18)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.3 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(25)
|
||||
})
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(MasonryGridItem, { localVue })
|
||||
wrapper.vm.$parent.$emit = jest.fn()
|
||||
})
|
||||
|
||||
describe('given no aspect ratio', () => {
|
||||
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
|
||||
wrapper = mount(MasonryGridItem, { localVue })
|
||||
it('emits "calculating-item-height" when starting calculation', async () => {
|
||||
wrapper.vm.calculateItemHeight()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(8)
|
||||
})
|
||||
const firstCallArgument = wrapper.vm.$parent.$emit.mock.calls[0][0]
|
||||
expect(firstCallArgument).toBe('calculating-item-height')
|
||||
})
|
||||
|
||||
it('emits "finished-calculating-item-height" after the calculation', async () => {
|
||||
wrapper.vm.calculateItemHeight()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const secondCallArgument = wrapper.vm.$parent.$emit.mock.calls[1][0]
|
||||
expect(secondCallArgument).toBe('finished-calculating-item-height')
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,17 +5,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const landscapeRatio = 1.3
|
||||
const squareRatio = 1
|
||||
const portraitRatio = 0.7
|
||||
|
||||
const getRowSpan = aspectRatio => {
|
||||
if (aspectRatio >= landscapeRatio) return 13
|
||||
else if (aspectRatio >= squareRatio) return 15
|
||||
else if (aspectRatio >= portraitRatio) return 18
|
||||
else return 25
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
imageAspectRatio: {
|
||||
@ -25,7 +14,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 8,
|
||||
rowSpan: 10,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -45,7 +34,13 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.calculateItemHeight()
|
||||
const image = this.$el.querySelector('img')
|
||||
if (image) {
|
||||
image.onload = () => this.calculateItemHeight()
|
||||
} else {
|
||||
// use timeout to make sure layout is set up before calculation
|
||||
setTimeout(() => this.calculateItemHeight(), 0)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -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`
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
.ds-card-image img {
|
||||
width: 100%;
|
||||
max-height: 2000px;
|
||||
|
||||
@ -66,6 +66,8 @@ describe('CreateUserAccount', () => {
|
||||
wrapper.find('input#checkbox0').setChecked()
|
||||
wrapper.find('input#checkbox1').setChecked()
|
||||
wrapper.find('input#checkbox2').setChecked()
|
||||
wrapper.find('input#checkbox3').setChecked()
|
||||
wrapper.find('input#checkbox4').setChecked()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await wrapper.html()
|
||||
}
|
||||
|
||||
@ -88,12 +88,33 @@
|
||||
v-html="$t('components.registration.signup.form.minimum-age')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox3" type="checkbox" v-model="noCommercial" :checked="noCommercial" />
|
||||
<label
|
||||
for="checkbox3"
|
||||
v-html="$t('components.registration.signup.form.no-commercial')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox4" type="checkbox" v-model="noPolitical" :checked="noPolitical" />
|
||||
<label
|
||||
for="checkbox4"
|
||||
v-html="$t('components.registration.signup.form.no-political')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-button
|
||||
style="float: right;"
|
||||
icon="check"
|
||||
type="submit"
|
||||
:loading="$apollo.loading"
|
||||
:disabled="errors || !termsAndConditionsConfirmed || !dataPrivacy || !minimumAge"
|
||||
:disabled="
|
||||
errors ||
|
||||
!termsAndConditionsConfirmed ||
|
||||
!dataPrivacy ||
|
||||
!minimumAge ||
|
||||
!noCommercial ||
|
||||
!noPolitical
|
||||
"
|
||||
primary
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
@ -145,6 +166,8 @@ export default {
|
||||
termsAndConditionsConfirmed: false,
|
||||
dataPrivacy: false,
|
||||
minimumAge: false,
|
||||
noCommercial: false,
|
||||
noPolitical: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import vueDropzone from 'nuxt-dropzone'
|
||||
import gql from 'graphql-tag'
|
||||
import { updateUserMutation } from '~/graphql/User.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -62,14 +62,7 @@ export default {
|
||||
const avatarUpload = file[0]
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!, $avatarUpload: Upload) {
|
||||
UpdateUser(id: $id, avatarUpload: $avatarUpload) {
|
||||
id
|
||||
avatar
|
||||
}
|
||||
}
|
||||
`,
|
||||
mutation: updateUserMutation(),
|
||||
variables: {
|
||||
avatarUpload,
|
||||
id: this.user.id,
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<dropdown v-else :class="{ 'disabled-content': user.disabled }" placement="top-start" offset="0">
|
||||
<template slot="default" slot-scope="{ openMenu, closeMenu, isOpen }">
|
||||
<nuxt-link :to="userLink" :class="['user', isOpen && 'active']">
|
||||
<div @mouseover="openInfoMenu" @mouseleave="closeMenu(true)">
|
||||
<div @mouseover="showPopover ? openMenu(true) : () => {}" @mouseleave="closeMenu(true)">
|
||||
<hc-avatar v-if="showAvatar" class="avatar" :user="user" />
|
||||
<div>
|
||||
<ds-text class="userinfo">
|
||||
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="popover" v-if="showCounts">
|
||||
<template slot="popover" v-if="showPopover">
|
||||
<div style="min-width: 250px">
|
||||
<hc-badges v-if="user.badges && user.badges.length" :badges="user.badges" />
|
||||
<ds-text
|
||||
@ -106,7 +106,7 @@ export default {
|
||||
showAvatar: { type: Boolean, default: true },
|
||||
trunc: { type: Number, default: 18 }, // "-1" is no trunc
|
||||
dateTime: { type: [Date, String], default: null },
|
||||
showCounts: { type: Boolean, default: true },
|
||||
showPopover: { type: Boolean, default: true },
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@ -143,9 +143,6 @@ export default {
|
||||
this.user.followedByCount = followedByCount
|
||||
this.user.followedByCurrentUser = followedByCurrentUser
|
||||
},
|
||||
openInfoMenu() {
|
||||
if (this.showCounts) this.openMenu(true)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<hc-user
|
||||
:user="scope.row.submitter"
|
||||
:showAvatar="false"
|
||||
:showPopover="false"
|
||||
:trunc="30"
|
||||
data-test="filing-user"
|
||||
/>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<!-- Content Column -->
|
||||
<td class="ds-table-col" data-test="report-content">
|
||||
<client-only v-if="isUser">
|
||||
<hc-user :user="report.resource" :showAvatar="false" :trunc="30" :showCounts="false" />
|
||||
<hc-user :user="report.resource" :showAvatar="false" :trunc="30" :showPopover="false" />
|
||||
</client-only>
|
||||
<nuxt-link v-else class="title" :to="linkTarget">
|
||||
{{ linkText | truncate(50) }}
|
||||
@ -33,7 +33,7 @@
|
||||
:user="report.resource.author"
|
||||
:showAvatar="false"
|
||||
:trunc="30"
|
||||
:showCounts="false"
|
||||
:showPopover="false"
|
||||
/>
|
||||
</client-only>
|
||||
<span v-else>—</span>
|
||||
@ -51,7 +51,7 @@
|
||||
:showAvatar="false"
|
||||
:trunc="30"
|
||||
:date-time="report.updatedAt"
|
||||
:showCounts="false"
|
||||
:showPopover="false"
|
||||
/>
|
||||
</client-only>
|
||||
</td>
|
||||
|
||||
17
webapp/components/utils/UpdateQuery.js
Normal file
17
webapp/components/utils/UpdateQuery.js
Normal file
@ -0,0 +1,17 @@
|
||||
import unionBy from 'lodash/unionBy'
|
||||
|
||||
export default function UpdateQuery(component, { $state, pageKey }) {
|
||||
if (!pageKey) throw new Error('No key given for the graphql query { data } object')
|
||||
return (previousResult, { fetchMoreResult }) => {
|
||||
const oldData = (previousResult && previousResult[pageKey]) || []
|
||||
const newData = (fetchMoreResult && fetchMoreResult[pageKey]) || []
|
||||
if (newData.length < component.pageSize) {
|
||||
component.hasMore = false
|
||||
$state.complete()
|
||||
}
|
||||
const result = {}
|
||||
result[pageKey] = unionBy(oldData, newData, item => item.id)
|
||||
$state.loaded()
|
||||
return result
|
||||
}
|
||||
}
|
||||
86
webapp/components/utils/UpdateQuery.spec.js
Normal file
86
webapp/components/utils/UpdateQuery.spec.js
Normal file
@ -0,0 +1,86 @@
|
||||
import UpdateQuery from './UpdateQuery'
|
||||
|
||||
let $state
|
||||
let component
|
||||
let pageKey
|
||||
let updateQuery
|
||||
let previousResult
|
||||
let fetchMoreResult
|
||||
|
||||
beforeEach(() => {
|
||||
component = {
|
||||
hasMore: true,
|
||||
pageSize: 1,
|
||||
}
|
||||
|
||||
$state = {
|
||||
complete: jest.fn(),
|
||||
loaded: jest.fn(),
|
||||
}
|
||||
previousResult = { Post: [{ id: 1, foo: 'bar' }] }
|
||||
fetchMoreResult = { Post: [{ id: 2, foo: 'baz' }] }
|
||||
updateQuery = () => UpdateQuery(component, { $state, pageKey })
|
||||
})
|
||||
|
||||
describe('UpdateQuery', () => {
|
||||
it('throws error because no key is given', () => {
|
||||
expect(() => {
|
||||
updateQuery()({ Post: [] }, { fetchMoreResult: { Post: [] } })
|
||||
}).toThrow(/No key given/)
|
||||
})
|
||||
|
||||
describe('with a page key', () => {
|
||||
beforeEach(() => (pageKey = 'Post'))
|
||||
|
||||
describe('given two arrays of things', () => {
|
||||
it('merges the arrays', () => {
|
||||
expect(updateQuery()(previousResult, { fetchMoreResult })).toEqual({
|
||||
Post: [
|
||||
{ id: 1, foo: 'bar' },
|
||||
{ id: 2, foo: 'baz' },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not create duplicates', () => {
|
||||
fetchMoreResult = { Post: [{ id: 1, foo: 'baz' }] }
|
||||
expect(updateQuery()(previousResult, { fetchMoreResult })).toEqual({
|
||||
Post: [{ id: 1, foo: 'bar' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not call $state.complete()', () => {
|
||||
expect(updateQuery()(previousResult, { fetchMoreResult }))
|
||||
expect($state.complete).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('in case of fewer records than pageSize', () => {
|
||||
beforeEach(() => (component.pageSize = 10))
|
||||
it('calls $state.complete()', () => {
|
||||
expect(updateQuery()(previousResult, { fetchMoreResult }))
|
||||
expect($state.complete).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('changes component.hasMore to `false`', () => {
|
||||
expect(component.hasMore).toBe(true)
|
||||
expect(updateQuery()(previousResult, { fetchMoreResult }))
|
||||
expect(component.hasMore).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given one array is undefined', () => {
|
||||
describe('does not crash', () => {
|
||||
it('neither if the previous data was undefined', () => {
|
||||
expect(updateQuery()(undefined, { fetchMoreResult })).toEqual({
|
||||
Post: [{ id: 2, foo: 'baz' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('not if the new data is undefined', () => {
|
||||
expect(updateQuery()(previousResult, {})).toEqual({ Post: [{ id: 1, foo: 'bar' }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,3 +1,3 @@
|
||||
export const COMMENT_MIN_LENGTH = 1
|
||||
export const COMMENT_MAX_UNTRUNCATED_LENGTH = 300
|
||||
export const COMMENT_MAX_UNTRUNCATED_LENGTH = 1200
|
||||
export const COMMENT_TRUNCATE_TO_LENGTH = 180
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const userFragment = lang => gql`
|
||||
export const userFragment = gql`
|
||||
fragment user on User {
|
||||
id
|
||||
slug
|
||||
@ -8,11 +8,10 @@ export const userFragment = lang => gql`
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
shoutedCount
|
||||
contributionsCount
|
||||
commentedCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
}
|
||||
`
|
||||
export const locationAndBadgesFragment = lang => gql`
|
||||
fragment locationAndBadges on User {
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
@ -23,17 +22,17 @@ export const userFragment = lang => gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const postCountsFragment = gql`
|
||||
fragment postCounts on Post {
|
||||
commentsCount
|
||||
export const userCountsFragment = gql`
|
||||
fragment userCounts on User {
|
||||
shoutedCount
|
||||
shoutedByCurrentUser
|
||||
emotionsCount
|
||||
contributionsCount
|
||||
commentedCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
}
|
||||
`
|
||||
export const postFragment = lang => gql`
|
||||
${userFragment(lang)}
|
||||
|
||||
export const postFragment = gql`
|
||||
fragment post on Post {
|
||||
id
|
||||
title
|
||||
@ -46,9 +45,22 @@ export const postFragment = lang => gql`
|
||||
slug
|
||||
image
|
||||
language
|
||||
author {
|
||||
...user
|
||||
}
|
||||
pinnedAt
|
||||
imageAspectRatio
|
||||
}
|
||||
`
|
||||
|
||||
export const postCountsFragment = gql`
|
||||
fragment postCounts on Post {
|
||||
commentsCount
|
||||
shoutedCount
|
||||
shoutedByCurrentUser
|
||||
emotionsCount
|
||||
}
|
||||
`
|
||||
|
||||
export const tagsCategoriesAndPinnedFragment = gql`
|
||||
fragment tagsCategoriesAndPinned on Post {
|
||||
tags {
|
||||
id
|
||||
}
|
||||
@ -63,13 +75,10 @@ export const postFragment = lang => gql`
|
||||
name
|
||||
role
|
||||
}
|
||||
pinnedAt
|
||||
imageAspectRatio
|
||||
}
|
||||
`
|
||||
export const commentFragment = lang => gql`
|
||||
${userFragment(lang)}
|
||||
|
||||
export const commentFragment = gql`
|
||||
fragment comment on Comment {
|
||||
id
|
||||
createdAt
|
||||
@ -78,8 +87,5 @@ export const commentFragment = lang => gql`
|
||||
deleted
|
||||
content
|
||||
contentExcerpt
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,19 +1,42 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { postFragment, commentFragment, postCountsFragment } from './Fragments'
|
||||
import {
|
||||
userFragment,
|
||||
postFragment,
|
||||
commentFragment,
|
||||
postCountsFragment,
|
||||
userCountsFragment,
|
||||
locationAndBadgesFragment,
|
||||
tagsCategoriesAndPinnedFragment,
|
||||
} from './Fragments'
|
||||
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
${locationAndBadgesFragment(lang)}
|
||||
${postFragment}
|
||||
${postCountsFragment}
|
||||
${commentFragment(lang)}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
${commentFragment}
|
||||
|
||||
query Post($id: ID!) {
|
||||
Post(id: $id) {
|
||||
...post
|
||||
...postCounts
|
||||
...tagsCategoriesAndPinned
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
comments(orderBy: createdAt_asc) {
|
||||
...comment
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,13 +46,23 @@ export default i18n => {
|
||||
export const filterPosts = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
${locationAndBadgesFragment(lang)}
|
||||
${postFragment}
|
||||
${postCountsFragment}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
|
||||
query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) {
|
||||
Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
||||
...post
|
||||
...postCounts
|
||||
...tagsCategoriesAndPinned
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -38,8 +71,12 @@ export const filterPosts = i18n => {
|
||||
export const profilePagePosts = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
${locationAndBadgesFragment(lang)}
|
||||
${postFragment}
|
||||
${postCountsFragment}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
|
||||
query profilePagePosts(
|
||||
$filter: _PostFilter
|
||||
@ -50,6 +87,12 @@ export const profilePagePosts = i18n => {
|
||||
profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
||||
...post
|
||||
...postCounts
|
||||
...tagsCategoriesAndPinned
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -66,16 +109,32 @@ export const PostsEmotionsByCurrentUser = () => {
|
||||
export const relatedContributions = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
${locationAndBadgesFragment(lang)}
|
||||
${postFragment}
|
||||
${postCountsFragment}
|
||||
${tagsCategoriesAndPinnedFragment}
|
||||
|
||||
query Post($slug: String!) {
|
||||
Post(slug: $slug) {
|
||||
...post
|
||||
...postCounts
|
||||
...tagsCategoriesAndPinned
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
relatedContributions(first: 2) {
|
||||
...post
|
||||
...postCounts
|
||||
...tagsCategoriesAndPinned
|
||||
author {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +1,38 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { userFragment, postFragment, commentFragment } from './Fragments'
|
||||
import {
|
||||
userCountsFragment,
|
||||
locationAndBadgesFragment,
|
||||
userFragment,
|
||||
postFragment,
|
||||
commentFragment,
|
||||
} from './Fragments'
|
||||
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${userFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
${locationAndBadgesFragment(lang)}
|
||||
|
||||
query User($id: ID!) {
|
||||
User(id: $id) {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
about
|
||||
locationName
|
||||
createdAt
|
||||
badgesCount
|
||||
followingCount
|
||||
following(first: 7) {
|
||||
...user
|
||||
}
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
isBlocked
|
||||
following(first: 7) {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
followedBy(first: 7) {
|
||||
...user
|
||||
...userCounts
|
||||
...locationAndBadges
|
||||
}
|
||||
socialMedia {
|
||||
id
|
||||
@ -47,10 +58,10 @@ export const minimisedUserQuery = () => {
|
||||
}
|
||||
|
||||
export const notificationQuery = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${commentFragment(lang)}
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${commentFragment}
|
||||
${postFragment}
|
||||
|
||||
query($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) {
|
||||
notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) {
|
||||
@ -62,11 +73,20 @@ export const notificationQuery = i18n => {
|
||||
__typename
|
||||
... on Post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
... on Comment {
|
||||
...comment
|
||||
author {
|
||||
...user
|
||||
}
|
||||
post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,10 +96,10 @@ export const notificationQuery = i18n => {
|
||||
}
|
||||
|
||||
export const markAsReadMutation = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${commentFragment(lang)}
|
||||
${postFragment(lang)}
|
||||
${userFragment}
|
||||
${commentFragment}
|
||||
${postFragment}
|
||||
|
||||
mutation($id: ID!) {
|
||||
markAsRead(id: $id) {
|
||||
@ -91,11 +111,17 @@ export const markAsReadMutation = i18n => {
|
||||
__typename
|
||||
... on Post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
... on Comment {
|
||||
...comment
|
||||
post {
|
||||
...post
|
||||
author {
|
||||
...user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,16 +131,19 @@ export const markAsReadMutation = i18n => {
|
||||
}
|
||||
|
||||
export const followUserMutation = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${userFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
|
||||
mutation($id: ID!) {
|
||||
followUser(id: $id) {
|
||||
name
|
||||
...user
|
||||
...userCounts
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
followedBy(first: 7) {
|
||||
...user
|
||||
...userCounts
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,39 +151,59 @@ export const followUserMutation = i18n => {
|
||||
}
|
||||
|
||||
export const unfollowUserMutation = i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql`
|
||||
${userFragment(lang)}
|
||||
${userFragment}
|
||||
${userCountsFragment}
|
||||
|
||||
mutation($id: ID!) {
|
||||
unfollowUser(id: $id) {
|
||||
name
|
||||
...user
|
||||
...userCounts
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
followedBy(first: 7) {
|
||||
...user
|
||||
...userCounts
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const allowEmbedIframesMutation = () => {
|
||||
export const updateUserMutation = () => {
|
||||
return gql`
|
||||
mutation($id: ID!, $allowEmbedIframes: Boolean) {
|
||||
UpdateUser(id: $id, allowEmbedIframes: $allowEmbedIframes) {
|
||||
mutation(
|
||||
$id: ID!
|
||||
$slug: String
|
||||
$name: String
|
||||
$locationName: String
|
||||
$about: String
|
||||
$allowEmbedIframes: Boolean
|
||||
$showShoutsPublicly: Boolean
|
||||
$termsAndConditionsAgreedVersion: String
|
||||
$avatarUpload: Upload
|
||||
) {
|
||||
UpdateUser(
|
||||
id: $id
|
||||
slug: $slug
|
||||
name: $name
|
||||
locationName: $locationName
|
||||
about: $about
|
||||
allowEmbedIframes: $allowEmbedIframes
|
||||
showShoutsPublicly: $showShoutsPublicly
|
||||
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||
avatarUpload: $avatarUpload
|
||||
) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
locationName
|
||||
about
|
||||
allowEmbedIframes
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const showShoutsPubliclyMutation = () => {
|
||||
return gql`
|
||||
mutation($id: ID!, $showShoutsPublicly: Boolean) {
|
||||
UpdateUser(id: $id, showShoutsPublicly: $showShoutsPublicly) {
|
||||
id
|
||||
showShoutsPublicly
|
||||
locale
|
||||
termsAndConditionsAgreedVersion
|
||||
avatar
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -167,14 +216,3 @@ export const checkSlugAvailableQuery = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const localeMutation = () => {
|
||||
return gql`
|
||||
mutation($id: ID!, $locale: String) {
|
||||
UpdateUser(id: $id, locale: $locale) {
|
||||
id
|
||||
locale
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@
|
||||
}
|
||||
},
|
||||
"deleteUserAccount": {
|
||||
"name": "Daten löschen",
|
||||
"name": "Benutzerkonto löschen",
|
||||
"contributionsCount": "Meine {count} Beiträge löschen",
|
||||
"commentedCount": "Meine {count} Kommentare löschen",
|
||||
"accountDescription": "Sei dir bewusst, dass deine Beiträge und Kommentare für unsere Community wichtig sind. Wenn du sie trotzdem löschen möchtest, musst du sie unten markieren.",
|
||||
@ -550,6 +550,8 @@
|
||||
"terms-and-condition": "Ich stimme den <a href=\"\/terms-and-conditions\"><ds-text bold color=\"primary\" > Nutzungsbedingungen<\/ds-text><\/a>zu.",
|
||||
"data-privacy": "Ich habe die <a href=\"https:\/\/human-connection.org\/datenschutz\/\" target=\"_blank\"><ds-text bold color=\"primary\" >Datenschutzerklärung<\/ds-text><\/a> gelesen und verstanden",
|
||||
"minimum-age": "Ich bin 18 Jahre oder älter.",
|
||||
"no-commercial": "Ich habe keine kommerziellen Absichten und ich repräsentiere kein kommerzielles Unternehmen oder Organisation.",
|
||||
"no-political": "Ich bin nicht im Auftrag einer Partei oder politischen Organisation im Netzwerk. ",
|
||||
"invitation-code": "Dein Einladungscode lautet: <b>{code}<\/b>",
|
||||
"errors": {
|
||||
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
"terms-and-condition": "I confirm to the <a href=\"/terms-and-conditions\"><ds-text bold color=\"primary\" > Terms and conditions</ds-text></a>.",
|
||||
"data-privacy": " I have read and understood the <a href=\"https://human-connection.org/datenschutz/\" target=\"_blank\"><ds-text bold color=\"primary\" >Privacy Statement</ds-text></a> ",
|
||||
"minimum-age": "I'm 18 years or older.",
|
||||
"no-commercial": "I have no commercial interests and I am not representing a company or any other commercial organisation on the network.",
|
||||
"no-political": "I am not on behalf of a party or political organization in the network.",
|
||||
"invitation-code": "Your invitation code is: <b>{code}</b>",
|
||||
"errors": {
|
||||
"email-exists": "There is already a user account with this e-mail address!",
|
||||
@ -269,10 +271,10 @@
|
||||
"name": "Download Data"
|
||||
},
|
||||
"deleteUserAccount": {
|
||||
"name": "Delete data",
|
||||
"name": "Delete user account",
|
||||
"contributionsCount": "Delete my {count} posts",
|
||||
"commentedCount": "Delete my {count} comments",
|
||||
"accountDescription": "Be aware that your Post and Comments are important to our community. If you still choose to delete them, you have to mark them below.",
|
||||
"accountDescription": "Be aware that your Posts and Comments are important to our community. If you still choose to delete them, you have to mark them below.",
|
||||
"accountWarning": "You <b>CAN'T MANAGE</b> and <b>CAN'T RECOVER</b> your Account, Posts, or Comments after deleting your account!",
|
||||
"success": "Account successfully deleted!",
|
||||
"pleaseConfirm": "<b class='is-danger'>Destructive action!</b> Type <b>{confirm}</b> to confirm"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user