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
|
- yarn global add wait-on
|
||||||
# Install Codecov
|
# Install Codecov
|
||||||
- yarn install
|
- yarn install
|
||||||
- cp cypress.env.template.json cypress.env.json
|
- cp backend/.env.template backend/.env
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- docker-compose -f docker-compose.yml build --parallel
|
- docker-compose -f docker-compose.yml build --parallel
|
||||||
@ -63,14 +63,14 @@ before_deploy:
|
|||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: script
|
- provider: script
|
||||||
script: scripts/docker_push.sh
|
script: bash scripts/docker_push.sh
|
||||||
on:
|
on:
|
||||||
branch: master
|
branch: master
|
||||||
- provider: script
|
- provider: script
|
||||||
script: scripts/deploy.sh
|
script: bash scripts/deploy.sh
|
||||||
on:
|
on:
|
||||||
branch: master
|
branch: master
|
||||||
- provider: script
|
- provider: script
|
||||||
script: scripts/github_release.sh
|
script: bash scripts/github_release.sh
|
||||||
on:
|
on:
|
||||||
branch: master
|
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).
|
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)
|
- 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)
|
- Add shoutedBy_some to _PostFilter [`#2353`](https://github.com/Human-Connection/Human-Connection/pull/2353)
|
||||||
- build(deps-dev): bump date-fns from 2.8.0 to 2.8.1 [`#2342`](https://github.com/Human-Connection/Human-Connection/pull/2342)
|
- build(deps-dev): bump 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)
|
- 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)
|
- 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)
|
- 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 #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)
|
- 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)
|
- 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)
|
- 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 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)
|
- 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)
|
- 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)
|
- Extract AvatarMenu into its own component [`994a0b0`](https://github.com/Human-Connection/Human-Connection/commit/994a0b049d1803784d9c06383872f1c9e33095a0)
|
||||||
- Finish portuguese translations [`15c671c`](https://github.com/Human-Connection/Human-Connection/commit/15c671c4a8aae86317896ca30601389504bce9e1)
|
- 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)
|
#### [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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
#### [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.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)
|
- 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): 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)
|
#### [0.1.7](https://github.com/Human-Connection/Human-Connection/compare/0.1.6...0.1.7)
|
||||||
|
|
||||||
|
|||||||
@ -33,9 +33,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/joi": "^16.1.8",
|
"@hapi/joi": "^16.1.8",
|
||||||
"@sentry/node": "^5.10.1",
|
"@sentry/node": "^5.10.2",
|
||||||
"apollo-cache-inmemory": "~1.6.3",
|
"apollo-cache-inmemory": "~1.6.5",
|
||||||
"apollo-client": "~2.6.4",
|
"apollo-client": "~2.6.8",
|
||||||
"apollo-link-context": "~1.0.19",
|
"apollo-link-context": "~1.0.19",
|
||||||
"apollo-link-http": "~1.5.16",
|
"apollo-link-http": "~1.5.16",
|
||||||
"apollo-server": "~2.9.13",
|
"apollo-server": "~2.9.13",
|
||||||
@ -63,28 +63,28 @@
|
|||||||
"lodash": "~4.17.14",
|
"lodash": "~4.17.14",
|
||||||
"merge-graphql-schemas": "^1.7.3",
|
"merge-graphql-schemas": "^1.7.3",
|
||||||
"metascraper": "^5.8.9",
|
"metascraper": "^5.8.9",
|
||||||
"metascraper-audio": "^5.8.7",
|
"metascraper-audio": "^5.8.10",
|
||||||
"metascraper-author": "^5.8.7",
|
"metascraper-author": "^5.8.7",
|
||||||
"metascraper-clearbit-logo": "^5.3.0",
|
"metascraper-clearbit-logo": "^5.3.0",
|
||||||
"metascraper-date": "^5.8.7",
|
"metascraper-date": "^5.8.7",
|
||||||
"metascraper-description": "^5.8.7",
|
"metascraper-description": "^5.8.10",
|
||||||
"metascraper-image": "^5.8.7",
|
"metascraper-image": "^5.8.10",
|
||||||
"metascraper-lang": "^5.8.9",
|
"metascraper-lang": "^5.8.10",
|
||||||
"metascraper-lang-detector": "^4.10.2",
|
"metascraper-lang-detector": "^4.10.2",
|
||||||
"metascraper-logo": "^5.8.7",
|
"metascraper-logo": "^5.8.10",
|
||||||
"metascraper-publisher": "^5.8.7",
|
"metascraper-publisher": "^5.8.7",
|
||||||
"metascraper-soundcloud": "^5.8.9",
|
"metascraper-soundcloud": "^5.8.10",
|
||||||
"metascraper-title": "^5.8.7",
|
"metascraper-title": "^5.8.10",
|
||||||
"metascraper-url": "^5.8.7",
|
"metascraper-url": "^5.8.7",
|
||||||
"metascraper-video": "^5.8.9",
|
"metascraper-video": "^5.8.10",
|
||||||
"metascraper-youtube": "^5.8.9",
|
"metascraper-youtube": "^5.8.10",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"mustache": "^3.1.0",
|
"mustache": "^3.1.0",
|
||||||
"neo4j-driver": "~1.7.6",
|
"neo4j-driver": "~1.7.6",
|
||||||
"neo4j-graphql-js": "^2.10.0",
|
"neo4j-graphql-js": "^2.10.2",
|
||||||
"neode": "^0.3.3",
|
"neode": "^0.3.6",
|
||||||
"node-fetch": "~2.6.0",
|
"node-fetch": "~2.6.0",
|
||||||
"nodemailer": "^6.4.1",
|
"nodemailer": "^6.4.2",
|
||||||
"nodemailer-html-to-text": "^3.1.0",
|
"nodemailer-html-to-text": "^3.1.0",
|
||||||
"npm-run-all": "~4.1.5",
|
"npm-run-all": "~4.1.5",
|
||||||
"request": "~2.88.0",
|
"request": "~2.88.0",
|
||||||
@ -97,11 +97,11 @@
|
|||||||
"xregexp": "^4.2.4"
|
"xregexp": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "~7.7.4",
|
"@babel/cli": "~7.7.5",
|
||||||
"@babel/core": "~7.7.5",
|
"@babel/core": "~7.7.5",
|
||||||
"@babel/node": "~7.7.4",
|
"@babel/node": "~7.7.4",
|
||||||
"@babel/plugin-proposal-throw-expressions": "^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",
|
"@babel/register": "~7.7.0",
|
||||||
"apollo-server-testing": "~2.9.13",
|
"apollo-server-testing": "~2.9.13",
|
||||||
"babel-core": "~7.0.0-0",
|
"babel-core": "~7.0.0-0",
|
||||||
@ -112,14 +112,14 @@
|
|||||||
"eslint": "~6.7.2",
|
"eslint": "~6.7.2",
|
||||||
"eslint-config-prettier": "~6.7.0",
|
"eslint-config-prettier": "~6.7.0",
|
||||||
"eslint-config-standard": "~14.1.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-jest": "~23.1.1",
|
||||||
"eslint-plugin-node": "~10.0.0",
|
"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-promise": "~4.2.1",
|
||||||
"eslint-plugin-standard": "~4.0.1",
|
"eslint-plugin-standard": "~4.0.1",
|
||||||
"jest": "~24.9.0",
|
"jest": "~24.9.0",
|
||||||
"nodemon": "~2.0.1",
|
"nodemon": "~2.0.2",
|
||||||
"prettier": "~1.19.1",
|
"prettier": "~1.19.1",
|
||||||
"supertest": "~4.0.2"
|
"supertest": "~4.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import { v1 as neo4j } from 'neo4j-driver'
|
import { v1 as neo4j } from 'neo4j-driver'
|
||||||
import CONFIG from './../config'
|
import CONFIG from './../config'
|
||||||
import setupNeode from './neode'
|
import Neode from 'neode'
|
||||||
|
import models from '../models'
|
||||||
|
|
||||||
let driver
|
let driver
|
||||||
|
const defaultOptions = {
|
||||||
|
uri: CONFIG.NEO4J_URI,
|
||||||
|
username: CONFIG.NEO4J_USERNAME,
|
||||||
|
password: CONFIG.NEO4J_PASSWORD,
|
||||||
|
}
|
||||||
|
|
||||||
export function getDriver(options = {}) {
|
export function getDriver(options = {}) {
|
||||||
const {
|
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||||
uri = CONFIG.NEO4J_URI,
|
|
||||||
username = CONFIG.NEO4J_USERNAME,
|
|
||||||
password = CONFIG.NEO4J_PASSWORD,
|
|
||||||
} = options
|
|
||||||
if (!driver) {
|
if (!driver) {
|
||||||
driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
|
driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
|
||||||
}
|
}
|
||||||
@ -17,10 +19,11 @@ export function getDriver(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let neodeInstance
|
let neodeInstance
|
||||||
export function neode() {
|
export function getNeode(options = {}) {
|
||||||
if (!neodeInstance) {
|
if (!neodeInstance) {
|
||||||
const { NEO4J_URI: uri, NEO4J_USERNAME: username, NEO4J_PASSWORD: password } = CONFIG
|
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||||
neodeInstance = setupNeode({ uri, username, password })
|
neodeInstance = new Neode(uri, username, password).with(models)
|
||||||
|
return neodeInstance
|
||||||
}
|
}
|
||||||
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) {
|
} catch (err) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const query = `
|
const session = driver.session()
|
||||||
|
|
||||||
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const updateUserLastActiveTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (user:User {id: $id, deleted: false, disabled: false })
|
MATCH (user:User {id: $id, deleted: false, disabled: false })
|
||||||
SET user.lastActiveAt = toString(datetime())
|
SET user.lastActiveAt = toString(datetime())
|
||||||
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
|
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`,
|
||||||
const session = driver.session()
|
{ id },
|
||||||
let result
|
)
|
||||||
|
return updateUserLastActiveTransactionResponse.records.map(record => record.get('user'))
|
||||||
try {
|
|
||||||
result = await session.run(query, { id })
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const [currentUser] = await result.records.map(record => {
|
|
||||||
return record.get('user')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [currentUser] = await writeTxResultPromise
|
||||||
if (!currentUser) return null
|
if (!currentUser) return null
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
...currentUser,
|
...currentUser,
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Factory from '../seed/factories/index'
|
import Factory from '../seed/factories/index'
|
||||||
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||||
import decode from './decode'
|
import decode from './decode'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
|
|||||||
@ -2,30 +2,23 @@ import extractHashtags from '../hashtags/extractHashtags'
|
|||||||
|
|
||||||
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||||
if (!hashtags.length) return
|
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()
|
const session = context.driver.session()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await session.run(cypherDeletePreviousRelations, {
|
await session.writeTransaction(txc => {
|
||||||
postId,
|
return txc.run(
|
||||||
})
|
`
|
||||||
await session.run(cypherCreateNewTagsAndRelations, {
|
MATCH (post:Post { id: $postId})
|
||||||
postId,
|
OPTIONAL MATCH (post)-[previousRelations:TAGGED]->(tag:Tag)
|
||||||
hashtags,
|
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 {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let server
|
let server
|
||||||
@ -11,7 +11,7 @@ let hashtagingUser
|
|||||||
let authenticatedUser
|
let authenticatedUser
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
const categoryIds = ['cat9']
|
const categoryIds = ['cat9']
|
||||||
const createPostMutation = gql`
|
const createPostMutation = gql`
|
||||||
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
||||||
@ -36,7 +36,7 @@ beforeAll(() => {
|
|||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
user: authenticatedUser,
|
user: authenticatedUser,
|
||||||
neode: instance,
|
neode,
|
||||||
driver,
|
driver,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -48,14 +48,14 @@ beforeAll(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
hashtagingUser = await instance.create('User', {
|
hashtagingUser = await neode.create('User', {
|
||||||
id: 'you',
|
id: 'you',
|
||||||
name: 'Al Capone',
|
name: 'Al Capone',
|
||||||
slug: 'al-capone',
|
slug: 'al-capone',
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
await instance.create('Category', {
|
await neode.create('Category', {
|
||||||
id: 'cat9',
|
id: 'cat9',
|
||||||
name: 'Democracy & Politics',
|
name: 'Democracy & Politics',
|
||||||
icon: 'university',
|
icon: 'university',
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import sluggify from './sluggifyMiddleware'
|
|||||||
import excerpt from './excerptMiddleware'
|
import excerpt from './excerptMiddleware'
|
||||||
import xss from './xssMiddleware'
|
import xss from './xssMiddleware'
|
||||||
import permissions from './permissionsMiddleware'
|
import permissions from './permissionsMiddleware'
|
||||||
import user from './userMiddleware'
|
import user from './user/userMiddleware'
|
||||||
import includedFields from './includedFieldsMiddleware'
|
import includedFields from './includedFieldsMiddleware'
|
||||||
import orderBy from './orderByMiddleware'
|
import orderBy from './orderByMiddleware'
|
||||||
import validation from './validation/validationMiddleware'
|
import validation from './validation/validationMiddleware'
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const createLocation = async (session, mapboxData) => {
|
|||||||
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
|
lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null,
|
||||||
}
|
}
|
||||||
|
|
||||||
let query =
|
let mutation =
|
||||||
'MERGE (l:Location {id: $id}) ' +
|
'MERGE (l:Location {id: $id}) ' +
|
||||||
'SET l.name = $nameEN, ' +
|
'SET l.name = $nameEN, ' +
|
||||||
'l.nameEN = $nameEN, ' +
|
'l.nameEN = $nameEN, ' +
|
||||||
@ -53,19 +53,23 @@ const createLocation = async (session, mapboxData) => {
|
|||||||
'l.type = $type'
|
'l.type = $type'
|
||||||
|
|
||||||
if (data.lat && data.lng) {
|
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)
|
try {
|
||||||
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(mutation, data)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createOrUpdateLocations = async (userId, locationName, driver) => {
|
const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||||
if (isEmpty(locationName)) {
|
if (isEmpty(locationName)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||||
locationName,
|
locationName,
|
||||||
@ -106,33 +110,44 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
|||||||
if (data.context) {
|
if (data.context) {
|
||||||
await asyncForEach(data.context, async ctx => {
|
await asyncForEach(data.context, async ctx => {
|
||||||
await createLocation(session, ctx)
|
await createLocation(session, ctx)
|
||||||
|
try {
|
||||||
await session.run(
|
await session.writeTransaction(transaction => {
|
||||||
'MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId}) ' +
|
return transaction.run(
|
||||||
'MERGE (child)<-[:IS_IN]-(parent) ' +
|
`
|
||||||
'RETURN child.id, parent.id',
|
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
|
||||||
|
MERGE (child)<-[:IS_IN]-(parent)
|
||||||
|
RETURN child.id, parent.id
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
parentId: parent.id,
|
parentId: parent.id,
|
||||||
childId: ctx.id,
|
childId: ctx.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
parent = ctx
|
parent = ctx
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// delete all current locations from user
|
// delete all current locations from user and add new location
|
||||||
await session.run('MATCH (u:User {id: $userId})-[r:IS_IN]->(l:Location) DETACH DELETE r', {
|
try {
|
||||||
userId: userId,
|
await session.writeTransaction(transaction => {
|
||||||
})
|
return transaction.run(
|
||||||
// connect user with location
|
`
|
||||||
await session.run(
|
MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location)
|
||||||
'MATCH (u:User {id: $userId}), (l:Location {id: $locationId}) MERGE (u)-[:IS_IN]->(l) RETURN l.id, u.id',
|
DETACH DELETE relationship
|
||||||
{
|
WITH user
|
||||||
userId: userId,
|
MATCH (location:Location {id: $locationId})
|
||||||
locationId: data.id,
|
MERGE (user)-[:IS_IN]->(location)
|
||||||
},
|
RETURN location.id, user.id
|
||||||
|
`,
|
||||||
|
{ userId: userId, locationId: data.id },
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default createOrUpdateLocations
|
export default createOrUpdateLocations
|
||||||
|
|||||||
@ -1,66 +1,73 @@
|
|||||||
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
import extractMentionedUsers from './mentions/extractMentionedUsers'
|
||||||
|
import { validateNotifyUsers } from '../validation/validationMiddleware'
|
||||||
|
|
||||||
const postAuthorOfComment = async (comment, { context }) => {
|
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||||
const cypherFindUser = `
|
const idsOfUsers = extractMentionedUsers(args.content)
|
||||||
MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
|
const post = await resolve(root, args, context, resolveInfo)
|
||||||
RETURN user { .id }
|
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()
|
const session = context.driver.session()
|
||||||
let result
|
let postAuthorId
|
||||||
try {
|
try {
|
||||||
result = await session.run(cypherFindUser, {
|
postAuthorId = await session.readTransaction(transaction => {
|
||||||
commentId: comment.id,
|
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 {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
const [postAuthor] = await result.records.map(record => {
|
|
||||||
return record.get('user')
|
|
||||||
})
|
|
||||||
return postAuthor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||||
if (!idsOfUsers.length) return
|
await validateNotifyUsers(label, reason)
|
||||||
|
let mentionedCypher
|
||||||
// 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
|
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case 'mentioned_in_post': {
|
case 'mentioned_in_post': {
|
||||||
cypher = `
|
mentionedCypher = `
|
||||||
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
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
|
break
|
||||||
}
|
}
|
||||||
case 'mentioned_in_comment': {
|
case 'mentioned_in_comment': {
|
||||||
cypher = `
|
mentionedCypher = `
|
||||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
|
`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mentionedCypher += `
|
||||||
SET notification.read = FALSE
|
SET notification.read = FALSE
|
||||||
SET (
|
SET (
|
||||||
CASE
|
CASE
|
||||||
@ -68,97 +75,47 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
THEN notification END ).createdAt = toString(datetime())
|
THEN notification END ).createdAt = toString(datetime())
|
||||||
SET notification.updatedAt = toString(datetime())
|
SET notification.updatedAt = toString(datetime())
|
||||||
`
|
`
|
||||||
break
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(mentionedCypher, { id, idsOfUsers, reason })
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
}
|
}
|
||||||
case 'commented_on_post': {
|
}
|
||||||
cypher = `
|
|
||||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
|
||||||
MATCH (user: User)
|
await validateNotifyUsers(label, reason)
|
||||||
WHERE user.id in $idsOfUsers
|
const session = context.driver.session()
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
|
||||||
AND NOT (author)<-[:BLOCKED]-(user)
|
try {
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
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 notification.read = FALSE
|
||||||
SET (
|
SET (
|
||||||
CASE
|
CASE
|
||||||
WHEN notification.createdAt IS NULL
|
WHEN notification.createdAt IS NULL
|
||||||
THEN notification END ).createdAt = toString(datetime())
|
THEN notification END ).createdAt = toString(datetime())
|
||||||
SET notification.updatedAt = toString(datetime())
|
SET notification.updatedAt = toString(datetime())
|
||||||
`
|
`,
|
||||||
break
|
{ commentId, postAuthorId, reason },
|
||||||
}
|
)
|
||||||
}
|
|
||||||
const session = context.driver.session()
|
|
||||||
try {
|
|
||||||
await session.run(cypher, {
|
|
||||||
id,
|
|
||||||
idsOfUsers,
|
|
||||||
reason,
|
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
const idsOfUsers = extractMentionedUsers(args.content)
|
|
||||||
|
|
||||||
const post = await resolve(root, args, context, resolveInfo)
|
|
||||||
|
|
||||||
if (post) {
|
|
||||||
await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return post
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
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')
|
|
||||||
})
|
|
||||||
if (context.user.id !== postAuthor.id) {
|
|
||||||
await notifyUsers('Comment', comment.id, [postAuthor.id], 'commented_on_post', context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return comment
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreatePost: handleContentDataOfPost,
|
CreatePost: handleContentDataOfPost,
|
||||||
UpdatePost: handleContentDataOfPost,
|
UpdatePost: handleContentDataOfPost,
|
||||||
CreateComment: handleCreateComment,
|
CreateComment: handleContentDataOfComment,
|
||||||
UpdateComment: handleContentDataOfComment,
|
UpdateComment: handleContentDataOfComment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let server
|
let server, query, mutate, notifiedUser, authenticatedUser
|
||||||
let query
|
|
||||||
let mutate
|
|
||||||
let notifiedUser
|
|
||||||
let authenticatedUser
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
const categoryIds = ['cat9']
|
const categoryIds = ['cat9']
|
||||||
const createPostMutation = gql`
|
const createPostMutation = gql`
|
||||||
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
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({
|
const createServerResult = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
user: authenticatedUser,
|
user: authenticatedUser,
|
||||||
neode: instance,
|
neode: neode,
|
||||||
driver,
|
driver,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -56,14 +53,14 @@ beforeAll(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
notifiedUser = await instance.create('User', {
|
notifiedUser = await neode.create('User', {
|
||||||
id: 'you',
|
id: 'you',
|
||||||
name: 'Al Capone',
|
name: 'Al Capone',
|
||||||
slug: 'al-capone',
|
slug: 'al-capone',
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
await instance.create('Category', {
|
await neode.create('Category', {
|
||||||
id: 'cat9',
|
id: 'cat9',
|
||||||
name: 'Democracy & Politics',
|
name: 'Democracy & Politics',
|
||||||
icon: 'university',
|
icon: 'university',
|
||||||
@ -146,7 +143,7 @@ describe('notifications', () => {
|
|||||||
describe('commenter is not me', () => {
|
describe('commenter is not me', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
commentContent = 'Commenters comment.'
|
commentContent = 'Commenters comment.'
|
||||||
commentAuthor = await instance.create('User', {
|
commentAuthor = await neode.create('User', {
|
||||||
id: 'commentAuthor',
|
id: 'commentAuthor',
|
||||||
name: 'Mrs Comment',
|
name: 'Mrs Comment',
|
||||||
slug: 'mrs-comment',
|
slug: 'mrs-comment',
|
||||||
@ -173,7 +170,6 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -190,7 +186,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -214,7 +210,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -228,7 +224,7 @@ describe('notifications', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
postAuthor = await instance.create('User', {
|
postAuthor = await neode.create('User', {
|
||||||
id: 'postAuthor',
|
id: 'postAuthor',
|
||||||
name: 'Mrs Post',
|
name: 'Mrs Post',
|
||||||
slug: 'mrs-post',
|
slug: 'mrs-post',
|
||||||
@ -265,7 +261,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -409,7 +405,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -432,7 +428,7 @@ describe('notifications', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
commentContent =
|
commentContent =
|
||||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
'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',
|
id: 'commentAuthor',
|
||||||
name: 'Mrs Comment',
|
name: 'Mrs Comment',
|
||||||
slug: 'mrs-comment',
|
slug: 'mrs-comment',
|
||||||
@ -442,7 +438,7 @@ describe('notifications', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('sends only one notification with reason mentioned_in_comment', async () => {
|
it('sends only one notification with reason mentioned_in_comment', async () => {
|
||||||
postAuthor = await instance.create('User', {
|
postAuthor = await neode.create('User', {
|
||||||
id: 'MrPostAuthor',
|
id: 'MrPostAuthor',
|
||||||
name: 'Mr Author',
|
name: 'Mr Author',
|
||||||
slug: 'mr-author',
|
slug: 'mr-author',
|
||||||
@ -467,7 +463,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -501,7 +497,7 @@ describe('notifications', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -518,7 +514,7 @@ describe('notifications', () => {
|
|||||||
await postAuthor.relateTo(notifiedUser, 'blocked')
|
await postAuthor.relateTo(notifiedUser, 'blocked')
|
||||||
commentContent =
|
commentContent =
|
||||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
'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',
|
id: 'commentAuthor',
|
||||||
name: 'Mrs Comment',
|
name: 'Mrs Comment',
|
||||||
slug: 'mrs-comment',
|
slug: 'mrs-comment',
|
||||||
@ -532,7 +528,7 @@ describe('notifications', () => {
|
|||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { notifications: [] },
|
data: { notifications: [] },
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { gql } from '../helpers/jest'
|
import { gql } from '../helpers/jest'
|
||||||
import Factory from '../seed/factories'
|
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 { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer from '../server'
|
import createServer from '../server'
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||||
import { neode } from '../bootstrap/neo4j'
|
import { getNeode } from '../bootstrap/neo4j'
|
||||||
import CONFIG from '../config'
|
import CONFIG from '../config'
|
||||||
|
|
||||||
const debug = !!CONFIG.DEBUG
|
const debug = !!CONFIG.DEBUG
|
||||||
const allowExternalErrors = true
|
const allowExternalErrors = true
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
const isAuthenticated = rule({
|
const isAuthenticated = rule({
|
||||||
cache: 'contextual',
|
cache: 'contextual',
|
||||||
@ -36,7 +36,7 @@ const isMyOwn = rule({
|
|||||||
const isMySocialMedia = rule({
|
const isMySocialMedia = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_, args, { user }) => {
|
})(async (_, args, { user }) => {
|
||||||
let socialMedia = await instance.find('SocialMedia', args.id)
|
let socialMedia = await neode.find('SocialMedia', args.id)
|
||||||
socialMedia = await socialMedia.toJson()
|
socialMedia = await socialMedia.toJson()
|
||||||
return socialMedia.ownedBy.node.id === user.id
|
return socialMedia.ownedBy.node.id === user.id
|
||||||
})
|
})
|
||||||
@ -47,17 +47,18 @@ const isAuthor = rule({
|
|||||||
if (!user) return false
|
if (!user) return false
|
||||||
const { id: resourceId } = args
|
const { id: resourceId } = args
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
const authorReadTxPromise = session.readTransaction(async transaction => {
|
||||||
const result = await session.run(
|
const authorTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
|
MATCH (resource {id: $resourceId})<-[:WROTE]-(author {id: $userId})
|
||||||
RETURN author
|
RETURN author
|
||||||
`,
|
`,
|
||||||
{ resourceId, userId: user.id },
|
{ resourceId, userId: user.id },
|
||||||
)
|
)
|
||||||
const [author] = result.records.map(record => {
|
return authorTransactionResponse.records.map(record => record.get('author'))
|
||||||
return record.get('author')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [author] = await authorReadTxPromise
|
||||||
return !!author
|
return !!author
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
|
|||||||
import createServer from '../server'
|
import createServer from '../server'
|
||||||
import Factory from '../seed/factories'
|
import Factory from '../seed/factories'
|
||||||
import { gql } from '../helpers/jest'
|
import { gql } from '../helpers/jest'
|
||||||
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = getNeode()
|
const instance = getNeode()
|
||||||
|
|||||||
@ -4,10 +4,16 @@ const isUniqueFor = (context, type) => {
|
|||||||
return async slug => {
|
return async slug => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const response = await session.run(`MATCH(p:${type} {slug: $slug }) return p.slug`, {
|
const existingSlug = await session.readTransaction(transaction => {
|
||||||
slug,
|
return transaction.run(
|
||||||
|
`
|
||||||
|
MATCH(p:${type} {slug: $slug })
|
||||||
|
RETURN p.slug
|
||||||
|
`,
|
||||||
|
{ slug },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
return response.records.length === 0
|
return existingSlug.records.length === 0
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../seed/factories'
|
import Factory from '../seed/factories'
|
||||||
import { gql } from '../helpers/jest'
|
import { gql } from '../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||||
import createServer from '../server'
|
import createServer from '../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import createOrUpdateLocations from './nodes/locations'
|
import createOrUpdateLocations from '../nodes/locations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
SignupVerification: async (resolve, root, args, context, info) => {
|
SignupVerification: async (resolve, root, args, context, info) => {
|
||||||
const result = await 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
|
return result
|
||||||
},
|
},
|
||||||
UpdateUser: async (resolve, root, args, context, info) => {
|
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_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
|
||||||
const NO_CATEGORIES_ERR_MESSAGE =
|
const NO_CATEGORIES_ERR_MESSAGE =
|
||||||
'You cannot save a post without at least one category or more than three'
|
'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 validateCreateComment = async (resolve, root, args, context, info) => {
|
||||||
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||||
const { postId } = args
|
const { postId } = args
|
||||||
@ -14,14 +14,15 @@ const validateCreateComment = async (resolve, root, args, context, info) => {
|
|||||||
}
|
}
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const postQueryRes = await session.run(
|
const postQueryRes = await session.readTransaction(transaction => {
|
||||||
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
RETURN post`,
|
RETURN post
|
||||||
{
|
`,
|
||||||
postId,
|
{ postId },
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
const [post] = postQueryRes.records.map(record => {
|
const [post] = postQueryRes.records.map(record => {
|
||||||
return record.get('post')
|
return record.get('post')
|
||||||
})
|
})
|
||||||
@ -72,8 +73,8 @@ const validateReview = async (resolve, root, args, context, info) => {
|
|||||||
const { user, driver } = context
|
const { user, driver } = context
|
||||||
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const reportReadTxPromise = session.writeTransaction(async txc => {
|
const reportReadTxPromise = session.readTransaction(async transaction => {
|
||||||
const validateReviewTransactionResponse = await txc.run(
|
const validateReviewTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource {id: $resourceId})
|
MATCH (resource {id: $resourceId})
|
||||||
WHERE resource:User OR resource:Post OR resource:Comment
|
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)
|
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 {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateComment: validateCreateComment,
|
CreateComment: validateCreateComment,
|
||||||
UpdateComment: validateUpdateComment,
|
UpdateComment: validateUpdateComment,
|
||||||
CreatePost: validatePost,
|
CreatePost: validatePost,
|
||||||
UpdatePost: validateUpdatePost,
|
UpdatePost: validateUpdatePost,
|
||||||
|
UpdateUser: validateUpdateUser,
|
||||||
fileReport: validateReport,
|
fileReport: validateReport,
|
||||||
review: validateReview,
|
review: validateReview,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import Factory from '../../seed/factories'
|
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 { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer from '../../server'
|
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(() => {
|
beforeAll(() => {
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
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 Factory from '../seed/factories'
|
||||||
import { neode } from '../bootstrap/neo4j'
|
import { getNeode } from '../bootstrap/neo4j'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await factory.cleanDatabase()
|
await factory.cleanDatabase()
|
||||||
@ -10,7 +10,7 @@ afterEach(async () => {
|
|||||||
|
|
||||||
describe('role', () => {
|
describe('role', () => {
|
||||||
it('defaults to `user`', async () => {
|
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(
|
await expect(user.toJson()).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@ -21,7 +21,7 @@ describe('role', () => {
|
|||||||
|
|
||||||
describe('slug', () => {
|
describe('slug', () => {
|
||||||
it('normalizes to lowercase letters', async () => {
|
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(
|
await expect(user.toJson()).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
slug: 'matt',
|
slug: 'matt',
|
||||||
@ -30,9 +30,9 @@ describe('slug', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('must be unique', async done => {
|
it('must be unique', async done => {
|
||||||
await instance.create('User', { slug: 'Matt' })
|
await neode.create('User', { slug: 'Matt' })
|
||||||
try {
|
try {
|
||||||
await expect(instance.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
|
await expect(neode.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
|
||||||
done()
|
done()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`
|
throw new Error(`
|
||||||
@ -54,7 +54,7 @@ describe('slug', () => {
|
|||||||
|
|
||||||
describe('characters', () => {
|
describe('characters', () => {
|
||||||
const createUser = attrs => {
|
const createUser = attrs => {
|
||||||
return instance.create('User', attrs).then(user => user.toJson())
|
return neode.create('User', attrs).then(user => user.toJson())
|
||||||
}
|
}
|
||||||
|
|
||||||
it('-', async () => {
|
it('-', async () => {
|
||||||
@ -70,15 +70,11 @@ describe('slug', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it(' ', async () => {
|
it(' ', async () => {
|
||||||
await expect(createUser({ slug: 'matt rider' })).rejects.toThrow(
|
await expect(createUser({ slug: 'matt rider' })).rejects.toThrow('ERROR_VALIDATION')
|
||||||
/fails to match the required pattern/,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('ä', async () => {
|
it('ä', async () => {
|
||||||
await expect(createUser({ slug: 'mätt' })).rejects.toThrow(
|
await expect(createUser({ slug: 'mätt' })).rejects.toThrow('ERROR_VALIDATION')
|
||||||
/fails to match the required pattern/,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export default {
|
|||||||
Mutation: {
|
Mutation: {
|
||||||
CreateComment: async (object, params, context, resolveInfo) => {
|
CreateComment: async (object, params, context, resolveInfo) => {
|
||||||
const { postId } = params
|
const { postId } = params
|
||||||
|
const { user, driver } = context
|
||||||
// Adding relationship from comment to post by passing in the postId,
|
// 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
|
// 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
|
// because we use relationships for this. So, we are deleting it from params
|
||||||
@ -12,9 +13,11 @@ export default {
|
|||||||
delete params.postId
|
delete params.postId
|
||||||
params.id = params.id || uuid()
|
params.id = params.id || uuid()
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = driver.session()
|
||||||
try {
|
|
||||||
const createCommentCypher = `
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const createCommentTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
MATCH (author:User {id: $userId})
|
MATCH (author:User {id: $userId})
|
||||||
WITH post, author
|
WITH post, author
|
||||||
@ -23,15 +26,15 @@ export default {
|
|||||||
SET comment.updatedAt = toString(datetime())
|
SET comment.updatedAt = toString(datetime())
|
||||||
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
||||||
RETURN comment
|
RETURN comment
|
||||||
`
|
`,
|
||||||
const transactionRes = await session.run(createCommentCypher, {
|
{ userId: user.id, postId, params },
|
||||||
userId: context.user.id,
|
)
|
||||||
postId,
|
return createCommentTransactionResponse.records.map(
|
||||||
params,
|
record => record.get('comment').properties,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
const [comment] = transactionRes.records.map(record => record.get('comment').properties)
|
const [comment] = await writeTxResultPromise
|
||||||
|
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -39,15 +42,22 @@ export default {
|
|||||||
},
|
},
|
||||||
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
UpdateComment: async (_parent, params, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const updateCommentCypher = `
|
const updateCommentTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (comment:Comment {id: $params.id})
|
MATCH (comment:Comment {id: $params.id})
|
||||||
SET comment += $params
|
SET comment += $params
|
||||||
SET comment.updatedAt = toString(datetime())
|
SET comment.updatedAt = toString(datetime())
|
||||||
RETURN comment
|
RETURN comment
|
||||||
`
|
`,
|
||||||
const transactionRes = await session.run(updateCommentCypher, { params })
|
{ params },
|
||||||
const [comment] = transactionRes.records.map(record => record.get('comment').properties)
|
)
|
||||||
|
return updateCommentTransactionResponse.records.map(
|
||||||
|
record => record.get('comment').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [comment] = await writeTxResultPromise
|
||||||
return comment
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -55,8 +65,8 @@ export default {
|
|||||||
},
|
},
|
||||||
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
DeleteComment: async (_parent, args, context, _resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const deleteCommentTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (comment:Comment {id: $commentId})
|
MATCH (comment:Comment {id: $commentId})
|
||||||
SET comment.deleted = TRUE
|
SET comment.deleted = TRUE
|
||||||
@ -66,7 +76,12 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ commentId: args.id },
|
{ 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
|
return comment
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import Factory from '../../seed/factories'
|
|||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
@ -10,7 +10,8 @@ const factory = Factory()
|
|||||||
|
|
||||||
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
|
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
@ -19,8 +20,7 @@ beforeAll(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const client = createTestClient(server)
|
mutate = createTestClient(server).mutate
|
||||||
mutate = client.mutate
|
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -100,6 +100,7 @@ describe('CreateComment', () => {
|
|||||||
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
||||||
{
|
{
|
||||||
data: { CreateComment: { content: "I'm authorised to comment" } },
|
data: { CreateComment: { content: "I'm authorised to comment" } },
|
||||||
|
errors: undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -108,6 +109,7 @@ describe('CreateComment', () => {
|
|||||||
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
|
||||||
{
|
{
|
||||||
data: { CreateComment: { author: { name: 'Author' } } },
|
data: { CreateComment: { author: { name: 'Author' } } },
|
||||||
|
errors: undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -157,6 +159,7 @@ describe('UpdateComment', () => {
|
|||||||
it('updates the comment', async () => {
|
it('updates the comment', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } },
|
data: { UpdateComment: { id: 'c456', content: 'The comment is updated' } },
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -172,6 +175,7 @@ describe('UpdateComment', () => {
|
|||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateCommentMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let mutate, query, authenticatedUser, variables
|
let mutate, query, authenticatedUser, variables
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
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()
|
const neode = getNeode()
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { neode } from '../../../bootstrap/neo4j'
|
import log from './databaseLogger'
|
||||||
|
|
||||||
export const undefinedToNullResolver = list => {
|
export const undefinedToNullResolver = list => {
|
||||||
const resolvers = {}
|
const resolvers = {}
|
||||||
list.forEach(key => {
|
list.forEach(key => {
|
||||||
resolvers[key] = async (parent, params, context, resolveInfo) => {
|
resolvers[key] = async parent => {
|
||||||
return typeof parent[key] === 'undefined' ? null : parent[key]
|
return typeof parent[key] === 'undefined' ? null : parent[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -11,7 +11,6 @@ export const undefinedToNullResolver = list => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Resolver(type, options = {}) {
|
export default function Resolver(type, options = {}) {
|
||||||
const instance = neode()
|
|
||||||
const {
|
const {
|
||||||
idAttribute = 'id',
|
idAttribute = 'id',
|
||||||
undefinedToNull = [],
|
undefinedToNull = [],
|
||||||
@ -22,32 +21,49 @@ export default function Resolver(type, options = {}) {
|
|||||||
} = options
|
} = options
|
||||||
|
|
||||||
const _hasResolver = (resolvers, { key, connection }, { returnType }) => {
|
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]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
const id = parent[idAttribute]
|
const id = parent[idAttribute]
|
||||||
const statement = `MATCH(:${type} {${idAttribute}: {id}})${connection} RETURN related`
|
const session = driver.session()
|
||||||
const result = await instance.cypher(statement, { id })
|
const readTxResultPromise = session.readTransaction(async txc => {
|
||||||
let response = result.records.map(r => r.get('related').properties)
|
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
|
if (returnType === 'object') response = response[0] || null
|
||||||
return response
|
return response
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const booleanResolver = obj => {
|
const booleanResolver = obj => {
|
||||||
const resolvers = {}
|
const resolvers = {}
|
||||||
for (const [key, condition] of Object.entries(obj)) {
|
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]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
const result = await instance.cypher(
|
const id = parent[idAttribute]
|
||||||
`
|
const session = driver.session()
|
||||||
${condition.replace('this', 'this {id: $parent.id}')} as ${key}`,
|
const readTxResultPromise = session.readTransaction(async txc => {
|
||||||
{
|
const nodeCondition = condition.replace('this', 'this {id: $id}')
|
||||||
parent,
|
const cypher = `${nodeCondition} as ${key}`
|
||||||
cypherParams,
|
const result = await txc.run(cypher, { id, cypherParams })
|
||||||
},
|
log(result)
|
||||||
)
|
const [response] = result.records.map(r => r.get(key))
|
||||||
const [record] = result.records
|
return response
|
||||||
return record.get(key)
|
})
|
||||||
|
try {
|
||||||
|
return await readTxResultPromise
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolvers
|
return resolvers
|
||||||
@ -56,16 +72,25 @@ export default function Resolver(type, options = {}) {
|
|||||||
const countResolver = obj => {
|
const countResolver = obj => {
|
||||||
const resolvers = {}
|
const resolvers = {}
|
||||||
for (const [key, connection] of Object.entries(obj)) {
|
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]
|
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||||
|
const session = driver.session()
|
||||||
|
const readTxResultPromise = session.readTransaction(async txc => {
|
||||||
const id = parent[idAttribute]
|
const id = parent[idAttribute]
|
||||||
const statement = `
|
const cypher = `
|
||||||
MATCH(u:${type} {${idAttribute}: {id}})${connection}
|
MATCH(u:${type} {${idAttribute}: $id})${connection}
|
||||||
RETURN COUNT(DISTINCT(related)) as count
|
RETURN COUNT(DISTINCT(related)) as count
|
||||||
`
|
`
|
||||||
const result = await instance.cypher(statement, { id })
|
const result = await txc.run(cypher, { id, cypherParams })
|
||||||
|
log(result)
|
||||||
const [response] = result.records.map(r => r.get('count').toNumber())
|
const [response] = result.records.map(r => r.get('count').toNumber())
|
||||||
return response
|
return response
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
return await readTxResultPromise
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolvers
|
return resolvers
|
||||||
|
|||||||
@ -5,24 +5,29 @@ export default async function createPasswordReset(options) {
|
|||||||
const normalizedEmail = normalizeEmail(email)
|
const normalizedEmail = normalizeEmail(email)
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const cypher = `
|
const createPasswordResetTxPromise = session.writeTransaction(async transaction => {
|
||||||
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
|
const createPasswordResetTransactionResponse = await transaction.run(
|
||||||
CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL})
|
|
||||||
MERGE (u)-[:REQUESTED]->(pr)
|
|
||||||
RETURN e, pr, u
|
|
||||||
`
|
`
|
||||||
const transactionRes = await session.run(cypher, {
|
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(),
|
issuedAt: issuedAt.toISOString(),
|
||||||
nonce,
|
nonce,
|
||||||
email: normalizedEmail,
|
email: normalizedEmail,
|
||||||
})
|
},
|
||||||
const records = transactionRes.records.map(record => {
|
)
|
||||||
const { email } = record.get('e').properties
|
return createPasswordResetTransactionResponse.records.map(record => {
|
||||||
const { nonce } = record.get('pr').properties
|
const { email } = record.get('email').properties
|
||||||
const { name } = record.get('u').properties
|
const { nonce } = record.get('passwordReset').properties
|
||||||
|
const { name } = record.get('user').properties
|
||||||
return { email, nonce, name }
|
return { email, nonce, name }
|
||||||
})
|
})
|
||||||
return records[0] || {}
|
})
|
||||||
|
const [records] = await createPasswordResetTxPromise
|
||||||
|
return records || {}
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
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'
|
import { UserInputError } from 'apollo-server'
|
||||||
|
|
||||||
export default async function alreadyExistingMail({ args, context }) {
|
export default async function alreadyExistingMail({ args, context }) {
|
||||||
const cypher = `
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
|
const existingEmailAddressTxPromise = session.writeTransaction(async transaction => {
|
||||||
|
const existingEmailAddressTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (email:EmailAddress {email: $email})
|
MATCH (email:EmailAddress {email: $email})
|
||||||
OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
|
OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
|
||||||
RETURN email, user
|
RETURN email, user
|
||||||
`
|
`,
|
||||||
let transactionRes
|
{ email: args.email },
|
||||||
const session = context.driver.session()
|
)
|
||||||
try {
|
return existingEmailAddressTransactionResponse.records.map(record => {
|
||||||
transactionRes = await session.run(cypher, { email: args.email })
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
const [result] = transactionRes.records.map(record => {
|
|
||||||
return {
|
return {
|
||||||
alreadyExistingEmail: record.get('email').properties,
|
alreadyExistingEmail: record.get('email').properties,
|
||||||
user: record.get('user') && record.get('user').properties,
|
user: record.get('user') && record.get('user').properties,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { alreadyExistingEmail, user } = result || {}
|
})
|
||||||
|
const [emailBelongsToUser] = await existingEmailAddressTxPromise
|
||||||
|
const { alreadyExistingEmail, user } = emailBelongsToUser || {}
|
||||||
if (user) throw new UserInputError('A user account with this email already exists.')
|
if (user) throw new UserInputError('A user account with this email already exists.')
|
||||||
return alreadyExistingEmail
|
return alreadyExistingEmail
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const resourceTypes = ['Post', 'Comment']
|
const resourceTypes = ['Post', 'Comment']
|
||||||
|
|
||||||
const transformReturnType = record => {
|
const transformReturnType = record => {
|
||||||
@ -42,16 +44,29 @@ export default {
|
|||||||
}
|
}
|
||||||
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : ''
|
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : ''
|
||||||
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : ''
|
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : ''
|
||||||
const cypher = `
|
|
||||||
|
const readTxResultPromise = session.readTransaction(async transaction => {
|
||||||
|
const notificationsTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
|
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
|
||||||
${whereClause}
|
${whereClause}
|
||||||
RETURN resource, notification, user
|
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}
|
${orderByClause}
|
||||||
${offset} ${limit}
|
${offset} ${limit}
|
||||||
`
|
`,
|
||||||
|
{ id: currentUser.id },
|
||||||
|
)
|
||||||
|
log(notificationsTransactionResponse)
|
||||||
|
return notificationsTransactionResponse.records.map(record => record.get('notification'))
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const result = await session.run(cypher, { id: currentUser.id })
|
const notifications = await readTxResultPromise
|
||||||
return result.records.map(transformReturnType)
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
@ -61,15 +76,21 @@ export default {
|
|||||||
markAsRead: async (parent, args, context, resolveInfo) => {
|
markAsRead: async (parent, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const cypher = `
|
const markNotificationAsReadTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id:$id})
|
||||||
SET notification.read = TRUE
|
SET notification.read = TRUE
|
||||||
RETURN resource, notification, user
|
RETURN resource, notification, user
|
||||||
`
|
`,
|
||||||
const result = await session.run(cypher, { resourceId: args.id, id: currentUser.id })
|
{ resourceId: args.id, id: currentUser.id },
|
||||||
const notifications = await result.records.map(transformReturnType)
|
)
|
||||||
return notifications[0]
|
log(markNotificationAsReadTransactionResponse)
|
||||||
|
return markNotificationAsReadTransactionResponse.records.map(transformReturnType)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [notifications] = await writeTxResultPromise
|
||||||
|
return notifications
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -184,6 +184,7 @@ describe('given some notifications', () => {
|
|||||||
data: {
|
data: {
|
||||||
notifications: expect.arrayContaining(expected),
|
notifications: expect.arrayContaining(expected),
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -233,7 +234,10 @@ describe('given some notifications', () => {
|
|||||||
`
|
`
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deletePostMutation, variables: { id: 'p3' } }),
|
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()
|
authenticatedUser = await user.toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,11 +246,12 @@ describe('given some notifications', () => {
|
|||||||
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
data: { notifications: [expect.any(Object), expect.any(Object)] },
|
data: { notifications: [expect.any(Object), expect.any(Object)] },
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
await deletePostAction()
|
await deletePostAction()
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: notificationQuery, variables: { ...variables, read: false } }),
|
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()
|
const stillValid = new Date()
|
||||||
stillValid.setDate(stillValid.getDate() - 1)
|
stillValid.setDate(stillValid.getDate() - 1)
|
||||||
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
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()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(cypher, {
|
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,
|
stillValid,
|
||||||
email,
|
email,
|
||||||
nonce,
|
nonce,
|
||||||
encryptedNewPassword,
|
encryptedNewPassword,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return passwordResetTransactionResponse.records.map(record => record.get('passwordReset'))
|
||||||
})
|
})
|
||||||
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
const [reset] = await passwordResetTxPromise
|
||||||
const response = !!(reset && reset.properties.usedAt)
|
return !!(reset && reset.properties.usedAt)
|
||||||
return response
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
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 createPasswordReset from './helpers/createPasswordReset'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
@ -14,14 +14,11 @@ let authenticatedUser
|
|||||||
let variables
|
let variables
|
||||||
|
|
||||||
const getAllPasswordResets = async () => {
|
const getAllPasswordResets = async () => {
|
||||||
const session = driver.session()
|
const passwordResetQuery = await neode.cypher(
|
||||||
try {
|
'MATCH (passwordReset:PasswordReset) RETURN passwordReset',
|
||||||
const transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
|
)
|
||||||
const resets = transactionRes.records.map(record => record.get('r'))
|
const resets = passwordResetQuery.records.map(record => record.get('passwordReset'))
|
||||||
return resets
|
return resets
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -57,17 +57,20 @@ export default {
|
|||||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||||
const { postId, data } = params
|
const { postId, data } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const readTxResultPromise = session.readTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const emotionsCountTransactionResponse = await transaction.run(
|
||||||
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
`
|
||||||
|
MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
||||||
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
||||||
`,
|
`,
|
||||||
{ postId, data },
|
{ postId, data },
|
||||||
)
|
)
|
||||||
|
return emotionsCountTransactionResponse.records.map(
|
||||||
const [emotionsCount] = transactionRes.records.map(record => {
|
record => record.get('emotionsCount').low,
|
||||||
return record.get('emotionsCount').low
|
)
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [emotionsCount] = await readTxResultPromise
|
||||||
return emotionsCount
|
return emotionsCount
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -76,16 +79,18 @@ export default {
|
|||||||
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
||||||
const { postId } = params
|
const { postId } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const readTxResultPromise = session.readTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const emotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
`
|
||||||
RETURN collect(emoted.emotion) as emotion`,
|
MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||||
|
RETURN collect(emoted.emotion) as emotion
|
||||||
|
`,
|
||||||
{ userId: context.user.id, postId },
|
{ userId: context.user.id, postId },
|
||||||
)
|
)
|
||||||
|
return emotionsTransactionResponse.records.map(record => record.get('emotion'))
|
||||||
const [emotions] = transactionRes.records.map(record => {
|
|
||||||
return record.get('emotion')
|
|
||||||
})
|
})
|
||||||
|
try {
|
||||||
|
const [emotions] = await readTxResultPromise
|
||||||
return emotions
|
return emotions
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -98,7 +103,11 @@ export default {
|
|||||||
delete params.categoryIds
|
delete params.categoryIds
|
||||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||||
params.id = params.id || uuid()
|
params.id = params.id || uuid()
|
||||||
const createPostCypher = `CREATE (post:Post {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.createdAt = toString(datetime())
|
||||||
SET post.updatedAt = toString(datetime())
|
SET post.updatedAt = toString(datetime())
|
||||||
WITH post
|
WITH post
|
||||||
@ -108,15 +117,15 @@ export default {
|
|||||||
UNWIND $categoryIds AS categoryId
|
UNWIND $categoryIds AS categoryId
|
||||||
MATCH (category:Category {id: categoryId})
|
MATCH (category:Category {id: categoryId})
|
||||||
MERGE (post)-[:CATEGORIZED]->(category)
|
MERGE (post)-[:CATEGORIZED]->(category)
|
||||||
RETURN post`
|
RETURN post
|
||||||
|
`,
|
||||||
const createPostVariables = { userId: context.user.id, categoryIds, params }
|
{ userId: context.user.id, categoryIds, params },
|
||||||
|
)
|
||||||
const session = context.driver.session()
|
return createPostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(createPostCypher, createPostVariables)
|
const [post] = await writeTxResultPromise
|
||||||
const posts = transactionRes.records.map(record => record.get('post').properties)
|
return post
|
||||||
return posts[0]
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||||
throw new UserInputError('Post with this slug already exists!')
|
throw new UserInputError('Post with this slug already exists!')
|
||||||
@ -129,14 +138,14 @@ export default {
|
|||||||
const { categoryIds } = params
|
const { categoryIds } = params
|
||||||
delete params.categoryIds
|
delete params.categoryIds
|
||||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||||
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
const session = context.driver.session()
|
||||||
|
let updatePostCypher = `
|
||||||
|
MATCH (post:Post {id: $params.id})
|
||||||
SET post += $params
|
SET post += $params
|
||||||
SET post.updatedAt = toString(datetime())
|
SET post.updatedAt = toString(datetime())
|
||||||
WITH post
|
WITH post
|
||||||
`
|
`
|
||||||
|
|
||||||
const session = context.driver.session()
|
|
||||||
try {
|
|
||||||
if (categoryIds && categoryIds.length) {
|
if (categoryIds && categoryIds.length) {
|
||||||
const cypherDeletePreviousRelations = `
|
const cypherDeletePreviousRelations = `
|
||||||
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||||
@ -144,7 +153,9 @@ export default {
|
|||||||
RETURN post, category
|
RETURN post, category
|
||||||
`
|
`
|
||||||
|
|
||||||
await session.run(cypherDeletePreviousRelations, { params })
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(cypherDeletePreviousRelations, { params })
|
||||||
|
})
|
||||||
|
|
||||||
updatePostCypher += `
|
updatePostCypher += `
|
||||||
UNWIND $categoryIds AS categoryId
|
UNWIND $categoryIds AS categoryId
|
||||||
@ -156,11 +167,15 @@ export default {
|
|||||||
|
|
||||||
updatePostCypher += `RETURN post`
|
updatePostCypher += `RETURN post`
|
||||||
const updatePostVariables = { categoryIds, params }
|
const updatePostVariables = { categoryIds, params }
|
||||||
|
try {
|
||||||
const transactionRes = await session.run(updatePostCypher, updatePostVariables)
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const [post] = transactionRes.records.map(record => {
|
const updatePostTransactionResponse = await transaction.run(
|
||||||
return record.get('post').properties
|
updatePostCypher,
|
||||||
|
updatePostVariables,
|
||||||
|
)
|
||||||
|
return updatePostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
})
|
})
|
||||||
|
const [post] = await writeTxResultPromise
|
||||||
return post
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -169,9 +184,8 @@ export default {
|
|||||||
|
|
||||||
DeletePost: async (object, args, context, resolveInfo) => {
|
DeletePost: async (object, args, context, resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
// we cannot set slug to 'UNAVAILABE' because of unique constraints
|
const deletePostTransactionResponse = await transaction.run(
|
||||||
const transactionRes = await session.run(
|
|
||||||
`
|
`
|
||||||
MATCH (post:Post {id: $postId})
|
MATCH (post:Post {id: $postId})
|
||||||
OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
|
OPTIONAL MATCH (post)<-[:COMMENTS]-(comment:Comment)
|
||||||
@ -185,7 +199,10 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ postId: args.id },
|
{ 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
|
return post
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -195,21 +212,24 @@ export default {
|
|||||||
const { to, data } = params
|
const { to, data } = params
|
||||||
const { user } = context
|
const { user } = context
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const addPostEmotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
`
|
||||||
|
MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
||||||
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
||||||
RETURN userFrom, postTo, emotedRelation`,
|
RETURN userFrom, postTo, emotedRelation`,
|
||||||
{ user, to, data },
|
{ user, to, data },
|
||||||
)
|
)
|
||||||
|
return addPostEmotionsTransactionResponse.records.map(record => {
|
||||||
const [emoted] = transactionRes.records.map(record => {
|
|
||||||
return {
|
return {
|
||||||
from: { ...record.get('userFrom').properties },
|
from: { ...record.get('userFrom').properties },
|
||||||
to: { ...record.get('postTo').properties },
|
to: { ...record.get('postTo').properties },
|
||||||
...record.get('emotedRelation').properties,
|
...record.get('emotedRelation').properties,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [emoted] = await writeTxResultPromise
|
||||||
return emoted
|
return emoted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -219,20 +239,25 @@ export default {
|
|||||||
const { to, data } = params
|
const { to, data } = params
|
||||||
const { id: from } = context.user
|
const { id: from } = context.user
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionRes = await session.run(
|
const removePostEmotionsTransactionResponse = await transaction.run(
|
||||||
`MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
`
|
||||||
|
MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
||||||
DELETE emotedRelation
|
DELETE emotedRelation
|
||||||
RETURN userFrom, postTo`,
|
RETURN userFrom, postTo
|
||||||
|
`,
|
||||||
{ from, to, data },
|
{ from, to, data },
|
||||||
)
|
)
|
||||||
const [emoted] = transactionRes.records.map(record => {
|
return removePostEmotionsTransactionResponse.records.map(record => {
|
||||||
return {
|
return {
|
||||||
from: { ...record.get('userFrom').properties },
|
from: { ...record.get('userFrom').properties },
|
||||||
to: { ...record.get('postTo').properties },
|
to: { ...record.get('postTo').properties },
|
||||||
emotion: data.emotion,
|
emotion: data.emotion,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [emoted] = await writeTxResultPromise
|
||||||
return emoted
|
return emoted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -344,21 +369,28 @@ export default {
|
|||||||
relatedContributions: async (parent, params, context, resolveInfo) => {
|
relatedContributions: async (parent, params, context, resolveInfo) => {
|
||||||
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
|
||||||
const { id } = parent
|
const { id } = parent
|
||||||
const statement = `
|
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)
|
MATCH (p:Post {id: $id})-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||||
WHERE NOT post.deleted AND NOT post.disabled
|
WHERE NOT post.deleted AND NOT post.disabled
|
||||||
RETURN DISTINCT post
|
RETURN DISTINCT post
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
`
|
`,
|
||||||
let relatedContributions
|
{ id },
|
||||||
const session = context.driver.session()
|
)
|
||||||
|
return relatedContributionsTransactionResponse.records.map(
|
||||||
|
record => record.get('post').properties,
|
||||||
|
)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const result = await session.run(statement, { id })
|
const relatedContributions = await writeTxResultPromise
|
||||||
relatedContributions = result.records.map(r => r.get('post').properties)
|
return relatedContributions
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return relatedContributions
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
@ -383,7 +383,10 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('updates a post', async () => {
|
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(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
@ -394,6 +397,7 @@ describe('UpdatePost', () => {
|
|||||||
data: {
|
data: {
|
||||||
UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) },
|
UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) },
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -421,6 +425,7 @@ describe('UpdatePost', () => {
|
|||||||
categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]),
|
categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -441,6 +446,7 @@ describe('UpdatePost', () => {
|
|||||||
categories: expect.arrayContaining([{ id: 'cat27' }]),
|
categories: expect.arrayContaining([{ id: 'cat27' }]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -722,6 +728,7 @@ describe('UpdatePost', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
||||||
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
import { neode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import fileUpload from './fileUpload'
|
import fileUpload from './fileUpload'
|
||||||
import encryptPassword from '../../helpers/encryptPassword'
|
import encryptPassword from '../../helpers/encryptPassword'
|
||||||
import generateNonce from './helpers/generateNonce'
|
import generateNonce from './helpers/generateNonce'
|
||||||
import existingEmailAddress from './helpers/existingEmailAddress'
|
import existingEmailAddress from './helpers/existingEmailAddress'
|
||||||
import normalizeEmail from './helpers/normalizeEmail'
|
import normalizeEmail from './helpers/normalizeEmail'
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
@ -16,7 +16,7 @@ export default {
|
|||||||
let emailAddress = await existingEmailAddress({ args, context })
|
let emailAddress = await existingEmailAddress({ args, context })
|
||||||
if (emailAddress) return emailAddress
|
if (emailAddress) return emailAddress
|
||||||
try {
|
try {
|
||||||
emailAddress = await instance.create('EmailAddress', args)
|
emailAddress = await neode.create('EmailAddress', args)
|
||||||
return emailAddress.toJson()
|
return emailAddress.toJson()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UserInputError(e.message)
|
throw new UserInputError(e.message)
|
||||||
@ -32,7 +32,7 @@ export default {
|
|||||||
|
|
||||||
let { nonce, email } = args
|
let { nonce, email } = args
|
||||||
email = normalizeEmail(email)
|
email = normalizeEmail(email)
|
||||||
const result = await instance.cypher(
|
const result = await neode.cypher(
|
||||||
`
|
`
|
||||||
MATCH(email:EmailAddress {nonce: {nonce}, email: {email}})
|
MATCH(email:EmailAddress {nonce: {nonce}, email: {email}})
|
||||||
WHERE NOT (email)-[:BELONGS_TO]->()
|
WHERE NOT (email)-[:BELONGS_TO]->()
|
||||||
@ -40,12 +40,12 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ nonce, email },
|
{ 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')
|
if (!emailAddress) throw new UserInputError('Invalid email or nonce')
|
||||||
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
||||||
args = await encryptPassword(args)
|
args = await encryptPassword(args)
|
||||||
try {
|
try {
|
||||||
const user = await instance.create('User', args)
|
const user = await neode.create('User', args)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
user.relateTo(emailAddress, 'primaryEmail'),
|
user.relateTo(emailAddress, 'primaryEmail'),
|
||||||
emailAddress.relateTo(user, 'belongsTo'),
|
emailAddress.relateTo(user, 'belongsTo'),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const transformReturnType = record => {
|
const transformReturnType = record => {
|
||||||
return {
|
return {
|
||||||
...record.get('report').properties,
|
...record.get('report').properties,
|
||||||
@ -11,12 +13,11 @@ const transformReturnType = record => {
|
|||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
fileReport: async (_parent, params, context, _resolveInfo) => {
|
fileReport: async (_parent, params, context, _resolveInfo) => {
|
||||||
let createdRelationshipWithNestedAttributes
|
|
||||||
const { resourceId, reasonCategory, reasonDescription } = params
|
const { resourceId, reasonCategory, reasonDescription } = params
|
||||||
const { driver, user } = context
|
const { driver, user } = context
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const reportWriteTxResultPromise = session.writeTransaction(async txc => {
|
const reportWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const reportTransactionResponse = await txc.run(
|
const reportTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (submitter:User {id: $submitterId})
|
MATCH (submitter:User {id: $submitterId})
|
||||||
MATCH (resource {id: $resourceId})
|
MATCH (resource {id: $resourceId})
|
||||||
@ -36,23 +37,23 @@ export default {
|
|||||||
reasonDescription,
|
reasonDescription,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
log(reportTransactionResponse)
|
||||||
return reportTransactionResponse.records.map(transformReturnType)
|
return reportTransactionResponse.records.map(transformReturnType)
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const txResult = await reportWriteTxResultPromise
|
const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise
|
||||||
if (!txResult[0]) return null
|
if (!createdRelationshipWithNestedAttributes) return null
|
||||||
createdRelationshipWithNestedAttributes = txResult[0]
|
return createdRelationshipWithNestedAttributes
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return createdRelationshipWithNestedAttributes
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Query: {
|
Query: {
|
||||||
reports: async (_parent, params, context, _resolveInfo) => {
|
reports: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { driver } = context
|
const { driver } = context
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
let reports, orderByClause, filterClause
|
let orderByClause, filterClause
|
||||||
switch (params.orderBy) {
|
switch (params.orderBy) {
|
||||||
case 'createdAt_asc':
|
case 'createdAt_asc':
|
||||||
orderByClause = 'ORDER BY report.createdAt ASC'
|
orderByClause = 'ORDER BY report.createdAt ASC'
|
||||||
@ -81,8 +82,8 @@ export default {
|
|||||||
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
|
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
|
||||||
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
|
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
|
||||||
|
|
||||||
const reportReadTxPromise = session.readTransaction(async tx => {
|
const reportReadTxPromise = session.readTransaction(async transaction => {
|
||||||
const allReportsTransactionResponse = await tx.run(
|
const allReportsTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (report:Report)-[:BELONGS_TO]->(resource)
|
MATCH (report:Report)-[:BELONGS_TO]->(resource)
|
||||||
WHERE (resource:User OR resource:Post OR resource:Comment)
|
WHERE (resource:User OR resource:Post OR resource:Comment)
|
||||||
@ -100,16 +101,15 @@ export default {
|
|||||||
${offset} ${limit}
|
${offset} ${limit}
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
log(allReportsTransactionResponse)
|
||||||
return allReportsTransactionResponse.records.map(record => record.get('report'))
|
return allReportsTransactionResponse.records.map(record => record.get('report'))
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const txResult = await reportReadTxPromise
|
const reports = await reportReadTxPromise
|
||||||
if (!txResult[0]) return null
|
return reports
|
||||||
reports = txResult
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return reports
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Report: {
|
Report: {
|
||||||
@ -118,23 +118,23 @@ export default {
|
|||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const { id } = parent
|
const { id } = parent
|
||||||
let filed
|
let filed
|
||||||
const readTxPromise = session.readTransaction(async tx => {
|
const readTxPromise = session.readTransaction(async transaction => {
|
||||||
const allReportsTransactionResponse = await tx.run(
|
const filedReportsTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id})
|
MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id})
|
||||||
RETURN filed, submitter
|
RETURN filed, submitter
|
||||||
`,
|
`,
|
||||||
{ id },
|
{ id },
|
||||||
)
|
)
|
||||||
return allReportsTransactionResponse.records.map(record => ({
|
log(filedReportsTransactionResponse)
|
||||||
|
return filedReportsTransactionResponse.records.map(record => ({
|
||||||
submitter: record.get('submitter').properties,
|
submitter: record.get('submitter').properties,
|
||||||
filed: record.get('filed').properties,
|
filed: record.get('filed').properties,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const txResult = await readTxPromise
|
const filedReports = await readTxPromise
|
||||||
if (!txResult[0]) return null
|
filed = filedReports.map(reportedRecord => {
|
||||||
filed = txResult.map(reportedRecord => {
|
|
||||||
const { submitter, filed } = reportedRecord
|
const { submitter, filed } = reportedRecord
|
||||||
const relationshipWithNestedAttributes = {
|
const relationshipWithNestedAttributes = {
|
||||||
...filed,
|
...filed,
|
||||||
@ -152,8 +152,8 @@ export default {
|
|||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const { id } = parent
|
const { id } = parent
|
||||||
let reviewed
|
let reviewed
|
||||||
const readTxPromise = session.readTransaction(async tx => {
|
const readTxPromise = session.readTransaction(async transaction => {
|
||||||
const allReportsTransactionResponse = await tx.run(
|
const reviewedReportsTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User)
|
MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User)
|
||||||
RETURN moderator, review
|
RETURN moderator, review
|
||||||
@ -161,14 +161,15 @@ export default {
|
|||||||
`,
|
`,
|
||||||
{ id },
|
{ id },
|
||||||
)
|
)
|
||||||
return allReportsTransactionResponse.records.map(record => ({
|
log(reviewedReportsTransactionResponse)
|
||||||
|
return reviewedReportsTransactionResponse.records.map(record => ({
|
||||||
review: record.get('review').properties,
|
review: record.get('review').properties,
|
||||||
moderator: record.get('moderator').properties,
|
moderator: record.get('moderator').properties,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const txResult = await readTxPromise
|
const reviewedReports = await readTxPromise
|
||||||
reviewed = txResult.map(reportedRecord => {
|
reviewed = reviewedReports.map(reportedRecord => {
|
||||||
const { review, moderator } = reportedRecord
|
const { review, moderator } = reportedRecord
|
||||||
const relationshipWithNestedAttributes = {
|
const relationshipWithNestedAttributes = {
|
||||||
...review,
|
...review,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
|
|||||||
import createServer from '../.././server'
|
import createServer from '../.././server'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = getNeode()
|
const instance = getNeode()
|
||||||
@ -21,7 +21,6 @@ describe('file a report on a resource', () => {
|
|||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
disable
|
|
||||||
closed
|
closed
|
||||||
rule
|
rule
|
||||||
resource {
|
resource {
|
||||||
@ -489,7 +488,6 @@ describe('file a report on a resource', () => {
|
|||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
disable
|
|
||||||
closed
|
closed
|
||||||
resource {
|
resource {
|
||||||
__typename
|
__typename
|
||||||
@ -624,7 +622,6 @@ describe('file a report on a resource', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
disable: false,
|
|
||||||
closed: false,
|
closed: false,
|
||||||
resource: {
|
resource: {
|
||||||
__typename: 'User',
|
__typename: 'User',
|
||||||
@ -645,7 +642,6 @@ describe('file a report on a resource', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
disable: false,
|
|
||||||
closed: false,
|
closed: false,
|
||||||
resource: {
|
resource: {
|
||||||
__typename: 'Post',
|
__typename: 'Post',
|
||||||
@ -666,7 +662,6 @@ describe('file a report on a resource', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
disable: false,
|
|
||||||
closed: false,
|
closed: false,
|
||||||
resource: {
|
resource: {
|
||||||
__typename: 'Comment',
|
__typename: 'Comment',
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { neode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
const getUserAndBadge = async ({ badgeKey, userId }) => {
|
const getUserAndBadge = async ({ badgeKey, userId }) => {
|
||||||
const user = await instance.first('User', 'id', userId)
|
const user = await neode.first('User', 'id', userId)
|
||||||
const badge = await instance.first('Badge', 'id', badgeKey)
|
const badge = await neode.first('Badge', 'id', badgeKey)
|
||||||
if (!user) throw new UserInputError("Couldn't find a user with that id")
|
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")
|
if (!badge) throw new UserInputError("Couldn't find a badge with that id")
|
||||||
return { user, badge }
|
return { user, badge }
|
||||||
@ -24,8 +24,8 @@ export default {
|
|||||||
const { user } = await getUserAndBadge(params)
|
const { user } = await getUserAndBadge(params)
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
// silly neode cannot remove relationships
|
await session.writeTransaction(transaction => {
|
||||||
await session.run(
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
|
MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId})
|
||||||
DELETE reward
|
DELETE reward
|
||||||
@ -36,6 +36,7 @@ export default {
|
|||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
shout: async (_object, params, context, _resolveInfo) => {
|
shout: async (_object, params, context, _resolveInfo) => {
|
||||||
@ -5,22 +7,24 @@ export default {
|
|||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(
|
const shoutWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
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
|
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
||||||
MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
|
MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
|
||||||
RETURN COUNT(relation) > 0 as isShouted`,
|
RETURN COUNT(relation) > 0 as isShouted
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
log(shoutTransactionResponse)
|
||||||
const [isShouted] = transactionRes.records.map(record => {
|
return shoutTransactionResponse.records.map(record => record.get('isShouted'))
|
||||||
return record.get('isShouted')
|
|
||||||
})
|
})
|
||||||
|
const [isShouted] = await shoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
@ -31,20 +35,24 @@ export default {
|
|||||||
const { id, type } = params
|
const { id, type } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
try {
|
try {
|
||||||
const transactionRes = await session.run(
|
const unshoutWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
`MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
|
const unshoutTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
|
||||||
WHERE $type IN labels(node)
|
WHERE $type IN labels(node)
|
||||||
DELETE relation
|
DELETE relation
|
||||||
RETURN COUNT(relation) > 0 as isShouted`,
|
RETURN COUNT(relation) > 0 as isShouted
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const [isShouted] = transactionRes.records.map(record => {
|
log(unshoutTransactionResponse)
|
||||||
return record.get('isShouted')
|
return unshoutTransactionResponse.records.map(record => record.get('isShouted'))
|
||||||
})
|
})
|
||||||
|
const [isShouted] = await unshoutWriteTxResultPromise
|
||||||
return isShouted
|
return isShouted
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let mutate, query, authenticatedUser, variables
|
let mutate, query, authenticatedUser, variables
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { neode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import Resolver from './helpers/Resolver'
|
import Resolver from './helpers/Resolver'
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||||
const [user, socialMedia] = await Promise.all([
|
const [user, socialMedia] = await Promise.all([
|
||||||
instance.find('User', context.user.id),
|
neode.find('User', context.user.id),
|
||||||
instance.create('SocialMedia', params),
|
neode.create('SocialMedia', params),
|
||||||
])
|
])
|
||||||
await socialMedia.relateTo(user, 'ownedBy')
|
await socialMedia.relateTo(user, 'ownedBy')
|
||||||
const response = await socialMedia.toJson()
|
const response = await socialMedia.toJson()
|
||||||
@ -16,14 +16,14 @@ export default {
|
|||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
UpdateSocialMedia: async (object, params, context, resolveInfo) => {
|
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 })
|
await socialMedia.update({ url: params.url })
|
||||||
const response = await socialMedia.toJson()
|
const response = await socialMedia.toJson()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
DeleteSocialMedia: async (object, { id }, context, resolveInfo) => {
|
DeleteSocialMedia: async (object, { id }, context, resolveInfo) => {
|
||||||
const socialMedia = await instance.find('SocialMedia', id)
|
const socialMedia = await neode.find('SocialMedia', id)
|
||||||
if (!socialMedia) return null
|
if (!socialMedia) return null
|
||||||
await socialMedia.delete()
|
await socialMedia.delete()
|
||||||
return socialMedia.toJson()
|
return socialMedia.toJson()
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
|
|||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
describe('SocialMedia', () => {
|
describe('SocialMedia', () => {
|
||||||
let socialMediaAction, someUser, ownerNode, owner
|
let socialMediaAction, someUser, ownerNode, owner
|
||||||
@ -27,15 +27,15 @@ describe('SocialMedia', () => {
|
|||||||
const newUrl = 'https://twitter.com/bullerby'
|
const newUrl = 'https://twitter.com/bullerby'
|
||||||
|
|
||||||
const setUpSocialMedia = async () => {
|
const setUpSocialMedia = async () => {
|
||||||
const socialMediaNode = await instance.create('SocialMedia', { url })
|
const socialMediaNode = await neode.create('SocialMedia', { url })
|
||||||
await socialMediaNode.relateTo(ownerNode, 'ownedBy')
|
await socialMediaNode.relateTo(ownerNode, 'ownedBy')
|
||||||
return socialMediaNode.toJson()
|
return socialMediaNode.toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const someUserNode = await instance.create('User', userParams)
|
const someUserNode = await neode.create('User', userParams)
|
||||||
someUser = await someUserNode.toJson()
|
someUser = await someUserNode.toJson()
|
||||||
ownerNode = await instance.create('User', ownerParams)
|
ownerNode = await neode.create('User', ownerParams)
|
||||||
owner = await ownerNode.toJson()
|
owner = await ownerNode.toJson()
|
||||||
|
|
||||||
socialMediaAction = async (user, mutation, variables) => {
|
socialMediaAction = async (user, mutation, variables) => {
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
statistics: async (_parent, _args, { driver }) => {
|
statistics: async (_parent, _args, { driver }) => {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const response = {}
|
const counts = {}
|
||||||
try {
|
try {
|
||||||
const mapping = {
|
const mapping = {
|
||||||
countUsers: 'User',
|
countUsers: 'User',
|
||||||
@ -13,27 +15,28 @@ export default {
|
|||||||
countFollows: 'FOLLOWS',
|
countFollows: 'FOLLOWS',
|
||||||
countShouts: 'SHOUTED',
|
countShouts: 'SHOUTED',
|
||||||
}
|
}
|
||||||
const cypher = `
|
const statisticsReadTxResultPromise = session.readTransaction(async transaction => {
|
||||||
|
const statisticsTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
CALL apoc.meta.stats() YIELD labels, relTypesCount
|
CALL apoc.meta.stats() YIELD labels, relTypesCount
|
||||||
RETURN labels, relTypesCount
|
RETURN labels, relTypesCount
|
||||||
`
|
`,
|
||||||
const result = await session.run(cypher)
|
)
|
||||||
const [statistics] = await result.records.map(record => {
|
log(statisticsTransactionResponse)
|
||||||
|
return statisticsTransactionResponse.records.map(record => {
|
||||||
return {
|
return {
|
||||||
...record.get('labels'),
|
...record.get('labels'),
|
||||||
...record.get('relTypesCount'),
|
...record.get('relTypesCount'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
const [statistics] = await statisticsReadTxResultPromise
|
||||||
Object.keys(mapping).forEach(key => {
|
Object.keys(mapping).forEach(key => {
|
||||||
const stat = statistics[mapping[key]]
|
const stat = statistics[mapping[key]]
|
||||||
response[key] = stat ? stat.toNumber() : 0
|
counts[key] = stat ? stat.toNumber() : 0
|
||||||
})
|
})
|
||||||
|
counts.countInvites = counts.countEmails - counts.countUsers
|
||||||
/*
|
return counts
|
||||||
* Note: invites count is calculated this way because invitation codes are not in use yet
|
|
||||||
*/
|
|
||||||
response.countInvites = response.countEmails - response.countUsers
|
|
||||||
return response
|
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
let query, authenticatedUser
|
let query, authenticatedUser
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import encode from '../../jwt/encode'
|
import encode from '../../jwt/encode'
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs'
|
||||||
import { AuthenticationError } from 'apollo-server'
|
import { AuthenticationError } from 'apollo-server'
|
||||||
import { neode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import normalizeEmail from './helpers/normalizeEmail'
|
import normalizeEmail from './helpers/normalizeEmail'
|
||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
@ -13,7 +14,7 @@ export default {
|
|||||||
},
|
},
|
||||||
currentUser: async (object, params, ctx, resolveInfo) => {
|
currentUser: async (object, params, ctx, resolveInfo) => {
|
||||||
if (!ctx.user) return null
|
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()
|
return user.toJson()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -25,17 +26,18 @@ export default {
|
|||||||
email = normalizeEmail(email)
|
email = normalizeEmail(email)
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
const result = await session.run(
|
const loginReadTxResultPromise = session.readTransaction(async transaction => {
|
||||||
|
const loginTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (user:User {deleted: false})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})
|
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
|
RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1
|
||||||
`,
|
`,
|
||||||
{ userEmail: email },
|
{ userEmail: email },
|
||||||
)
|
)
|
||||||
const [currentUser] = await result.records.map(record => {
|
log(loginTransactionResponse)
|
||||||
return record.get('user')
|
return loginTransactionResponse.records.map(record => record.get('user'))
|
||||||
})
|
})
|
||||||
|
const [currentUser] = await loginReadTxResultPromise
|
||||||
if (
|
if (
|
||||||
currentUser &&
|
currentUser &&
|
||||||
(await bcrypt.compareSync(password, currentUser.encryptedPassword)) &&
|
(await bcrypt.compareSync(password, currentUser.encryptedPassword)) &&
|
||||||
@ -53,7 +55,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
|
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')
|
const encryptedPassword = currentUser.get('encryptedPassword')
|
||||||
if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) {
|
if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { gql } from '../../helpers/jest'
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer, { context } from '../../server'
|
import createServer, { context } from '../../server'
|
||||||
import encode from '../../jwt/encode'
|
import encode from '../../jwt/encode'
|
||||||
import { neode as getNeode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
import fileUpload from './fileUpload'
|
import fileUpload from './fileUpload'
|
||||||
import { neode } from '../../bootstrap/neo4j'
|
import { getNeode } from '../../bootstrap/neo4j'
|
||||||
import { UserInputError, ForbiddenError } from 'apollo-server'
|
import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||||
import Resolver from './helpers/Resolver'
|
import Resolver from './helpers/Resolver'
|
||||||
|
import log from './helpers/databaseLogger'
|
||||||
|
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
export const getBlockedUsers = async context => {
|
export const getBlockedUsers = async context => {
|
||||||
const { neode } = context
|
const { neode } = context
|
||||||
@ -73,7 +74,7 @@ export default {
|
|||||||
block: async (object, args, context, resolveInfo) => {
|
block: async (object, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
if (currentUser.id === args.id) return null
|
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})
|
MATCH(u:User {id: $currentUser.id})-[r:FOLLOWS]->(b:User {id: $args.id})
|
||||||
DELETE r
|
DELETE r
|
||||||
@ -81,8 +82,8 @@ export default {
|
|||||||
{ currentUser, args },
|
{ currentUser, args },
|
||||||
)
|
)
|
||||||
const [user, blockedUser] = await Promise.all([
|
const [user, blockedUser] = await Promise.all([
|
||||||
instance.find('User', currentUser.id),
|
neode.find('User', currentUser.id),
|
||||||
instance.find('User', args.id),
|
neode.find('User', args.id),
|
||||||
])
|
])
|
||||||
await user.relateTo(blockedUser, 'blocked')
|
await user.relateTo(blockedUser, 'blocked')
|
||||||
return blockedUser.toJson()
|
return blockedUser.toJson()
|
||||||
@ -90,45 +91,57 @@ export default {
|
|||||||
unblock: async (object, args, context, resolveInfo) => {
|
unblock: async (object, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
if (currentUser.id === args.id) return null
|
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})
|
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(b:User {id: $args.id})
|
||||||
DELETE r
|
DELETE r
|
||||||
`,
|
`,
|
||||||
{ currentUser, args },
|
{ currentUser, args },
|
||||||
)
|
)
|
||||||
const blockedUser = await instance.find('User', args.id)
|
const blockedUser = await neode.find('User', args.id)
|
||||||
return blockedUser.toJson()
|
return blockedUser.toJson()
|
||||||
},
|
},
|
||||||
UpdateUser: async (object, args, context, resolveInfo) => {
|
UpdateUser: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { termsAndConditionsAgreedVersion } = args
|
const { termsAndConditionsAgreedVersion } = params
|
||||||
if (termsAndConditionsAgreedVersion) {
|
if (termsAndConditionsAgreedVersion) {
|
||||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||||
throw new ForbiddenError('Invalid version format!')
|
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 {
|
try {
|
||||||
const user = await instance.find('User', args.id)
|
const [user] = await writeTxResultPromise
|
||||||
if (!user) return null
|
return user
|
||||||
await user.update({ ...args, updatedAt: new Date().toISOString() })
|
} catch (error) {
|
||||||
return user.toJson()
|
throw new UserInputError(error.message)
|
||||||
} catch (e) {
|
} finally {
|
||||||
throw new UserInputError(e.message)
|
session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteUser: async (object, params, context, resolveInfo) => {
|
DeleteUser: async (object, params, context, resolveInfo) => {
|
||||||
const { resource } = params
|
const { resource } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
|
||||||
let user
|
|
||||||
try {
|
try {
|
||||||
if (resource && resource.length) {
|
if (resource && resource.length) {
|
||||||
await Promise.all(
|
await session.writeTransaction(transaction => {
|
||||||
resource.map(async node => {
|
resource.map(node => {
|
||||||
await session.run(
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||||
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
||||||
@ -136,17 +149,18 @@ export default {
|
|||||||
SET resource.content = 'UNAVAILABLE'
|
SET resource.content = 'UNAVAILABLE'
|
||||||
SET resource.contentExcerpt = 'UNAVAILABLE'
|
SET resource.contentExcerpt = 'UNAVAILABLE'
|
||||||
SET comment.deleted = true
|
SET comment.deleted = true
|
||||||
RETURN author`,
|
RETURN author
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
userId: context.user.id,
|
userId: context.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}),
|
})
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// we cannot set slug to 'UNAVAILABE' because of unique constraints
|
const deleteUserTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
const transactionResult = await session.run(
|
const deleteUserTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (user:User {id: $userId})
|
MATCH (user:User {id: $userId})
|
||||||
SET user.deleted = true
|
SET user.deleted = true
|
||||||
@ -158,14 +172,18 @@ export default {
|
|||||||
WITH user
|
WITH user
|
||||||
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
|
||||||
DETACH DELETE socialMedia
|
DETACH DELETE socialMedia
|
||||||
RETURN user`,
|
RETURN user
|
||||||
|
`,
|
||||||
{ userId: context.user.id },
|
{ userId: context.user.id },
|
||||||
)
|
)
|
||||||
user = transactionResult.records.map(r => r.get('user').properties)[0]
|
log(deleteUserTransactionResponse)
|
||||||
|
return deleteUserTransactionResponse.records.map(record => record.get('user').properties)
|
||||||
|
})
|
||||||
|
const [user] = await deleteUserTxResultPromise
|
||||||
|
return user
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
return user
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
@ -173,7 +191,7 @@ export default {
|
|||||||
if (typeof parent.email !== 'undefined') return parent.email
|
if (typeof parent.email !== 'undefined') return parent.email
|
||||||
const { id } = parent
|
const { id } = parent
|
||||||
const statement = `MATCH(u:User {id: {id}})-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e`
|
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)
|
const [{ email }] = result.records.map(r => r.get('e').properties)
|
||||||
return email
|
return email
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { gql } from '../../helpers/jest'
|
import { gql } from '../../helpers/jest'
|
||||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
@ -68,6 +68,7 @@ describe('User', () => {
|
|||||||
it('is permitted', async () => {
|
it('is permitted', async () => {
|
||||||
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
|
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
|
||||||
data: { User: [{ name: 'Johnny' }] },
|
data: { User: [{ name: 'Johnny' }] },
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -90,8 +91,7 @@ describe('User', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('UpdateUser', () => {
|
describe('UpdateUser', () => {
|
||||||
let userParams
|
let userParams, variables
|
||||||
let variables
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
userParams = {
|
userParams = {
|
||||||
@ -111,16 +111,23 @@ describe('UpdateUser', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateUserMutation = gql`
|
const updateUserMutation = gql`
|
||||||
mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) {
|
mutation(
|
||||||
|
$id: ID!
|
||||||
|
$name: String
|
||||||
|
$termsAndConditionsAgreedVersion: String
|
||||||
|
$locationName: String
|
||||||
|
) {
|
||||||
UpdateUser(
|
UpdateUser(
|
||||||
id: $id
|
id: $id
|
||||||
name: $name
|
name: $name
|
||||||
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||||
|
locationName: $locationName
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
termsAndConditionsAgreedVersion
|
termsAndConditionsAgreedVersion
|
||||||
termsAndConditionsAgreedAt
|
termsAndConditionsAgreedAt
|
||||||
|
locationName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -152,7 +159,7 @@ describe('UpdateUser', () => {
|
|||||||
authenticatedUser = await user.toJson()
|
authenticatedUser = await user.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('name within specifications', async () => {
|
it('updates the name', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
data: {
|
data: {
|
||||||
UpdateUser: {
|
UpdateUser: {
|
||||||
@ -160,36 +167,13 @@ describe('UpdateUser', () => {
|
|||||||
name: 'John Doughnut',
|
name: 'John Doughnut',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
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', () => {
|
describe('given a new agreed version of terms and conditions', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' }
|
variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' }
|
||||||
@ -202,6 +186,7 @@ describe('UpdateUser', () => {
|
|||||||
termsAndConditionsAgreedAt: expect.any(String),
|
termsAndConditionsAgreedAt: expect.any(String),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
@ -222,6 +207,7 @@ describe('UpdateUser', () => {
|
|||||||
termsAndConditionsAgreedAt: null,
|
termsAndConditionsAgreedAt: null,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject(
|
||||||
@ -238,6 +224,14 @@ describe('UpdateUser', () => {
|
|||||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
||||||
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
|
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(
|
await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject(
|
||||||
expectedResponse,
|
expectedResponse,
|
||||||
@ -418,6 +413,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
@ -465,6 +461,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
@ -511,6 +508,7 @@ describe('DeleteUser', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
mutate({ mutation: deleteUserMutation, variables }),
|
mutate({ mutation: deleteUserMutation, variables }),
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
|
|||||||
import createServer from '../../../server'
|
import createServer from '../../../server'
|
||||||
import Factory from '../../../seed/factories'
|
import Factory from '../../../seed/factories'
|
||||||
import { gql } from '../../../helpers/jest'
|
import { gql } from '../../../helpers/jest'
|
||||||
import { neode, getDriver } from '../../../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../../../bootstrap/neo4j'
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = neode()
|
const neode = getNeode()
|
||||||
|
|
||||||
let currentUser
|
let currentUser
|
||||||
let blockedUser
|
let blockedUser
|
||||||
@ -20,7 +20,7 @@ beforeEach(() => {
|
|||||||
return {
|
return {
|
||||||
user: authenticatedUser,
|
user: authenticatedUser,
|
||||||
driver,
|
driver,
|
||||||
neode: instance,
|
neode,
|
||||||
cypherParams: {
|
cypherParams: {
|
||||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||||
},
|
},
|
||||||
@ -55,11 +55,11 @@ describe('blockedUsers', () => {
|
|||||||
|
|
||||||
describe('authenticated and given a blocked user', () => {
|
describe('authenticated and given a blocked user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUser = await instance.create('User', {
|
currentUser = await neode.create('User', {
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
})
|
})
|
||||||
blockedUser = await instance.create('User', {
|
blockedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Blocked User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
@ -113,7 +113,7 @@ describe('block', () => {
|
|||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUser = await instance.create('User', {
|
currentUser = await neode.create('User', {
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
})
|
})
|
||||||
@ -138,7 +138,7 @@ describe('block', () => {
|
|||||||
|
|
||||||
describe('given a to-be-blocked user', () => {
|
describe('given a to-be-blocked user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
blockedUser = await instance.create('User', {
|
blockedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Blocked User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
@ -181,11 +181,11 @@ describe('block', () => {
|
|||||||
let postQuery
|
let postQuery
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const post1 = await instance.create('Post', {
|
const post1 = await neode.create('Post', {
|
||||||
id: 'p12',
|
id: 'p12',
|
||||||
title: 'A post written by the current user',
|
title: 'A post written by the current user',
|
||||||
})
|
})
|
||||||
const post2 = await instance.create('Post', {
|
const post2 = await neode.create('Post', {
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
title: 'A post written by the blocked user',
|
title: 'A post written by the blocked user',
|
||||||
})
|
})
|
||||||
@ -323,7 +323,7 @@ describe('unblock', () => {
|
|||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUser = await instance.create('User', {
|
currentUser = await neode.create('User', {
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
})
|
})
|
||||||
@ -348,7 +348,7 @@ describe('unblock', () => {
|
|||||||
|
|
||||||
describe('given another user', () => {
|
describe('given another user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
blockedUser = await instance.create('User', {
|
blockedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Blocked User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getDriver, neode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
import createBadge from './badges.js'
|
import createBadge from './badges.js'
|
||||||
import createUser from './users.js'
|
import createUser from './users.js'
|
||||||
import createPost from './posts.js'
|
import createPost from './posts.js'
|
||||||
@ -29,17 +29,23 @@ const factories = {
|
|||||||
|
|
||||||
export const cleanDatabase = async (options = {}) => {
|
export const cleanDatabase = async (options = {}) => {
|
||||||
const { driver = getDriver() } = options
|
const { driver = getDriver() } = options
|
||||||
const cypher = 'MATCH (n) DETACH DELETE n'
|
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
try {
|
try {
|
||||||
return await session.run(cypher)
|
await session.writeTransaction(transaction => {
|
||||||
|
return transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (everything)
|
||||||
|
DETACH DELETE everything
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Factory(options = {}) {
|
export default function Factory(options = {}) {
|
||||||
const { neo4jDriver = getDriver(), neodeInstance = neode() } = options
|
const { neo4jDriver = getDriver(), neodeInstance = getNeode() } = options
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
neo4jDriver,
|
neo4jDriver,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import sample from 'lodash/sample'
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer from '../server'
|
import createServer from '../server'
|
||||||
import Factory from './factories'
|
import Factory from './factories'
|
||||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||||
import { gql } from '../helpers/jest'
|
import { gql } from '../helpers/jest'
|
||||||
|
|
||||||
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import helmet from 'helmet'
|
|||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import CONFIG, { requiredConfigs } from './config'
|
import CONFIG, { requiredConfigs } from './config'
|
||||||
import middleware from './middleware'
|
import middleware from './middleware'
|
||||||
import { neode as getNeode, getDriver } from './bootstrap/neo4j'
|
import { getNeode, getDriver } from './bootstrap/neo4j'
|
||||||
import decode from './jwt/decode'
|
import decode from './jwt/decode'
|
||||||
import schema from './schema'
|
import schema from './schema'
|
||||||
import webfinger from './activitypub/routes/webfinger'
|
import webfinger from './activitypub/routes/webfinger'
|
||||||
@ -38,6 +38,12 @@ const createServer = options => {
|
|||||||
schema: middleware(schema),
|
schema: middleware(schema),
|
||||||
debug: !!CONFIG.DEBUG,
|
debug: !!CONFIG.DEBUG,
|
||||||
tracing: !!CONFIG.DEBUG,
|
tracing: !!CONFIG.DEBUG,
|
||||||
|
formatError: error => {
|
||||||
|
if (error.message === 'ERROR_VALIDATION') {
|
||||||
|
return new Error(error.originalError.details.map(d => d.message))
|
||||||
|
}
|
||||||
|
return error
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const server = new ApolloServer(Object.assign({}, defaults, options))
|
const 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"
|
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc"
|
||||||
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
|
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
|
||||||
|
|
||||||
"@babel/cli@~7.7.4":
|
"@babel/cli@~7.7.5":
|
||||||
version "7.7.4"
|
version "7.7.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b"
|
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.5.tgz#25702cc65418efc06989af3727897b9f4c8690b6"
|
||||||
integrity sha512-O7mmzaWdm+VabWQmxuM8hqNrWGGihN83KfhPUzp2lAW4kzIMwBxujXkZbD4fMwKMYY9FXTbDvXsJqU+5XHXi4A==
|
integrity sha512-y2YrMGXM3NUyu1Myg0pxg+Lx6g8XhEyvLHYNRwTBV6fDek3H7Io6b7N/LXscLs4HWn4HxMdy7f2rM1rTMp2mFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "^4.0.1"
|
commander "^4.0.1"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.1.0"
|
||||||
@ -184,6 +184,18 @@
|
|||||||
"@babel/types" "^7.7.4"
|
"@babel/types" "^7.7.4"
|
||||||
lodash "^4.17.13"
|
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":
|
"@babel/helper-optimise-call-expression@^7.7.4":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz#034af31370d2995242aa4df402c3b7794b2dcdf2"
|
||||||
@ -502,21 +514,21 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-amd@^7.7.4":
|
"@babel/plugin-transform-modules-amd@^7.7.5":
|
||||||
version "7.7.4"
|
version "7.7.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c"
|
||||||
integrity sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ==
|
integrity sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-transforms" "^7.7.4"
|
"@babel/helper-module-transforms" "^7.7.5"
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
babel-plugin-dynamic-import-node "^2.3.0"
|
babel-plugin-dynamic-import-node "^2.3.0"
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-commonjs@^7.7.4":
|
"@babel/plugin-transform-modules-commonjs@^7.7.5":
|
||||||
version "7.7.4"
|
version "7.7.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345"
|
||||||
integrity sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA==
|
integrity sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-transforms" "^7.7.4"
|
"@babel/helper-module-transforms" "^7.7.5"
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/helper-simple-access" "^7.7.4"
|
"@babel/helper-simple-access" "^7.7.4"
|
||||||
babel-plugin-dynamic-import-node "^2.3.0"
|
babel-plugin-dynamic-import-node "^2.3.0"
|
||||||
@ -576,10 +588,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/plugin-transform-regenerator@^7.7.4":
|
"@babel/plugin-transform-regenerator@^7.7.5":
|
||||||
version "7.7.4"
|
version "7.7.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9"
|
||||||
integrity sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw==
|
integrity sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-transform "^0.14.0"
|
regenerator-transform "^0.14.0"
|
||||||
|
|
||||||
@ -635,10 +647,10 @@
|
|||||||
"@babel/helper-create-regexp-features-plugin" "^7.7.4"
|
"@babel/helper-create-regexp-features-plugin" "^7.7.4"
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/preset-env@~7.7.4":
|
"@babel/preset-env@~7.7.6":
|
||||||
version "7.7.4"
|
version "7.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8"
|
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2"
|
||||||
integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==
|
integrity sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-imports" "^7.7.4"
|
"@babel/helper-module-imports" "^7.7.4"
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
@ -668,8 +680,8 @@
|
|||||||
"@babel/plugin-transform-function-name" "^7.7.4"
|
"@babel/plugin-transform-function-name" "^7.7.4"
|
||||||
"@babel/plugin-transform-literals" "^7.7.4"
|
"@babel/plugin-transform-literals" "^7.7.4"
|
||||||
"@babel/plugin-transform-member-expression-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-amd" "^7.7.5"
|
||||||
"@babel/plugin-transform-modules-commonjs" "^7.7.4"
|
"@babel/plugin-transform-modules-commonjs" "^7.7.5"
|
||||||
"@babel/plugin-transform-modules-systemjs" "^7.7.4"
|
"@babel/plugin-transform-modules-systemjs" "^7.7.4"
|
||||||
"@babel/plugin-transform-modules-umd" "^7.7.4"
|
"@babel/plugin-transform-modules-umd" "^7.7.4"
|
||||||
"@babel/plugin-transform-named-capturing-groups-regex" "^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-object-super" "^7.7.4"
|
||||||
"@babel/plugin-transform-parameters" "^7.7.4"
|
"@babel/plugin-transform-parameters" "^7.7.4"
|
||||||
"@babel/plugin-transform-property-literals" "^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-reserved-words" "^7.7.4"
|
||||||
"@babel/plugin-transform-shorthand-properties" "^7.7.4"
|
"@babel/plugin-transform-shorthand-properties" "^7.7.4"
|
||||||
"@babel/plugin-transform-spread" "^7.7.4"
|
"@babel/plugin-transform-spread" "^7.7.4"
|
||||||
@ -687,7 +699,7 @@
|
|||||||
"@babel/plugin-transform-unicode-regex" "^7.7.4"
|
"@babel/plugin-transform-unicode-regex" "^7.7.4"
|
||||||
"@babel/types" "^7.7.4"
|
"@babel/types" "^7.7.4"
|
||||||
browserslist "^4.6.0"
|
browserslist "^4.6.0"
|
||||||
core-js-compat "^3.1.1"
|
core-js-compat "^3.4.7"
|
||||||
invariant "^2.2.2"
|
invariant "^2.2.2"
|
||||||
js-levenshtein "^1.1.3"
|
js-levenshtein "^1.1.3"
|
||||||
semver "^5.5.0"
|
semver "^5.5.0"
|
||||||
@ -1022,10 +1034,10 @@
|
|||||||
url-regex "~4.1.1"
|
url-regex "~4.1.1"
|
||||||
video-extensions "~1.1.0"
|
video-extensions "~1.1.0"
|
||||||
|
|
||||||
"@metascraper/helpers@^5.8.7":
|
"@metascraper/helpers@^5.8.10", "@metascraper/helpers@^5.8.7":
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.8.7.tgz#b05f83f2a90001f7753c18a8b1bb978bd7c2f9d9"
|
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.8.10.tgz#efaae1d57afca6db1f0846852fe88d1608601f13"
|
||||||
integrity sha512-gDErMAA3d1CdkGxvAG4cDi7D2+fReZpD6lzYNJ/gsq45U3Pdz7ltsAvbp4amK92bGKYYPZtnUq85Wrr+Q+e06Q==
|
integrity sha512-o7vrlNC+wzfArTkQcQfHKT4iHUYEQYs6hoORTWN7A1dj5v8P1wl5oOs0oAc7MNGJ3nWnex3/bq/5SUWV301Arg==
|
||||||
dependencies:
|
dependencies:
|
||||||
audio-extensions "0.0.0"
|
audio-extensions "0.0.0"
|
||||||
chrono-node "~1.3.11"
|
chrono-node "~1.3.11"
|
||||||
@ -1101,56 +1113,56 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||||
|
|
||||||
"@sentry/apm@5.10.1":
|
"@sentry/apm@5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.10.1.tgz#2ec20cef0f87f9f638ff78dd5092e1e9d36c4b7d"
|
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.10.2.tgz#41a401b3964b68514439f8a595b12c6fd05ab21a"
|
||||||
integrity sha512-VSFK8giRG5/lN0YSaOw8+Cru/8MVevmoHZ5JC9iDIt0H6sGTUjOBKIqTZ0eq2Y99Vn0N9dkxjeT0rOIvsrg0gA==
|
integrity sha512-rPeAFsD/6ontvs7bsuHh+XAg1ohWo04ms08SNWqEvLRQJx7WfiWnjziyC0S3dXIYZDGdhruSsqQJPJN8r6Aj5g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/hub" "5.10.1"
|
"@sentry/hub" "5.10.2"
|
||||||
"@sentry/minimal" "5.10.1"
|
"@sentry/minimal" "5.10.2"
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
"@sentry/utils" "5.10.1"
|
"@sentry/utils" "5.10.2"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@sentry/core@5.10.1":
|
"@sentry/core@5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.1.tgz#356551f111d4df38e60852607cc8cde0ed8ccc76"
|
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.2.tgz#1cb64489e6f8363c3249415b49d3f1289814825f"
|
||||||
integrity sha512-MbiasA/cuMB0+9zVBGi5YLWRj7CdFQJOM29Vp8rm3xMaQDH0KHarpny1gOgMiLu/O/r8itjiZwKu+9pxOWGbeA==
|
integrity sha512-sKVeFH3v8K8xw2vM5MKMnnyAAwih+JSE3pbNL0CcCCA+/SwX+3jeAo2BhgXev2SAR/TjWW+wmeC9TdIW7KyYbg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/hub" "5.10.1"
|
"@sentry/hub" "5.10.2"
|
||||||
"@sentry/minimal" "5.10.1"
|
"@sentry/minimal" "5.10.2"
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
"@sentry/utils" "5.10.1"
|
"@sentry/utils" "5.10.2"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@sentry/hub@5.10.1":
|
"@sentry/hub@5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.1.tgz#3be4a0705cd0cd074be0aab0dc418ecb72885989"
|
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.2.tgz#25d9f36b8f7c5cb65cf486737fa61dc9bf69b7e3"
|
||||||
integrity sha512-g+P+0cj6vKdf6Ct4S47MxHwSMIjtIadOwBhb4Lqwij5YPtQ4LpVr10peKbE+FMMvCNQSvQnJEhTDko+AE7AoYw==
|
integrity sha512-hSlZIiu3hcR/I5yEhlpN9C0nip+U7hiRzRzUQaBiHO4YG4TC58NqnOPR89D/ekiuHIXzFpjW9OQmqtAMRoSUYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
"@sentry/utils" "5.10.1"
|
"@sentry/utils" "5.10.2"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@sentry/minimal@5.10.1":
|
"@sentry/minimal@5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.1.tgz#37104f81ef3b333c0f9e77ac94bfed348070dea3"
|
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.2.tgz#267c2f3aa6877a0fe7a86971942e83f3ee616580"
|
||||||
integrity sha512-oKrLvKaah0xGVIYbS1I7dVbo73aWssfiT2ypl9DYt8MAFiwfiiXz68FlG4z9dPZ2jSz9Jm2SAYHFaYLvU26TBQ==
|
integrity sha512-GalixiM9sckYfompH5HHTp9XT2BcjawBkcl1DMEKUBEi37+kUq0bivOBmnN1G/I4/wWOUdnAI/kagDWaWpbZPg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/hub" "5.10.1"
|
"@sentry/hub" "5.10.2"
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@sentry/node@^5.10.1":
|
"@sentry/node@^5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.10.1.tgz#cafbf3b0918c98fb9f99803ffe50056e32194bef"
|
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.10.2.tgz#1f5d6deefb2c1549ddb542c10952cccf5f9a4ac2"
|
||||||
integrity sha512-kard7OXQDvYqmQD93bOkYhznqrbsiFNZ6+dIi13eo/kc2Au+v1Th1mGvr9JDRE/X07z6vJMYMiorKd351G3p/A==
|
integrity sha512-1ib1hAhVtmfXOThpcCfR4S6wFopd6lHqgOMrAUPo9saHy8zseZPRC7iTWGoSPy2RMwjrURAk54VvFnLe7G+PdQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/apm" "5.10.1"
|
"@sentry/apm" "5.10.2"
|
||||||
"@sentry/core" "5.10.1"
|
"@sentry/core" "5.10.2"
|
||||||
"@sentry/hub" "5.10.1"
|
"@sentry/hub" "5.10.2"
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
"@sentry/utils" "5.10.1"
|
"@sentry/utils" "5.10.2"
|
||||||
cookie "^0.3.1"
|
cookie "^0.3.1"
|
||||||
https-proxy-agent "^3.0.0"
|
https-proxy-agent "^3.0.0"
|
||||||
lru_map "^0.3.3"
|
lru_map "^0.3.3"
|
||||||
@ -1161,10 +1173,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.10.0.tgz#4f0ba31b6e4d5371112c38279f11f66c73b43746"
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.10.0.tgz#4f0ba31b6e4d5371112c38279f11f66c73b43746"
|
||||||
integrity sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==
|
integrity sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==
|
||||||
|
|
||||||
"@sentry/utils@5.10.1":
|
"@sentry/utils@5.10.2":
|
||||||
version "5.10.1"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.1.tgz#eeb3ede85a9b5b1cd1aad7e3157052bee0d42551"
|
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.2.tgz#261f575079d30aaf604e59f5f4de0aa21db22252"
|
||||||
integrity sha512-zdv03sINfJ8QXSHP49845qhkbdNUrX20AagUY+Arq2zxmM4XxnRVA7dtWDkyy55bTt0ziRuSikBxR3266t8mDg==
|
integrity sha512-UcbbaFpYrGSV448lQ16Cr+W/MPuKUflQQUdrMCt5vgaf5+M7kpozlcji4GGGZUCXIA7oRP93ABoXj55s1OM9zw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
@ -1602,37 +1614,37 @@ apollo-cache-control@^0.8.8:
|
|||||||
apollo-server-env "^2.4.3"
|
apollo-server-env "^2.4.3"
|
||||||
graphql-extensions "^0.10.7"
|
graphql-extensions "^0.10.7"
|
||||||
|
|
||||||
apollo-cache-inmemory@~1.6.3:
|
apollo-cache-inmemory@~1.6.5:
|
||||||
version "1.6.3"
|
version "1.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
|
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a"
|
||||||
integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==
|
integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-cache "^1.3.2"
|
apollo-cache "^1.3.4"
|
||||||
apollo-utilities "^1.3.2"
|
apollo-utilities "^1.3.3"
|
||||||
optimism "^0.10.0"
|
optimism "^0.10.0"
|
||||||
ts-invariant "^0.4.0"
|
ts-invariant "^0.4.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
apollo-cache@1.3.4, apollo-cache@^1.3.4:
|
||||||
version "1.3.2"
|
version "1.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.2.tgz#df4dce56240d6c95c613510d7e409f7214e6d26a"
|
resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42"
|
||||||
integrity sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg==
|
integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA==
|
||||||
dependencies:
|
dependencies:
|
||||||
apollo-utilities "^1.3.2"
|
apollo-utilities "^1.3.3"
|
||||||
tslib "^1.9.3"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
apollo-client@~2.6.4:
|
apollo-client@~2.6.8:
|
||||||
version "2.6.4"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.4.tgz#872c32927263a0d34655c5ef8a8949fbb20b6140"
|
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537"
|
||||||
integrity sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==
|
integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/zen-observable" "^0.8.0"
|
"@types/zen-observable" "^0.8.0"
|
||||||
apollo-cache "1.3.2"
|
apollo-cache "1.3.4"
|
||||||
apollo-link "^1.0.0"
|
apollo-link "^1.0.0"
|
||||||
apollo-utilities "1.3.2"
|
apollo-utilities "1.3.3"
|
||||||
symbol-observable "^1.0.2"
|
symbol-observable "^1.0.2"
|
||||||
ts-invariant "^0.4.0"
|
ts-invariant "^0.4.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.10.0"
|
||||||
zen-observable "^0.8.0"
|
zen-observable "^0.8.0"
|
||||||
|
|
||||||
apollo-datasource@^0.6.3:
|
apollo-datasource@^0.6.3:
|
||||||
@ -1835,15 +1847,15 @@ apollo-tracing@^0.8.8:
|
|||||||
apollo-server-env "^2.4.3"
|
apollo-server-env "^2.4.3"
|
||||||
graphql-extensions "^0.10.7"
|
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:
|
apollo-utilities@1.3.3, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3:
|
||||||
version "1.3.2"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c"
|
||||||
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
|
integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@wry/equality" "^0.1.2"
|
"@wry/equality" "^0.1.2"
|
||||||
fast-json-stable-stringify "^2.0.0"
|
fast-json-stable-stringify "^2.0.0"
|
||||||
ts-invariant "^0.4.0"
|
ts-invariant "^0.4.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
aproba@^1.0.3:
|
aproba@^1.0.3:
|
||||||
version "1.2.0"
|
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"
|
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
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:
|
arrify@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
|
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
|
||||||
@ -2233,14 +2254,14 @@ browser-resolve@^1.11.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve "1.1.7"
|
resolve "1.1.7"
|
||||||
|
|
||||||
browserslist@^4.6.0, browserslist@^4.6.6:
|
browserslist@^4.6.0, browserslist@^4.8.2:
|
||||||
version "4.6.6"
|
version "4.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289"
|
||||||
integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
|
integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite "^1.0.30000984"
|
caniuse-lite "^1.0.30001015"
|
||||||
electron-to-chromium "^1.3.191"
|
electron-to-chromium "^1.3.322"
|
||||||
node-releases "^1.1.25"
|
node-releases "^1.1.42"
|
||||||
|
|
||||||
bser@^2.0.0:
|
bser@^2.0.0:
|
||||||
version "2.1.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"
|
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
|
||||||
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
|
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
|
||||||
|
|
||||||
caniuse-lite@^1.0.30000984:
|
caniuse-lite@^1.0.30001015:
|
||||||
version "1.0.30000989"
|
version "1.0.30001015"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0"
|
||||||
integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
|
integrity sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||||
|
|
||||||
core-js-compat@^3.1.1:
|
core-js-compat@^3.4.7:
|
||||||
version "3.2.1"
|
version "3.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.2.1.tgz#0cbdbc2e386e8e00d3b85dc81c848effec5b8150"
|
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.8.tgz#f72e6a4ed76437ea710928f44615f926a81607d5"
|
||||||
integrity sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==
|
integrity sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist "^4.6.6"
|
browserslist "^4.8.2"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
core-js@^2.4.0, core-js@^2.6.5:
|
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"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.8.1.tgz#2109362ccb6c87c3ca011e9e31f702bc09e4123b"
|
||||||
integrity sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg==
|
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"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
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"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
electron-to-chromium@^1.3.191:
|
electron-to-chromium@^1.3.322:
|
||||||
version "1.3.237"
|
version "1.3.322"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.237.tgz#39c5d1da59d6fd16ff705b97e772bb3b5dfda7e4"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8"
|
||||||
integrity sha512-SPAFjDr/7iiVK2kgTluwxela6eaWjjFkS9rO/iYpB/KGXgccUom5YC7OIf19c8m8GGptWxLU0Em8xM64A/N7Fg==
|
integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==
|
||||||
|
|
||||||
emoji-regex@^7.0.1:
|
emoji-regex@^7.0.1:
|
||||||
version "7.0.3"
|
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"
|
is-regex "^1.0.4"
|
||||||
object-keys "^1.0.12"
|
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:
|
es-to-primitive@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
|
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-date-object "^1.0.1"
|
||||||
is-symbol "^1.0.2"
|
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:
|
es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.46:
|
||||||
version "0.10.50"
|
version "0.10.50"
|
||||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
|
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"
|
debug "^2.6.9"
|
||||||
resolve "^1.5.0"
|
resolve "^1.5.0"
|
||||||
|
|
||||||
eslint-module-utils@^2.4.0:
|
eslint-module-utils@^2.4.1:
|
||||||
version "2.4.1"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c"
|
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c"
|
||||||
integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==
|
integrity sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.6.8"
|
debug "^2.6.9"
|
||||||
pkg-dir "^2.0.0"
|
pkg-dir "^2.0.0"
|
||||||
|
|
||||||
eslint-plugin-es@^2.0.0:
|
eslint-plugin-es@^2.0.0:
|
||||||
@ -3329,22 +3375,23 @@ eslint-plugin-es@^2.0.0:
|
|||||||
eslint-utils "^1.4.2"
|
eslint-utils "^1.4.2"
|
||||||
regexpp "^3.0.0"
|
regexpp "^3.0.0"
|
||||||
|
|
||||||
eslint-plugin-import@~2.18.2:
|
eslint-plugin-import@~2.19.1:
|
||||||
version "2.18.2"
|
version "2.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz#5654e10b7839d064dd0d46cd1b88ec2133a11448"
|
||||||
integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
|
integrity sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw==
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes "^3.0.3"
|
array-includes "^3.0.3"
|
||||||
|
array.prototype.flat "^1.2.1"
|
||||||
contains-path "^0.1.0"
|
contains-path "^0.1.0"
|
||||||
debug "^2.6.9"
|
debug "^2.6.9"
|
||||||
doctrine "1.5.0"
|
doctrine "1.5.0"
|
||||||
eslint-import-resolver-node "^0.3.2"
|
eslint-import-resolver-node "^0.3.2"
|
||||||
eslint-module-utils "^2.4.0"
|
eslint-module-utils "^2.4.1"
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
object.values "^1.1.0"
|
object.values "^1.1.0"
|
||||||
read-pkg-up "^2.0.0"
|
read-pkg-up "^2.0.0"
|
||||||
resolve "^1.11.0"
|
resolve "^1.12.0"
|
||||||
|
|
||||||
eslint-plugin-jest@~23.1.1:
|
eslint-plugin-jest@~23.1.1:
|
||||||
version "23.1.1"
|
version "23.1.1"
|
||||||
@ -3365,10 +3412,10 @@ eslint-plugin-node@~10.0.0:
|
|||||||
resolve "^1.10.1"
|
resolve "^1.10.1"
|
||||||
semver "^6.1.0"
|
semver "^6.1.0"
|
||||||
|
|
||||||
eslint-plugin-prettier@~3.1.1:
|
eslint-plugin-prettier@~3.1.2:
|
||||||
version "3.1.1"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
|
||||||
integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA==
|
integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier-linter-helpers "^1.0.0"
|
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"
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
|
||||||
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
|
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:
|
has-unicode@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
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"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||||
|
|
||||||
metascraper-audio@^5.8.7:
|
metascraper-audio@^5.8.10:
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.8.7.tgz#ce27b1f4056c1d1cbaa2cec0e819c3704f38fff4"
|
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.8.10.tgz#bc7bc0471ee178ab747baec4fb9bf7443078980d"
|
||||||
integrity sha512-ew9KZKOIl3u0500j7qIR/ZNiVtSohuyyiIWSxJVEeeguEOwAhMpOrpYAEkvKRo5CB89F2PNBIsXJIzMC4BWFrw==
|
integrity sha512-uR4PCG7mxz7GLZ3I3x83sTCAaD/+MMTSf5rtP+shfdGJCm6h3mNmUpZm6hlBunmBx/PpDpwdI34rkl2A8SUjnQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
|
|
||||||
metascraper-author@^5.8.7:
|
metascraper-author@^5.8.7:
|
||||||
version "5.8.7"
|
version "5.8.7"
|
||||||
@ -5787,19 +5839,19 @@ metascraper-date@^5.8.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.7"
|
||||||
|
|
||||||
metascraper-description@^5.8.7:
|
metascraper-description@^5.8.10:
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.8.7.tgz#e85ce218daf33b74813b1523ad7dc7dc3fb128af"
|
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.8.10.tgz#1b69f59fa76263fcd2c15f8ce73052b81900177a"
|
||||||
integrity sha512-KOv5gnQVvGF1CgpUczu7KJm76rWJ7SH5UFcqFST60hRNgR9xy0y3aHbVDOhZkjNN4UKqnxMF6XTS/WaQxCK/AA==
|
integrity sha512-0stYkl5OPpM0yM6Dl3WcXxLjl2gY5k77E4seeHOqHAUx1EKXNgrSrtO0I3PX9p6vcxP+WBtK6zlqHYU4qAMlSA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
|
|
||||||
metascraper-image@^5.8.7:
|
metascraper-image@^5.8.10:
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.8.7.tgz#d24697c5b5a6ba688948c48fadcb5fffeb6c703d"
|
resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.8.10.tgz#fe21811ca88eef13e64812462fb5a21ee48933dc"
|
||||||
integrity sha512-OMK+PFnHeavCSuEJY5tFkG5tdl/luYmPys7PKkJIwC8A8q5qoAC0InIUu+c0SDrdf4nzOj083DZTp32YQxYF5A==
|
integrity sha512-WOPnTupaDEl58iZp0M6kFlUcRSRQFSPWATPUi3AeW31VJM2sepxmJlqc5qVFTen/Lm+kI23firrvEg5N8tFUVA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
|
|
||||||
metascraper-lang-detector@^4.10.2:
|
metascraper-lang-detector@^4.10.2:
|
||||||
version "4.10.2"
|
version "4.10.2"
|
||||||
@ -5810,19 +5862,19 @@ metascraper-lang-detector@^4.10.2:
|
|||||||
franc "~4.0.0"
|
franc "~4.0.0"
|
||||||
iso-639-3 "~1.1.0"
|
iso-639-3 "~1.1.0"
|
||||||
|
|
||||||
metascraper-lang@^5.8.9:
|
metascraper-lang@^5.8.10:
|
||||||
version "5.8.9"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.8.9.tgz#589bac0fdc523b5b6e6317a7b6295474eedfb872"
|
resolved "https://registry.yarnpkg.com/metascraper-lang/-/metascraper-lang-5.8.10.tgz#b8827282dea500b68e49ebbe8b0081fb6b6584d5"
|
||||||
integrity sha512-VMiU+T9LFsra/bBc0w0+fw6lk8Snb/ULoIvHUF0+5wvkv4KzQicc0z1lTAL/28Et2Xa+R5Km5A9Ts7LYuQRqVw==
|
integrity sha512-qydko4UkLGqTimKzT+AkcIaXOo7/GkHGtclGiLae80lHeKzI5NG7kYN4eMv1r4BfBkcluSNeJ/P532T6ZD2Y1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
|
|
||||||
metascraper-logo@^5.8.7:
|
metascraper-logo@^5.8.10:
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.8.7.tgz#5efb7e6c5f91ccad812e2d9ec3facfef179f40b6"
|
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.8.10.tgz#8e0dc0296d71db03307584ecdb57cd3fcbad1d4b"
|
||||||
integrity sha512-QudGVJBBeXLWU54Xw2PmnsTf+qPUnbyYaOl4aFLg2wkLLza1GbuvOYGMiH9Y8k0WcRoesi9sQk+P0a/611blew==
|
integrity sha512-l5LkzZcVzrKclzf3JGx2cnCtPI/8Rf+EQV/SfXUqz7FUwgfT3uzRw9wBbqP25056ukh6aOuywGClTdnEu2PJcw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
|
|
||||||
metascraper-publisher@^5.8.7:
|
metascraper-publisher@^5.8.7:
|
||||||
version "5.8.7"
|
version "5.8.7"
|
||||||
@ -5831,20 +5883,20 @@ metascraper-publisher@^5.8.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.7"
|
||||||
|
|
||||||
metascraper-soundcloud@^5.8.9:
|
metascraper-soundcloud@^5.8.10:
|
||||||
version "5.8.9"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.8.9.tgz#5d02538078114c5ab25c46df4afc3f45a94b3d7c"
|
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.8.10.tgz#c281a35e2e7289006bd304dfb4074f01451e7f26"
|
||||||
integrity sha512-0otAe2E4N/KN2UqopJAM9NFZfSMyll2Q0XKhicfV/d+6Q1ERT7LWA/vwhBmxFwQzzX2mxZ8JFKeXUf6OZqEvVg==
|
integrity sha512-IBGGBFrzRiS1bTyR9+eJwv+fPvC8KoggpAZnGPABep4ZhfajblI3B+8U1kIXHMaFR4b1BaD4d+tWh3gNLZCjwQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
tldts "~5.6.2"
|
tldts "~5.6.2"
|
||||||
|
|
||||||
metascraper-title@^5.8.7:
|
metascraper-title@^5.8.10:
|
||||||
version "5.8.7"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.8.7.tgz#aecbbd9515bd74d2aeafa587c83447d926508ba0"
|
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.8.10.tgz#c25dc8e8ad7073c18c8d25db0b855f62d3d986bd"
|
||||||
integrity sha512-u+5KeJbsFKpi+pMnG71Gd49OLDQpkjiGIRTddhCZQhb45qHoTlGKN1nZuQ8nqJI6+ARWicFqtquomkaRXfBEnw==
|
integrity sha512-CauBJmLYtS+AZ9KJfnfJHp/tzUTo9yup56P/7aaOBcfVA5PWg3xdI1lVXJegmiTsBCyDEzWRVJ41f/ZlMjbAsg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
lodash "~4.17.15"
|
lodash "~4.17.15"
|
||||||
|
|
||||||
metascraper-url@^5.8.7:
|
metascraper-url@^5.8.7:
|
||||||
@ -5854,20 +5906,20 @@ metascraper-url@^5.8.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.7"
|
||||||
|
|
||||||
metascraper-video@^5.8.9:
|
metascraper-video@^5.8.10:
|
||||||
version "5.8.9"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.9.tgz#23c0fe71fae5088bc8e11bfa537eff80658aa6d9"
|
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.10.tgz#c43bdc3d4dc7ff97b94d45e0050fb50091da27be"
|
||||||
integrity sha512-xaimkGz1Txsd9qHUN2U5HyFMP8tkrb5LuW8bCo+0kdTu5c00HGurvs0/BpWrTW/CzUQBNl/uEybeDXm8J++03g==
|
integrity sha512-ofO7OLt73iMZM6IkA3iHtD1EzbEeiTYJK/xKBp+Awyl/dLUWKfsFjOAjkz9XDzLANRT+7+rwzqQmc+a2/rBVVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
lodash "~4.17.15"
|
lodash "~4.17.15"
|
||||||
|
|
||||||
metascraper-youtube@^5.8.9:
|
metascraper-youtube@^5.8.10:
|
||||||
version "5.8.9"
|
version "5.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.8.9.tgz#595f5e384e0db519378ca2023bd8aa6603866c9d"
|
resolved "https://registry.yarnpkg.com/metascraper-youtube/-/metascraper-youtube-5.8.10.tgz#c2b84b9faf8d4bd326a0a048e61cdbeefc7263ab"
|
||||||
integrity sha512-Zuew1tLSC14ceL9ZaNvlQ4GmFopbYDalr8gL+Ofo4ha4jKyX58VaPQtmIgASAJv/jlOXd9zCwEdhNw8/YyZZWw==
|
integrity sha512-2QLqIqc8FWGJmGEwvoWDdEZnSCLg5lzH/3qZm0P9joFGA6WWrfpaONCVW4M72xfVHv/WwEblKZERzlbJNEhGVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.8.7"
|
"@metascraper/helpers" "^5.8.10"
|
||||||
get-video-id "~3.1.4"
|
get-video-id "~3.1.4"
|
||||||
is-reachable "~4.0.0"
|
is-reachable "~4.0.0"
|
||||||
p-locate "~4.1.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"
|
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
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"
|
version "1.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49"
|
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.6.tgz#eccb135a71eba9048c68717444593a6424cffc49"
|
||||||
integrity sha512-6c3ALO3vYDfUqNoCy8OFzq+fQ7q/ab3LCuJrmm8P04M7RmyRCCnUtJ8IzSTGbiZvyhcehGK+azNDAEJhxPV/hA==
|
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"
|
text-encoding-utf-8 "^1.0.2"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
neo4j-graphql-js@^2.10.0:
|
neo4j-graphql-js@^2.10.2:
|
||||||
version "2.10.0"
|
version "2.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.10.0.tgz#4298793756d839dedb98bc3e50a2bd40a311874d"
|
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.10.2.tgz#e67d1aab6441b28f276adf0f6d655720983b9b84"
|
||||||
integrity sha512-jRdIyw+DHg9gfB6pWKb1ZHMR9rXIl7qf51efjUHIRHRbVR3RCcw1cKyONkq4LE8v2bHc7QDrKwJs+GQ1SRxDug==
|
integrity sha512-CgtKEgrWgSJBjuKQ5CEPt4tcG1z14oAB3UWQjX8scDlUag0iWofgzpPlrc3brn+RitfeEc3FuMSru8E9dVDJPg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.5.5"
|
"@babel/runtime" "^7.5.5"
|
||||||
"@babel/runtime-corejs2" "^7.5.5"
|
"@babel/runtime-corejs2" "^7.5.5"
|
||||||
@ -6122,14 +6174,14 @@ neo4j-graphql-js@^2.10.0:
|
|||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
neo4j-driver "^1.7.3"
|
neo4j-driver "^1.7.3"
|
||||||
|
|
||||||
neode@^0.3.3:
|
neode@^0.3.6:
|
||||||
version "0.3.3"
|
version "0.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.3.tgz#a539830cce6f6e4825462f6cb03f2969a0003f1b"
|
resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.6.tgz#7daf791eff6d170e52c338ea2e5cca6fdc6bfbe3"
|
||||||
integrity sha512-pArHG1hD2kVwrzLlz6B1+IgdOJRQj/BgR6KzH6DlVzSA6geoZRe68fbpvmOJtzyPU7iuUYxXVk87PpPM1A7dlg==
|
integrity sha512-jCskCPobtHpsIIYQD72h5lRjMJEX70KwIeqgpt1VOLI+d1zJZvUlDkcOKgarAW0fmwtHIrPOP6mLPe5G/ZG9+g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^15.1.0"
|
"@hapi/joi" "^15.1.0"
|
||||||
dotenv "^4.0.0"
|
dotenv "^4.0.0"
|
||||||
neo4j-driver "^1.7.5"
|
neo4j-driver "^1.7.6"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
next-tick@^1.0.0:
|
next-tick@^1.0.0:
|
||||||
@ -6204,12 +6256,12 @@ node-pre-gyp@^0.12.0:
|
|||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
tar "^4"
|
tar "^4"
|
||||||
|
|
||||||
node-releases@^1.1.25:
|
node-releases@^1.1.42:
|
||||||
version "1.1.28"
|
version "1.1.42"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.28.tgz#503c3c70d0e4732b84e7aaa2925fbdde10482d4a"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7"
|
||||||
integrity sha512-AQw4emh6iSXnCpDiFe0phYcThiccmkNWMZnFZ+lDJjAP8J0m2fVd59duvUUyuTirQOhIAajTFkzG6FHCLBO59g==
|
integrity sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
nodemailer-html-to-text@^3.1.0:
|
nodemailer-html-to-text@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
@ -6218,15 +6270,15 @@ nodemailer-html-to-text@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
html-to-text "^5.1.1"
|
html-to-text "^5.1.1"
|
||||||
|
|
||||||
nodemailer@^6.4.1:
|
nodemailer@^6.4.2:
|
||||||
version "6.4.1"
|
version "6.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.1.tgz#f70b40355b7b08f1f80344b353970a4f8f664370"
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.2.tgz#7147550e32cdc37453380ab78d2074533966090a"
|
||||||
integrity sha512-mSQAzMim8XIC1DemK9TifDTIgASfoJEllG5aC1mEtZeZ+FQyrSOdGBRth6JRA1ERzHQCET3QHVSd9Kc6mh356g==
|
integrity sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ==
|
||||||
|
|
||||||
nodemon@~2.0.1:
|
nodemon@~2.0.2:
|
||||||
version "2.0.1"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.1.tgz#cec436f8153ad5d3e6c27c304849a06cabea71cc"
|
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0"
|
||||||
integrity sha512-UC6FVhNLXjbbV4UzaXA3wUdbEkUZzLGgMGzmxvWAex5nzib/jhcSHVFlQODdbuUHq8SnnZ4/EABBAbC3RplvPg==
|
integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar "^3.2.2"
|
chokidar "^3.2.2"
|
||||||
debug "^3.2.6"
|
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"
|
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.0.tgz#7c4cc341eb8b53367312a7c546142f00c9e0ea20"
|
||||||
integrity sha512-I7zGBH0rDKwVGeGZpZoFaDhIwvJa3l1CZE+8VchylXbInNiCj7sxxea9P5dTM4ftKR5//nrqxrdeGSTWL2VpBA==
|
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"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
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"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||||
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
|
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"
|
version "1.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
|
||||||
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
|
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
|
||||||
@ -7452,6 +7509,11 @@ serve-static@1.14.1:
|
|||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||||
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
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:
|
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -7788,6 +7850,22 @@ string.prototype.padend@^3.0.0:
|
|||||||
es-abstract "^1.4.3"
|
es-abstract "^1.4.3"
|
||||||
function-bind "^1.0.2"
|
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:
|
string_decoder@^1.1.1:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||||
@ -8172,7 +8250,7 @@ ts-invariant@^0.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.3"
|
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"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
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
|
among other things. You can copy our template configuration and change the new
|
||||||
file according to your needs.
|
file according to your needs.
|
||||||
|
|
||||||
Make sure you are at the root level of the project. Then:
|
To start the services that are required for cypress testing, run:
|
||||||
```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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# in the top level folder Human-Connection/
|
# in the top level folder Human-Connection/
|
||||||
|
|||||||
@ -3,6 +3,14 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps";
|
|||||||
const narratorAvatar =
|
const narratorAvatar =
|
||||||
"https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg";
|
"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 => {
|
Then("I click on the {string} button", text => {
|
||||||
cy.get("button")
|
cy.get("button")
|
||||||
.contains(text)
|
.contains(text)
|
||||||
@ -23,6 +31,16 @@ Then("I should see my comment", () => {
|
|||||||
.should("contain", "today at");
|
.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", () => {
|
Then("the editor should be cleared", () => {
|
||||||
cy.get(".ProseMirror p").should("have.class", "is-empty");
|
cy.get(".ProseMirror p").should("have.class", "is-empty");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||||
import { VERSION } from '../../constants/terms-and-conditions-version.js'
|
import { VERSION } from '../../constants/terms-and-conditions-version.js'
|
||||||
|
import { gql } from '../../../backend/src/helpers/jest'
|
||||||
|
|
||||||
/* global cy */
|
/* global cy */
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ Given('somebody reported the following posts:', table => {
|
|||||||
cy.factory()
|
cy.factory()
|
||||||
.create('User', submitter)
|
.create('User', submitter)
|
||||||
.authenticateAs(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) {
|
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,3 +20,19 @@ Feature: Post Comment
|
|||||||
Then my comment should be successfully created
|
Then my comment should be successfully created
|
||||||
And I should see my comment
|
And I should see my comment
|
||||||
And the editor should be cleared
|
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 users from "../fixtures/users.json";
|
||||||
import { GraphQLClient, request } from 'graphql-request'
|
import { GraphQLClient, request } from 'graphql-request'
|
||||||
import { gql } from '../../backend/src/helpers/jest'
|
import { gql } from '../../backend/src/helpers/jest'
|
||||||
|
import config from '../../backend/src/config'
|
||||||
|
|
||||||
const backendHost = Cypress.env('BACKEND_HOST')
|
|
||||||
const switchLang = name => {
|
const switchLang = name => {
|
||||||
cy.get(".locale-menu").click();
|
cy.get(".locale-menu").click();
|
||||||
cy.contains(".locale-menu-popover a", name).click();
|
cy.contains(".locale-menu-popover a", name).click();
|
||||||
@ -31,7 +31,7 @@ const authenticatedHeaders = async (variables) => {
|
|||||||
login(email: $email, password: $password)
|
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}` }
|
return { authorization: `Bearer ${response.login}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +100,7 @@ Cypress.Commands.add(
|
|||||||
'authenticateAs',
|
'authenticateAs',
|
||||||
async ({email, password}) => {
|
async ({email, password}) => {
|
||||||
const headers = await authenticatedHeaders({ email, password })
|
const headers = await authenticatedHeaders({ email, password })
|
||||||
console.log(headers)
|
return new GraphQLClient(config.GRAPHQL_URI, { headers })
|
||||||
return new GraphQLClient(backendHost, { headers })
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
import Factory from '../../backend/src/seed/factories'
|
import Factory from '../../backend/src/seed/factories'
|
||||||
import { getDriver, neode as getNeode } from '../../backend/src/bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||||
import setupNeode from '../../backend/src/bootstrap/neode'
|
|
||||||
import neode from 'neode'
|
import neode from 'neode'
|
||||||
|
|
||||||
const backendHost = Cypress.env('SEED_SERVER_HOST')
|
const neo4jDriver = getDriver()
|
||||||
const neo4jConfigs = {
|
const neodeInstance = getNeode()
|
||||||
uri: Cypress.env('NEO4J_URI'),
|
const factoryOptions = { neo4jDriver, neodeInstance }
|
||||||
username: Cypress.env('NEO4J_USERNAME'),
|
|
||||||
password: Cypress.env('NEO4J_PASSWORD')
|
|
||||||
}
|
|
||||||
const neo4jDriver = getDriver(neo4jConfigs)
|
|
||||||
const factoryOptions = { seedServerHost: backendHost, neo4jDriver, neodeInstance: setupNeode(neo4jConfigs)}
|
|
||||||
const factory = Factory(factoryOptions)
|
const factory = Factory(factoryOptions)
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -18,7 +12,7 @@ beforeEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('neode', () => {
|
Cypress.Commands.add('neode', () => {
|
||||||
return setupNeode(neo4jConfigs)
|
return neodeInstance
|
||||||
})
|
})
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'first',
|
'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",
|
"name": "human-connection",
|
||||||
"version": "0.1.11",
|
"version": "0.1.13",
|
||||||
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
|
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
|
||||||
"author": "Human Connection gGmbh",
|
"author": "Human Connection gGmbh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -21,17 +21,17 @@
|
|||||||
"version": "auto-changelog -p"
|
"version": "auto-changelog -p"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.7.2",
|
"@babel/core": "^7.7.5",
|
||||||
"@babel/preset-env": "^7.7.4",
|
"@babel/preset-env": "^7.7.6",
|
||||||
"@babel/register": "^7.7.4",
|
"@babel/register": "^7.7.4",
|
||||||
"auto-changelog": "^1.16.2",
|
"auto-changelog": "^1.16.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"codecov": "^3.6.1",
|
"codecov": "^3.6.1",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"cucumber": "^6.0.5",
|
"cucumber": "^6.0.5",
|
||||||
"cypress": "^3.7.0",
|
"cypress": "^3.8.0",
|
||||||
"cypress-cucumber-preprocessor": "^1.18.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",
|
"cypress-plugin-retries": "^1.5.0",
|
||||||
"date-fns": "^2.8.1",
|
"date-fns": "^2.8.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
ROOT_DIR=$(dirname "$0")/..
|
ROOT_DIR=$(dirname "$0")/..
|
||||||
DOCKER_CLI_EXPERIMENTAL=enabled
|
|
||||||
# BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)}
|
# BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)}
|
||||||
|
|
||||||
IFS='.' read -r major minor patch < $ROOT_DIR/VERSION
|
IFS='.' read -r major minor patch < $ROOT_DIR/VERSION
|
||||||
apps=(nitro-web nitro-backend neo4j maintenance)
|
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:
|
# These three docker images have already been built by now:
|
||||||
# docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT --target production -t humanconnection/nitro-backend:latest $ROOT_DIR/backend
|
# docker build --build-arg BUILD_COMMIT=$BUILD_COMMIT --target production -t humanconnection/nitro-backend:latest $ROOT_DIR/backend
|
||||||
@ -17,13 +16,17 @@ echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
|||||||
|
|
||||||
for app in "${apps[@]}"
|
for app in "${apps[@]}"
|
||||||
do
|
do
|
||||||
|
SOURCE="humanconnection/${app}:latest"
|
||||||
|
echo "docker push $SOURCE"
|
||||||
|
docker push $SOURCE
|
||||||
|
|
||||||
for tag in "${tags[@]}"
|
for tag in "${tags[@]}"
|
||||||
do
|
do
|
||||||
SOURCE="humanconnection/${app}:latest"
|
|
||||||
TARGET="humanconnection/${app}:${tag}"
|
TARGET="humanconnection/${app}:${tag}"
|
||||||
if docker manifest inspect $TARGET &> /dev/null; then
|
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $TARGET >/dev/null; then
|
||||||
echo "Docker image ${TARGET} already present, skipping ..."
|
echo "docker image ${TARGET} already present, skipping ..."
|
||||||
else
|
else
|
||||||
|
echo -e "docker tag $SOURCE $TARGET\ndocker push $TARGET"
|
||||||
docker tag $SOURCE $TARGET
|
docker tag $SOURCE $TARGET
|
||||||
docker push $TARGET
|
docker push $TARGET
|
||||||
fi
|
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)"
|
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
|
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)"
|
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
|
EXPOSE 3000
|
||||||
|
|||||||
@ -6,14 +6,15 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# install all dependencies
|
# install all dependencies
|
||||||
|
$ cd webapp/
|
||||||
$ yarn install
|
$ yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy:
|
Copy:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
# in webapp/
|
||||||
cp .env.template .env
|
cp .env.template .env
|
||||||
cp cypress.env.template.json cypress.env.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure the files according to your needs and your local setup.
|
Configure the files according to your needs and your local setup.
|
||||||
|
|||||||
@ -22,10 +22,6 @@ describe('ContentMenu.vue', () => {
|
|||||||
locale: () => 'en',
|
locale: () => 'en',
|
||||||
},
|
},
|
||||||
$router: {
|
$router: {
|
||||||
resolve: jest.fn(obj => {
|
|
||||||
obj.href = '/post/edit/d23a4265-f5f7-4e17-9f86-85f714b4b9f8'
|
|
||||||
return obj
|
|
||||||
}),
|
|
||||||
push: jest.fn(),
|
push: jest.fn(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -76,7 +72,7 @@ describe('ContentMenu.vue', () => {
|
|||||||
.at(0)
|
.at(0)
|
||||||
.find('span.ds-menu-item-link')
|
.find('span.ds-menu-item-link')
|
||||||
.attributes('to'),
|
.attributes('to'),
|
||||||
).toBe('/post/edit/d23a4265-f5f7-4e17-9f86-85f714b4b9f8')
|
).toBe('/post-edit-id')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can delete the contribution', () => {
|
it('can delete the contribution', () => {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||||
>
|
>
|
||||||
<base-icon :name="item.route.icon" />
|
<base-icon :name="item.route.icon" />
|
||||||
{{ item.route.name }}
|
{{ item.route.label }}
|
||||||
</ds-menu-item>
|
</ds-menu-item>
|
||||||
</ds-menu>
|
</ds-menu>
|
||||||
</div>
|
</div>
|
||||||
@ -58,17 +58,15 @@ export default {
|
|||||||
if (this.resourceType === 'contribution') {
|
if (this.resourceType === 'contribution') {
|
||||||
if (this.isOwner) {
|
if (this.isOwner) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`post.menu.edit`),
|
label: this.$t(`post.menu.edit`),
|
||||||
path: this.$router.resolve({
|
|
||||||
name: 'post-edit-id',
|
name: 'post-edit-id',
|
||||||
params: {
|
params: {
|
||||||
id: this.resource.id,
|
id: this.resource.id,
|
||||||
},
|
},
|
||||||
}).href,
|
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
})
|
})
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`post.menu.delete`),
|
label: this.$t(`post.menu.delete`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.openModal('confirm', 'delete')
|
this.openModal('confirm', 'delete')
|
||||||
},
|
},
|
||||||
@ -79,7 +77,7 @@ export default {
|
|||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
if (!this.resource.pinnedBy) {
|
if (!this.resource.pinnedBy) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`post.menu.pin`),
|
label: this.$t(`post.menu.pin`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('pinPost', this.resource)
|
this.$emit('pinPost', this.resource)
|
||||||
},
|
},
|
||||||
@ -87,7 +85,7 @@ export default {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`post.menu.unpin`),
|
label: this.$t(`post.menu.unpin`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('unpinPost', this.resource)
|
this.$emit('unpinPost', this.resource)
|
||||||
},
|
},
|
||||||
@ -99,14 +97,14 @@ export default {
|
|||||||
|
|
||||||
if (this.isOwner && this.resourceType === 'comment') {
|
if (this.isOwner && this.resourceType === 'comment') {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`comment.menu.edit`),
|
label: this.$t(`comment.menu.edit`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('showEditCommentMenu', true)
|
this.$emit('showEditCommentMenu', true)
|
||||||
},
|
},
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
})
|
})
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`comment.menu.delete`),
|
label: this.$t(`comment.menu.delete`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.openModal('confirm', 'delete')
|
this.openModal('confirm', 'delete')
|
||||||
},
|
},
|
||||||
@ -116,7 +114,7 @@ export default {
|
|||||||
|
|
||||||
if (!this.isOwner) {
|
if (!this.isOwner) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`report.${this.resourceType}.title`),
|
label: this.$t(`report.${this.resourceType}.title`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.openModal('report')
|
this.openModal('report')
|
||||||
},
|
},
|
||||||
@ -127,7 +125,7 @@ export default {
|
|||||||
if (!this.isOwner && this.isModerator) {
|
if (!this.isOwner && this.isModerator) {
|
||||||
if (!this.resource.disabled) {
|
if (!this.resource.disabled) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`disable.${this.resourceType}.title`),
|
label: this.$t(`disable.${this.resourceType}.title`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.openModal('disable')
|
this.openModal('disable')
|
||||||
},
|
},
|
||||||
@ -135,7 +133,7 @@ export default {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`release.${this.resourceType}.title`),
|
label: this.$t(`release.${this.resourceType}.title`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.openModal('release')
|
this.openModal('release')
|
||||||
},
|
},
|
||||||
@ -147,14 +145,14 @@ export default {
|
|||||||
if (this.resourceType === 'user') {
|
if (this.resourceType === 'user') {
|
||||||
if (this.isOwner) {
|
if (this.isOwner) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`settings.name`),
|
label: this.$t(`settings.name`),
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (this.resource.isBlocked) {
|
if (this.resource.isBlocked) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`settings.blocked-users.unblock`),
|
label: this.$t(`settings.blocked-users.unblock`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('unblock', this.resource)
|
this.$emit('unblock', this.resource)
|
||||||
},
|
},
|
||||||
@ -162,7 +160,7 @@ export default {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`settings.blocked-users.block`),
|
label: this.$t(`settings.blocked-users.block`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('block', this.resource)
|
this.$emit('block', this.resource)
|
||||||
},
|
},
|
||||||
@ -186,7 +184,7 @@ export default {
|
|||||||
if (route.callback) {
|
if (route.callback) {
|
||||||
route.callback()
|
route.callback()
|
||||||
} else {
|
} else {
|
||||||
this.$router.push(route.path)
|
this.$router.push(route)
|
||||||
}
|
}
|
||||||
toggleMenu()
|
toggleMenu()
|
||||||
},
|
},
|
||||||
|
|||||||
@ -38,7 +38,6 @@
|
|||||||
</ds-chip>
|
</ds-chip>
|
||||||
<ds-chip v-else size="base">{{ form.title.length }}/{{ formSchema.title.max }}</ds-chip>
|
<ds-chip v-else size="base">{{ form.title.length }}/{{ formSchema.title.max }}</ds-chip>
|
||||||
</ds-text>
|
</ds-text>
|
||||||
<client-only>
|
|
||||||
<hc-editor
|
<hc-editor
|
||||||
:users="users"
|
:users="users"
|
||||||
:value="form.content"
|
:value="form.content"
|
||||||
@ -54,7 +53,6 @@
|
|||||||
{{ contentLength }}
|
{{ contentLength }}
|
||||||
</ds-chip>
|
</ds-chip>
|
||||||
</ds-text>
|
</ds-text>
|
||||||
</client-only>
|
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<hc-categories-select model="categoryIds" :existingCategoryIds="form.categoryIds" />
|
<hc-categories-select model="categoryIds" :existingCategoryIds="form.categoryIds" />
|
||||||
<ds-text align="right">
|
<ds-text align="right">
|
||||||
|
|||||||
@ -24,7 +24,6 @@
|
|||||||
import { Editor, EditorContent } from 'tiptap'
|
import { Editor, EditorContent } from 'tiptap'
|
||||||
import { History } from 'tiptap-extensions'
|
import { History } from 'tiptap-extensions'
|
||||||
import linkify from 'linkify-it'
|
import linkify from 'linkify-it'
|
||||||
import stringHash from 'string-hash'
|
|
||||||
import { replace, build } from 'xregexp/xregexp-all.js'
|
import { replace, build } from 'xregexp/xregexp-all.js'
|
||||||
|
|
||||||
import * as key from '../../constants/keycodes'
|
import * as key from '../../constants/keycodes'
|
||||||
@ -108,17 +107,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
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: {
|
placeholder: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler: function(val) {
|
handler: function(val) {
|
||||||
@ -129,7 +117,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
mounted() {
|
||||||
this.editor = new Editor({
|
this.editor = new Editor({
|
||||||
content: this.value || '',
|
content: this.value || '',
|
||||||
doc: this.doc,
|
doc: this.doc,
|
||||||
@ -247,11 +235,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onUpdate(e) {
|
onUpdate(e) {
|
||||||
const content = e.getHTML()
|
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) {
|
toggleLinkInput(attrs, element) {
|
||||||
if (!this.isLinkInputActive && attrs && element) {
|
if (!this.isLinkInputActive && attrs && element) {
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { allowEmbedIframesMutation } from '~/graphql/User.js'
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'embed-component',
|
name: 'embed-component',
|
||||||
@ -129,7 +129,7 @@ export default {
|
|||||||
async updateEmbedSettings(allowEmbedIframes) {
|
async updateEmbedSettings(allowEmbedIframes) {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: allowEmbedIframesMutation(),
|
mutation: updateUserMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
id: this.currentUser.id,
|
id: this.currentUser.id,
|
||||||
allowEmbedIframes,
|
allowEmbedIframes,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { config, mount } from '@vue/test-utils'
|
import { config, shallowMount } from '@vue/test-utils'
|
||||||
import MasonryGridItem from './MasonryGridItem'
|
import MasonryGridItem from './MasonryGridItem'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -8,41 +8,24 @@ config.stubs['ds-grid-item'] = '<span><slot /></span>'
|
|||||||
describe('MasonryGridItem', () => {
|
describe('MasonryGridItem', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
describe('given an imageAspectRatio', () => {
|
beforeEach(() => {
|
||||||
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
|
wrapper = shallowMount(MasonryGridItem, { localVue })
|
||||||
const propsData = { imageAspectRatio: 2 }
|
wrapper.vm.$parent.$emit = jest.fn()
|
||||||
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', () => {
|
it('emits "calculating-item-height" when starting calculation', async () => {
|
||||||
const propsData = { imageAspectRatio: 1.1 }
|
wrapper.vm.calculateItemHeight()
|
||||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
expect(wrapper.vm.rowSpan).toBe(15)
|
const firstCallArgument = wrapper.vm.$parent.$emit.mock.calls[0][0]
|
||||||
|
expect(firstCallArgument).toBe('calculating-item-height')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
|
it('emits "finished-calculating-item-height" after the calculation', async () => {
|
||||||
const propsData = { imageAspectRatio: 0.7 }
|
wrapper.vm.calculateItemHeight()
|
||||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
expect(wrapper.vm.rowSpan).toBe(18)
|
const secondCallArgument = wrapper.vm.$parent.$emit.mock.calls[1][0]
|
||||||
})
|
expect(secondCallArgument).toBe('finished-calculating-item-height')
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given no aspect ratio', () => {
|
|
||||||
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
|
|
||||||
wrapper = mount(MasonryGridItem, { localVue })
|
|
||||||
|
|
||||||
expect(wrapper.vm.rowSpan).toBe(8)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,17 +5,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
imageAspectRatio: {
|
imageAspectRatio: {
|
||||||
@ -25,7 +14,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 8,
|
rowSpan: 10,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -45,7 +34,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
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>
|
</script>
|
||||||
|
|||||||
@ -141,19 +141,10 @@ export default {
|
|||||||
this.$emit('unpinPost', post)
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style scoped lang="scss">
|
||||||
.ds-card-image img {
|
.ds-card-image img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 2000px;
|
max-height: 2000px;
|
||||||
|
|||||||
@ -66,6 +66,8 @@ describe('CreateUserAccount', () => {
|
|||||||
wrapper.find('input#checkbox0').setChecked()
|
wrapper.find('input#checkbox0').setChecked()
|
||||||
wrapper.find('input#checkbox1').setChecked()
|
wrapper.find('input#checkbox1').setChecked()
|
||||||
wrapper.find('input#checkbox2').setChecked()
|
wrapper.find('input#checkbox2').setChecked()
|
||||||
|
wrapper.find('input#checkbox3').setChecked()
|
||||||
|
wrapper.find('input#checkbox4').setChecked()
|
||||||
await wrapper.find('form').trigger('submit')
|
await wrapper.find('form').trigger('submit')
|
||||||
await wrapper.html()
|
await wrapper.html()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,12 +88,33 @@
|
|||||||
v-html="$t('components.registration.signup.form.minimum-age')"
|
v-html="$t('components.registration.signup.form.minimum-age')"
|
||||||
></label>
|
></label>
|
||||||
</ds-text>
|
</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
|
<ds-button
|
||||||
style="float: right;"
|
style="float: right;"
|
||||||
icon="check"
|
icon="check"
|
||||||
type="submit"
|
type="submit"
|
||||||
:loading="$apollo.loading"
|
:loading="$apollo.loading"
|
||||||
:disabled="errors || !termsAndConditionsConfirmed || !dataPrivacy || !minimumAge"
|
:disabled="
|
||||||
|
errors ||
|
||||||
|
!termsAndConditionsConfirmed ||
|
||||||
|
!dataPrivacy ||
|
||||||
|
!minimumAge ||
|
||||||
|
!noCommercial ||
|
||||||
|
!noPolitical
|
||||||
|
"
|
||||||
primary
|
primary
|
||||||
>
|
>
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
@ -145,6 +166,8 @@ export default {
|
|||||||
termsAndConditionsConfirmed: false,
|
termsAndConditionsConfirmed: false,
|
||||||
dataPrivacy: false,
|
dataPrivacy: false,
|
||||||
minimumAge: false,
|
minimumAge: false,
|
||||||
|
noCommercial: false,
|
||||||
|
noPolitical: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import vueDropzone from 'nuxt-dropzone'
|
import vueDropzone from 'nuxt-dropzone'
|
||||||
import gql from 'graphql-tag'
|
import { updateUserMutation } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -62,14 +62,7 @@ export default {
|
|||||||
const avatarUpload = file[0]
|
const avatarUpload = file[0]
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: gql`
|
mutation: updateUserMutation(),
|
||||||
mutation($id: ID!, $avatarUpload: Upload) {
|
|
||||||
UpdateUser(id: $id, avatarUpload: $avatarUpload) {
|
|
||||||
id
|
|
||||||
avatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
variables: {
|
||||||
avatarUpload,
|
avatarUpload,
|
||||||
id: this.user.id,
|
id: this.user.id,
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<dropdown v-else :class="{ 'disabled-content': user.disabled }" placement="top-start" offset="0">
|
<dropdown v-else :class="{ 'disabled-content': user.disabled }" placement="top-start" offset="0">
|
||||||
<template slot="default" slot-scope="{ openMenu, closeMenu, isOpen }">
|
<template slot="default" slot-scope="{ openMenu, closeMenu, isOpen }">
|
||||||
<nuxt-link :to="userLink" :class="['user', isOpen && 'active']">
|
<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" />
|
<hc-avatar v-if="showAvatar" class="avatar" :user="user" />
|
||||||
<div>
|
<div>
|
||||||
<ds-text class="userinfo">
|
<ds-text class="userinfo">
|
||||||
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<template slot="popover" v-if="showCounts">
|
<template slot="popover" v-if="showPopover">
|
||||||
<div style="min-width: 250px">
|
<div style="min-width: 250px">
|
||||||
<hc-badges v-if="user.badges && user.badges.length" :badges="user.badges" />
|
<hc-badges v-if="user.badges && user.badges.length" :badges="user.badges" />
|
||||||
<ds-text
|
<ds-text
|
||||||
@ -106,7 +106,7 @@ export default {
|
|||||||
showAvatar: { type: Boolean, default: true },
|
showAvatar: { type: Boolean, default: true },
|
||||||
trunc: { type: Number, default: 18 }, // "-1" is no trunc
|
trunc: { type: Number, default: 18 }, // "-1" is no trunc
|
||||||
dateTime: { type: [Date, String], default: null },
|
dateTime: { type: [Date, String], default: null },
|
||||||
showCounts: { type: Boolean, default: true },
|
showPopover: { type: Boolean, default: true },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
@ -143,9 +143,6 @@ export default {
|
|||||||
this.user.followedByCount = followedByCount
|
this.user.followedByCount = followedByCount
|
||||||
this.user.followedByCurrentUser = followedByCurrentUser
|
this.user.followedByCurrentUser = followedByCurrentUser
|
||||||
},
|
},
|
||||||
openInfoMenu() {
|
|
||||||
if (this.showCounts) this.openMenu(true)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
<hc-user
|
<hc-user
|
||||||
:user="scope.row.submitter"
|
:user="scope.row.submitter"
|
||||||
:showAvatar="false"
|
:showAvatar="false"
|
||||||
|
:showPopover="false"
|
||||||
:trunc="30"
|
:trunc="30"
|
||||||
data-test="filing-user"
|
data-test="filing-user"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<!-- Content Column -->
|
<!-- Content Column -->
|
||||||
<td class="ds-table-col" data-test="report-content">
|
<td class="ds-table-col" data-test="report-content">
|
||||||
<client-only v-if="isUser">
|
<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>
|
</client-only>
|
||||||
<nuxt-link v-else class="title" :to="linkTarget">
|
<nuxt-link v-else class="title" :to="linkTarget">
|
||||||
{{ linkText | truncate(50) }}
|
{{ linkText | truncate(50) }}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
:user="report.resource.author"
|
:user="report.resource.author"
|
||||||
:showAvatar="false"
|
:showAvatar="false"
|
||||||
:trunc="30"
|
:trunc="30"
|
||||||
:showCounts="false"
|
:showPopover="false"
|
||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
<span v-else>—</span>
|
<span v-else>—</span>
|
||||||
@ -51,7 +51,7 @@
|
|||||||
:showAvatar="false"
|
:showAvatar="false"
|
||||||
:trunc="30"
|
:trunc="30"
|
||||||
:date-time="report.updatedAt"
|
:date-time="report.updatedAt"
|
||||||
:showCounts="false"
|
:showPopover="false"
|
||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
</td>
|
</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_MIN_LENGTH = 1
|
||||||
export const COMMENT_MAX_UNTRUNCATED_LENGTH = 300
|
export const COMMENT_MAX_UNTRUNCATED_LENGTH = 1200
|
||||||
export const COMMENT_TRUNCATE_TO_LENGTH = 180
|
export const COMMENT_TRUNCATE_TO_LENGTH = 180
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
export const userFragment = lang => gql`
|
export const userFragment = gql`
|
||||||
fragment user on User {
|
fragment user on User {
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
@ -8,11 +8,10 @@ export const userFragment = lang => gql`
|
|||||||
avatar
|
avatar
|
||||||
disabled
|
disabled
|
||||||
deleted
|
deleted
|
||||||
shoutedCount
|
}
|
||||||
contributionsCount
|
`
|
||||||
commentedCount
|
export const locationAndBadgesFragment = lang => gql`
|
||||||
followedByCount
|
fragment locationAndBadges on User {
|
||||||
followedByCurrentUser
|
|
||||||
location {
|
location {
|
||||||
name: name${lang}
|
name: name${lang}
|
||||||
}
|
}
|
||||||
@ -23,17 +22,17 @@ export const userFragment = lang => gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const postCountsFragment = gql`
|
export const userCountsFragment = gql`
|
||||||
fragment postCounts on Post {
|
fragment userCounts on User {
|
||||||
commentsCount
|
|
||||||
shoutedCount
|
shoutedCount
|
||||||
shoutedByCurrentUser
|
contributionsCount
|
||||||
emotionsCount
|
commentedCount
|
||||||
|
followedByCount
|
||||||
|
followedByCurrentUser
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const postFragment = lang => gql`
|
|
||||||
${userFragment(lang)}
|
|
||||||
|
|
||||||
|
export const postFragment = gql`
|
||||||
fragment post on Post {
|
fragment post on Post {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@ -46,9 +45,22 @@ export const postFragment = lang => gql`
|
|||||||
slug
|
slug
|
||||||
image
|
image
|
||||||
language
|
language
|
||||||
author {
|
pinnedAt
|
||||||
...user
|
imageAspectRatio
|
||||||
}
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const postCountsFragment = gql`
|
||||||
|
fragment postCounts on Post {
|
||||||
|
commentsCount
|
||||||
|
shoutedCount
|
||||||
|
shoutedByCurrentUser
|
||||||
|
emotionsCount
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const tagsCategoriesAndPinnedFragment = gql`
|
||||||
|
fragment tagsCategoriesAndPinned on Post {
|
||||||
tags {
|
tags {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@ -63,13 +75,10 @@ export const postFragment = lang => gql`
|
|||||||
name
|
name
|
||||||
role
|
role
|
||||||
}
|
}
|
||||||
pinnedAt
|
|
||||||
imageAspectRatio
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const commentFragment = lang => gql`
|
|
||||||
${userFragment(lang)}
|
|
||||||
|
|
||||||
|
export const commentFragment = gql`
|
||||||
fragment comment on Comment {
|
fragment comment on Comment {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
@ -78,8 +87,5 @@ export const commentFragment = lang => gql`
|
|||||||
deleted
|
deleted
|
||||||
content
|
content
|
||||||
contentExcerpt
|
contentExcerpt
|
||||||
author {
|
|
||||||
...user
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,19 +1,42 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { postFragment, commentFragment, postCountsFragment } from './Fragments'
|
import {
|
||||||
|
userFragment,
|
||||||
|
postFragment,
|
||||||
|
commentFragment,
|
||||||
|
postCountsFragment,
|
||||||
|
userCountsFragment,
|
||||||
|
locationAndBadgesFragment,
|
||||||
|
tagsCategoriesAndPinnedFragment,
|
||||||
|
} from './Fragments'
|
||||||
|
|
||||||
export default i18n => {
|
export default i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
const lang = i18n.locale().toUpperCase()
|
||||||
return gql`
|
return gql`
|
||||||
${postFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
${locationAndBadgesFragment(lang)}
|
||||||
|
${postFragment}
|
||||||
${postCountsFragment}
|
${postCountsFragment}
|
||||||
${commentFragment(lang)}
|
${tagsCategoriesAndPinnedFragment}
|
||||||
|
${commentFragment}
|
||||||
|
|
||||||
query Post($id: ID!) {
|
query Post($id: ID!) {
|
||||||
Post(id: $id) {
|
Post(id: $id) {
|
||||||
...post
|
...post
|
||||||
...postCounts
|
...postCounts
|
||||||
|
...tagsCategoriesAndPinned
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
comments(orderBy: createdAt_asc) {
|
comments(orderBy: createdAt_asc) {
|
||||||
...comment
|
...comment
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,13 +46,23 @@ export default i18n => {
|
|||||||
export const filterPosts = i18n => {
|
export const filterPosts = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
const lang = i18n.locale().toUpperCase()
|
||||||
return gql`
|
return gql`
|
||||||
${postFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
${locationAndBadgesFragment(lang)}
|
||||||
|
${postFragment}
|
||||||
${postCountsFragment}
|
${postCountsFragment}
|
||||||
|
${tagsCategoriesAndPinnedFragment}
|
||||||
|
|
||||||
query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) {
|
query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) {
|
||||||
Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
||||||
...post
|
...post
|
||||||
...postCounts
|
...postCounts
|
||||||
|
...tagsCategoriesAndPinned
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -38,8 +71,12 @@ export const filterPosts = i18n => {
|
|||||||
export const profilePagePosts = i18n => {
|
export const profilePagePosts = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
const lang = i18n.locale().toUpperCase()
|
||||||
return gql`
|
return gql`
|
||||||
${postFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
${locationAndBadgesFragment(lang)}
|
||||||
|
${postFragment}
|
||||||
${postCountsFragment}
|
${postCountsFragment}
|
||||||
|
${tagsCategoriesAndPinnedFragment}
|
||||||
|
|
||||||
query profilePagePosts(
|
query profilePagePosts(
|
||||||
$filter: _PostFilter
|
$filter: _PostFilter
|
||||||
@ -50,6 +87,12 @@ export const profilePagePosts = i18n => {
|
|||||||
profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
||||||
...post
|
...post
|
||||||
...postCounts
|
...postCounts
|
||||||
|
...tagsCategoriesAndPinned
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -66,16 +109,32 @@ export const PostsEmotionsByCurrentUser = () => {
|
|||||||
export const relatedContributions = i18n => {
|
export const relatedContributions = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
const lang = i18n.locale().toUpperCase()
|
||||||
return gql`
|
return gql`
|
||||||
${postFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
${locationAndBadgesFragment(lang)}
|
||||||
|
${postFragment}
|
||||||
${postCountsFragment}
|
${postCountsFragment}
|
||||||
|
${tagsCategoriesAndPinnedFragment}
|
||||||
|
|
||||||
query Post($slug: String!) {
|
query Post($slug: String!) {
|
||||||
Post(slug: $slug) {
|
Post(slug: $slug) {
|
||||||
...post
|
...post
|
||||||
...postCounts
|
...postCounts
|
||||||
|
...tagsCategoriesAndPinned
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
relatedContributions(first: 2) {
|
relatedContributions(first: 2) {
|
||||||
...post
|
...post
|
||||||
...postCounts
|
...postCounts
|
||||||
|
...tagsCategoriesAndPinned
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,38 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { userFragment, postFragment, commentFragment } from './Fragments'
|
import {
|
||||||
|
userCountsFragment,
|
||||||
|
locationAndBadgesFragment,
|
||||||
|
userFragment,
|
||||||
|
postFragment,
|
||||||
|
commentFragment,
|
||||||
|
} from './Fragments'
|
||||||
|
|
||||||
export default i18n => {
|
export default i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
const lang = i18n.locale().toUpperCase()
|
||||||
return gql`
|
return gql`
|
||||||
${userFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
${locationAndBadgesFragment(lang)}
|
||||||
|
|
||||||
query User($id: ID!) {
|
query User($id: ID!) {
|
||||||
User(id: $id) {
|
User(id: $id) {
|
||||||
...user
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
about
|
about
|
||||||
locationName
|
locationName
|
||||||
createdAt
|
createdAt
|
||||||
badgesCount
|
|
||||||
followingCount
|
|
||||||
following(first: 7) {
|
|
||||||
...user
|
|
||||||
}
|
|
||||||
followedByCount
|
|
||||||
followedByCurrentUser
|
followedByCurrentUser
|
||||||
isBlocked
|
isBlocked
|
||||||
|
following(first: 7) {
|
||||||
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
|
}
|
||||||
followedBy(first: 7) {
|
followedBy(first: 7) {
|
||||||
...user
|
...user
|
||||||
|
...userCounts
|
||||||
|
...locationAndBadges
|
||||||
}
|
}
|
||||||
socialMedia {
|
socialMedia {
|
||||||
id
|
id
|
||||||
@ -47,10 +58,10 @@ export const minimisedUserQuery = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const notificationQuery = i18n => {
|
export const notificationQuery = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
|
||||||
return gql`
|
return gql`
|
||||||
${commentFragment(lang)}
|
${userFragment}
|
||||||
${postFragment(lang)}
|
${commentFragment}
|
||||||
|
${postFragment}
|
||||||
|
|
||||||
query($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) {
|
query($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) {
|
||||||
notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) {
|
notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) {
|
||||||
@ -62,11 +73,20 @@ export const notificationQuery = i18n => {
|
|||||||
__typename
|
__typename
|
||||||
... on Post {
|
... on Post {
|
||||||
...post
|
...post
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
... on Comment {
|
... on Comment {
|
||||||
...comment
|
...comment
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
}
|
||||||
post {
|
post {
|
||||||
...post
|
...post
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,10 +96,10 @@ export const notificationQuery = i18n => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const markAsReadMutation = i18n => {
|
export const markAsReadMutation = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
|
||||||
return gql`
|
return gql`
|
||||||
${commentFragment(lang)}
|
${userFragment}
|
||||||
${postFragment(lang)}
|
${commentFragment}
|
||||||
|
${postFragment}
|
||||||
|
|
||||||
mutation($id: ID!) {
|
mutation($id: ID!) {
|
||||||
markAsRead(id: $id) {
|
markAsRead(id: $id) {
|
||||||
@ -91,11 +111,17 @@ export const markAsReadMutation = i18n => {
|
|||||||
__typename
|
__typename
|
||||||
... on Post {
|
... on Post {
|
||||||
...post
|
...post
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
... on Comment {
|
... on Comment {
|
||||||
...comment
|
...comment
|
||||||
post {
|
post {
|
||||||
...post
|
...post
|
||||||
|
author {
|
||||||
|
...user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,16 +131,19 @@ export const markAsReadMutation = i18n => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const followUserMutation = i18n => {
|
export const followUserMutation = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
|
||||||
return gql`
|
return gql`
|
||||||
${userFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
|
||||||
mutation($id: ID!) {
|
mutation($id: ID!) {
|
||||||
followUser(id: $id) {
|
followUser(id: $id) {
|
||||||
name
|
...user
|
||||||
|
...userCounts
|
||||||
followedByCount
|
followedByCount
|
||||||
followedByCurrentUser
|
followedByCurrentUser
|
||||||
followedBy(first: 7) {
|
followedBy(first: 7) {
|
||||||
...user
|
...user
|
||||||
|
...userCounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,39 +151,59 @@ export const followUserMutation = i18n => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const unfollowUserMutation = i18n => {
|
export const unfollowUserMutation = i18n => {
|
||||||
const lang = i18n.locale().toUpperCase()
|
|
||||||
return gql`
|
return gql`
|
||||||
${userFragment(lang)}
|
${userFragment}
|
||||||
|
${userCountsFragment}
|
||||||
|
|
||||||
mutation($id: ID!) {
|
mutation($id: ID!) {
|
||||||
unfollowUser(id: $id) {
|
unfollowUser(id: $id) {
|
||||||
name
|
...user
|
||||||
|
...userCounts
|
||||||
followedByCount
|
followedByCount
|
||||||
followedByCurrentUser
|
followedByCurrentUser
|
||||||
followedBy(first: 7) {
|
followedBy(first: 7) {
|
||||||
...user
|
...user
|
||||||
|
...userCounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allowEmbedIframesMutation = () => {
|
export const updateUserMutation = () => {
|
||||||
return gql`
|
return gql`
|
||||||
mutation($id: ID!, $allowEmbedIframes: Boolean) {
|
mutation(
|
||||||
UpdateUser(id: $id, allowEmbedIframes: $allowEmbedIframes) {
|
$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
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
locationName
|
||||||
|
about
|
||||||
allowEmbedIframes
|
allowEmbedIframes
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const showShoutsPubliclyMutation = () => {
|
|
||||||
return gql`
|
|
||||||
mutation($id: ID!, $showShoutsPublicly: Boolean) {
|
|
||||||
UpdateUser(id: $id, showShoutsPublicly: $showShoutsPublicly) {
|
|
||||||
id
|
|
||||||
showShoutsPublicly
|
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": {
|
"deleteUserAccount": {
|
||||||
"name": "Daten löschen",
|
"name": "Benutzerkonto löschen",
|
||||||
"contributionsCount": "Meine {count} Beiträge löschen",
|
"contributionsCount": "Meine {count} Beiträge löschen",
|
||||||
"commentedCount": "Meine {count} Kommentare 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.",
|
"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.",
|
"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",
|
"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.",
|
"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>",
|
"invitation-code": "Dein Einladungscode lautet: <b>{code}<\/b>",
|
||||||
"errors": {
|
"errors": {
|
||||||
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
|
"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>.",
|
"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> ",
|
"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.",
|
"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>",
|
"invitation-code": "Your invitation code is: <b>{code}</b>",
|
||||||
"errors": {
|
"errors": {
|
||||||
"email-exists": "There is already a user account with this e-mail address!",
|
"email-exists": "There is already a user account with this e-mail address!",
|
||||||
@ -269,10 +271,10 @@
|
|||||||
"name": "Download Data"
|
"name": "Download Data"
|
||||||
},
|
},
|
||||||
"deleteUserAccount": {
|
"deleteUserAccount": {
|
||||||
"name": "Delete data",
|
"name": "Delete user account",
|
||||||
"contributionsCount": "Delete my {count} posts",
|
"contributionsCount": "Delete my {count} posts",
|
||||||
"commentedCount": "Delete my {count} comments",
|
"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!",
|
"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!",
|
"success": "Account successfully deleted!",
|
||||||
"pleaseConfirm": "<b class='is-danger'>Destructive action!</b> Type <b>{confirm}</b> to confirm"
|
"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