mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into remove-data-test-attributes-in-production
This commit is contained in:
commit
27a2ccd9ae
90
.gitbook/assets/browserstack-logo.svg
Normal file
90
.gitbook/assets/browserstack-logo.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 490.1 105.6" style="enable-background:new 0 0 490.1 105.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F4B960;}
|
||||
.st1{fill:#E66F32;}
|
||||
.st2{fill:#E43C41;}
|
||||
.st3{fill:#BDD041;}
|
||||
.st4{fill:#6DB54C;}
|
||||
.st5{fill:#AEDAE6;}
|
||||
.st6{fill:#56B8DE;}
|
||||
.st7{fill:#00B1D5;}
|
||||
.st8{fill:url(#SVGID_1_);}
|
||||
.st9{fill:#221F1F;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#000111;}
|
||||
</style>
|
||||
<title>Browserstack-logo-white</title>
|
||||
<circle class="st0" cx="52.8" cy="52.8" r="52.8"/>
|
||||
<circle class="st1" cx="47.5" cy="47.5" r="47.5"/>
|
||||
<circle class="st2" cx="53.8" cy="41.1" r="41.1"/>
|
||||
<circle class="st3" cx="57.1" cy="44.4" r="37.8"/>
|
||||
<circle class="st4" cx="54.3" cy="47.2" r="35.1"/>
|
||||
<circle class="st5" cx="48.8" cy="41.7" r="29.5"/>
|
||||
<circle class="st6" cx="53.6" cy="36.8" r="24.7"/>
|
||||
<circle class="st7" cx="56.6" cy="39.9" r="21.7"/>
|
||||
<radialGradient id="SVGID_1_" cx="53.45" cy="63.02" r="18.57" gradientTransform="matrix(1 0 0 -1 0 106)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#797979"/>
|
||||
<stop offset="1" style="stop-color:#4C4C4C"/>
|
||||
</radialGradient>
|
||||
<circle class="st8" cx="53.5" cy="43" r="18.6"/>
|
||||
<circle class="st9" cx="53.5" cy="43" r="18.6"/>
|
||||
<ellipse transform="matrix(0.4094 -0.9123 0.9123 0.4094 2.8913 76.9251)" class="st10" cx="60.9" cy="36.2" rx="5.7" ry="3.7"/>
|
||||
<path class="st11" d="M122.5,32.6c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0.1,0h16.6c9.5,0,13.9,4.4,13.9,11c0.2,3.7-1.8,7.2-5.2,8.8v0.1
|
||||
c3.7,1.5,6.1,5.2,6,9.3c0,8.2-5.6,12.2-15.4,12.2h-16c-0.3,0-0.6-0.2-0.7-0.5c0,0,0,0,0-0.1L122.5,32.6L122.5,32.6z M139.6,49.1
|
||||
c3.9,0,6.4-2.2,6.4-5.4s-2.4-5.5-6.4-5.5h-8.9c-0.2,0-0.4,0.1-0.4,0.3c0,0,0,0,0,0.1v10.2c0,0.2,0.1,0.3,0.3,0.4c0,0,0,0,0.1,0
|
||||
H139.6L139.6,49.1z M130.6,66.9h9.3c4.3,0,6.8-2.3,6.8-5.8s-2.4-5.7-6.7-5.7h-9.3c-0.2,0-0.4,0.1-0.4,0.3c0,0,0,0,0,0.1v10.7
|
||||
C130.3,66.8,130.4,66.9,130.6,66.9C130.6,66.9,130.6,66.9,130.6,66.9L130.6,66.9z"/>
|
||||
<path class="st11" d="M159.9,73.3c-0.3,0-0.6-0.2-0.7-0.5c0,0,0,0,0-0.1V44.6c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0.1,0h6
|
||||
c0.3,0,0.6,0.2,0.7,0.5c0,0,0,0,0,0.1v2.5h0.1c1.5-2.2,4.2-3.8,8.2-3.8c2.4,0,4.8,0.8,6.6,2.4c0.3,0.3,0.4,0.5,0.1,0.8l-3.5,4.1
|
||||
c-0.2,0.3-0.6,0.4-0.9,0.2c0,0,0,0-0.1,0c-1.4-0.9-3-1.4-4.7-1.4c-4.1,0-6,2.7-6,7.4v15.9c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0
|
||||
H159.9L159.9,73.3z"/>
|
||||
<path class="st11" d="M182.9,65.8c-0.8-2.3-1.1-4.8-1.1-7.2c-0.1-2.5,0.3-4.9,1.1-7.2c1.8-5.1,6.6-8.1,13.1-8.1s11.2,3,13,8.1
|
||||
c0.8,2.3,1.1,4.8,1.1,7.2c0.1,2.5-0.3,4.9-1.1,7.2c-1.8,5.1-6.6,8.1-13,8.1S184.7,71,182.9,65.8z M201.9,64c0.5-1.7,0.8-3.6,0.7-5.4
|
||||
c0.1-1.8-0.1-3.7-0.7-5.4c-0.9-2.5-3.3-4-5.9-3.8c-2.6-0.2-5.1,1.4-6,3.8c-0.5,1.8-0.8,3.6-0.7,5.4c-0.1,1.8,0.1,3.7,0.7,5.4
|
||||
c0.9,2.5,3.4,4,6,3.8C198.6,68,201,66.5,201.9,64L201.9,64z"/>
|
||||
<path class="st11" d="M241.9,73.3c-0.4,0-0.7-0.3-0.8-0.6L235,53.9h-0.1l-6.2,18.7c-0.1,0.4-0.4,0.6-0.8,0.6h-5.4
|
||||
c-0.4,0-0.7-0.3-0.8-0.6l-10-28.1c-0.1-0.2,0-0.5,0.2-0.6c0.1,0,0.2-0.1,0.3,0h6.3c0.4,0,0.8,0.2,0.9,0.6l6.1,19.3h0.1l6-19.3
|
||||
c0.1-0.4,0.5-0.6,0.9-0.6h4.7c0.4,0,0.7,0.2,0.9,0.6l6.4,19.3h0.1l5.8-19.3c0.1-0.4,0.5-0.7,0.9-0.6h6.3c0.2-0.1,0.5,0.1,0.5,0.3
|
||||
c0,0.1,0,0.2,0,0.3l-10,28.1c-0.1,0.4-0.4,0.6-0.8,0.6L241.9,73.3L241.9,73.3z"/>
|
||||
<path class="st11" d="M259.3,69.3c-0.2-0.2-0.3-0.6-0.1-0.8c0,0,0,0,0.1-0.1l3.7-3.6c0.3-0.2,0.7-0.2,0.9,0c2.6,2.1,5.9,3.3,9.3,3.3
|
||||
c3.9,0,5.9-1.5,5.9-3.5c0-1.8-1.1-2.9-5.2-3.2l-3.4-0.3c-6.4-0.6-9.7-3.6-9.7-8.6c0-5.7,4.4-9.2,12.3-9.2c4.2-0.1,8.4,1.2,11.9,3.6
|
||||
c0.3,0.2,0.3,0.5,0.2,0.8c0,0,0,0,0,0.1l-3.2,3.6c-0.2,0.3-0.6,0.3-0.9,0.1c-2.5-1.5-5.4-2.4-8.3-2.4c-3.1,0-4.8,1.3-4.8,3
|
||||
s1.1,2.7,5.2,3.1l3.4,0.3c6.6,0.6,9.8,3.8,9.8,8.6c0,5.8-4.6,9.9-13.3,9.9C268,74,263.2,72.4,259.3,69.3z"/>
|
||||
<path class="st11" d="M291.2,65.8c-0.8-2.3-1.2-4.7-1.1-7.2c-0.1-2.5,0.3-4.9,1-7.2c1.8-5.1,6.6-8.1,12.9-8.1c6.5,0,11.2,3.1,13,8.1
|
||||
c0.7,2.1,1,4.1,1,8.8c0,0.3-0.3,0.6-0.6,0.6c0,0-0.1,0-0.1,0h-19.5c-0.2,0-0.4,0.1-0.4,0.3c0,0,0,0,0,0.1c0,0.8,0.2,1.5,0.5,2.2
|
||||
c1,2.9,3.5,4.4,7.1,4.4c2.7,0.1,5.4-0.9,7.4-2.8c0.2-0.3,0.7-0.4,1-0.1c0,0,0,0,0,0l3.9,3.2c0.2,0.1,0.3,0.5,0.2,0.7
|
||||
c0,0.1-0.1,0.1-0.1,0.1c-2.7,2.9-7.2,5-13,5C297.8,73.9,293,70.9,291.2,65.8z M310.4,52.8c-0.9-2.4-3.2-3.8-6.2-3.8
|
||||
s-5.4,1.4-6.2,3.8c-0.3,0.8-0.4,1.6-0.4,2.5c0,0.2,0.1,0.3,0.3,0.4c0,0,0,0,0.1,0h12.4c0.2,0,0.4-0.1,0.4-0.3c0,0,0,0,0-0.1
|
||||
C310.8,54.5,310.6,53.6,310.4,52.8L310.4,52.8z"/>
|
||||
<path class="st11" d="M323.6,73.3c-0.3,0-0.6-0.2-0.7-0.5c0,0,0,0,0-0.1V44.6c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0.1,0h6
|
||||
c0.3,0,0.6,0.2,0.7,0.5c0,0,0,0,0,0.1v2.5h0.1c1.5-2.2,4.2-3.8,8.2-3.8c2.4,0,4.8,0.8,6.6,2.4c0.3,0.3,0.4,0.5,0.1,0.8l-3.5,4.1
|
||||
c-0.2,0.3-0.6,0.4-0.9,0.2c0,0,0,0-0.1,0c-1.4-0.9-3-1.4-4.7-1.4c-4.1,0-6,2.7-6,7.4v15.9c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0
|
||||
H323.6L323.6,73.3z"/>
|
||||
<path class="st11" d="M346.5,68.5c-0.3-0.2-0.4-0.6-0.2-0.9c0,0,0,0,0,0l4.1-4.4c0.2-0.3,0.6-0.3,0.9-0.1c0,0,0,0,0,0
|
||||
c3.5,2.7,7.7,4.2,12.1,4.4c5.3,0,8.4-2.5,8.4-6c0-3-2-4.9-8.1-5.7l-2.4-0.3c-8.6-1.1-13.5-4.9-13.5-11.8c0-7.5,5.9-12.4,15.1-12.4
|
||||
c5.1-0.1,10.1,1.4,14.5,4.2c0.3,0.1,0.4,0.4,0.2,0.7c0,0.1-0.1,0.1-0.1,0.2l-3.1,4.5c-0.2,0.3-0.6,0.4-0.9,0.2
|
||||
c-3.2-2.1-6.9-3.2-10.7-3.2c-4.5,0-7,2.3-7,5.5c0,2.9,2.2,4.8,8.2,5.6l2.4,0.3c8.6,1.1,13.3,4.9,13.3,12c0,7.3-5.7,12.8-16.8,12.8
|
||||
C356.3,73.9,350,71.5,346.5,68.5z"/>
|
||||
<path class="st11" d="M393.3,73.8c-6.4,0-8.8-2.9-8.8-8.6V49.8c0-0.2-0.1-0.3-0.3-0.4c0,0,0,0-0.1,0H382c-0.3,0-0.6-0.2-0.7-0.5
|
||||
c0,0,0,0,0-0.1v-4.1c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0.1,0h2.1c0.2,0,0.4-0.1,0.4-0.3c0,0,0,0,0-0.1v-8c0-0.3,0.3-0.6,0.6-0.6
|
||||
c0,0,0,0,0.1,0h6c0.3,0,0.6,0.2,0.7,0.5c0,0,0,0,0,0.1v8c0,0.2,0.1,0.3,0.3,0.4c0,0,0,0,0.1,0h4.2c0.3,0,0.6,0.2,0.7,0.5
|
||||
c0,0,0,0,0,0.1v4.1c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0h-4.2c-0.2,0-0.4,0.1-0.4,0.3c0,0,0,0,0,0.1V65c0,2.1,0.9,2.7,3,2.7h1.6
|
||||
c0.3,0,0.6,0.2,0.7,0.5c0,0,0,0,0,0.1v4.9c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0L393.3,73.8L393.3,73.8z"/>
|
||||
<path class="st11" d="M421.2,73.3c-0.3,0-0.6-0.2-0.7-0.5c0,0,0,0,0-0.1v-2.1h0c-1.5,2-4.5,3.4-8.9,3.4c-5.8,0-10.6-2.8-10.6-8.9
|
||||
c0-6.4,4.9-9.3,12.7-9.3h6.4c0.2,0,0.4-0.1,0.4-0.3c0,0,0,0,0-0.1v-1.4c0-3.3-1.7-4.9-7-4.9c-2.6-0.1-5.1,0.6-7.2,2
|
||||
c-0.3,0.2-0.7,0.2-0.9-0.1c0,0,0,0,0-0.1l-2.4-4c-0.2-0.2-0.1-0.6,0.1-0.8c0,0,0,0,0,0c2.6-1.7,6-2.9,11.2-2.9
|
||||
c9.6,0,13.2,3,13.2,10.2v19.1c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0H421.2L421.2,73.3z M420.4,63.4v-2.2c0-0.2-0.1-0.3-0.3-0.4
|
||||
c0,0,0,0-0.1,0h-5.2c-4.7,0-6.8,1.2-6.8,3.9c0,2.4,1.9,3.6,5.5,3.6C417.9,68.4,420.4,66.8,420.4,63.4L420.4,63.4z"/>
|
||||
<path class="st11" d="M433.1,65.8c-0.7-2.3-1.1-4.8-1-7.2c-0.1-2.4,0.3-4.9,1-7.2c1.8-5.2,6.7-8.1,13.1-8.1c4.2-0.2,8.2,1.5,11,4.6
|
||||
c0.2,0.2,0.2,0.6,0,0.8c0,0,0,0-0.1,0.1l-4.1,3.3c-0.3,0.2-0.7,0.2-0.9-0.1c0,0,0,0,0-0.1c-1.5-1.7-3.6-2.6-5.9-2.5
|
||||
c-2.8,0-5,1.3-5.9,3.8c-0.5,1.8-0.8,3.6-0.7,5.4c-0.1,1.8,0.1,3.7,0.7,5.5c0.9,2.5,3.1,3.8,5.9,3.8c2.2,0.1,4.4-0.9,5.9-2.6
|
||||
c0.2-0.3,0.6-0.3,0.9-0.1c0,0,0,0,0,0l4.1,3.3c0.3,0.2,0.3,0.5,0.1,0.8c0,0,0,0-0.1,0.1c-2.9,3-6.9,4.6-11,4.5
|
||||
C439.8,73.9,435,71.1,433.1,65.8z"/>
|
||||
<path class="st11" d="M482.8,73.3c-0.4,0-0.8-0.2-1-0.6l-8-12.3l-4.3,4.6v7.7c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0-0.1,0h-6
|
||||
c-0.3,0-0.6-0.2-0.7-0.5c0,0,0,0,0-0.1V32.6c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0.1,0h6c0.3,0,0.6,0.2,0.7,0.5c0,0,0,0,0,0.1v23.8
|
||||
l10.8-11.8c0.3-0.4,0.8-0.6,1.2-0.6h6.7c0.2,0,0.4,0.1,0.4,0.3c0,0.1,0,0.3-0.1,0.3l-10.1,10.7L490,72.7c0.1,0.2,0.1,0.4,0,0.5
|
||||
c-0.1,0.1-0.2,0.1-0.3,0.1H482.8L482.8,73.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
@ -12,7 +12,7 @@ install:
|
||||
- yarn global add wait-on
|
||||
# Install Codecov
|
||||
- yarn install
|
||||
- cp cypress.env.template.json cypress.env.json
|
||||
- cp backend/.env.template backend/.env
|
||||
|
||||
before_script:
|
||||
- docker-compose -f docker-compose.yml build --parallel
|
||||
|
||||
@ -55,7 +55,11 @@ Check out the [contribution guideline](./CONTRIBUTING.md), too!
|
||||
|
||||
## Attributions
|
||||
|
||||
Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)
|
||||
Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
|
||||
|
||||
Browser compatibility testing with [BrowserStack](https://www.browserstack.com/).
|
||||
|
||||
<img alt="BrowserStack Logo" src=".gitbook/assets/browserstack-logo.svg" width="256">
|
||||
|
||||
## License
|
||||
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
|
||||
|
||||
@ -33,12 +33,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^16.1.8",
|
||||
"@sentry/node": "^5.9.0",
|
||||
"@sentry/node": "^5.10.1",
|
||||
"apollo-cache-inmemory": "~1.6.3",
|
||||
"apollo-client": "~2.6.4",
|
||||
"apollo-link-context": "~1.0.19",
|
||||
"apollo-link-http": "~1.5.16",
|
||||
"apollo-server": "~2.9.12",
|
||||
"apollo-server": "~2.9.13",
|
||||
"apollo-server-express": "^2.9.7",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"bcryptjs": "~2.4.3",
|
||||
@ -62,7 +62,7 @@
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.3",
|
||||
"metascraper": "^5.8.8",
|
||||
"metascraper": "^5.8.9",
|
||||
"metascraper-audio": "^5.8.7",
|
||||
"metascraper-author": "^5.8.7",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
@ -76,15 +76,15 @@
|
||||
"metascraper-soundcloud": "^5.8.9",
|
||||
"metascraper-title": "^5.8.7",
|
||||
"metascraper-url": "^5.8.7",
|
||||
"metascraper-video": "^5.8.7",
|
||||
"metascraper-video": "^5.8.9",
|
||||
"metascraper-youtube": "^5.8.9",
|
||||
"minimatch": "^3.0.4",
|
||||
"mustache": "^3.1.0",
|
||||
"neo4j-driver": "~1.7.6",
|
||||
"neo4j-graphql-js": "^2.9.3",
|
||||
"neo4j-graphql-js": "^2.10.0",
|
||||
"neode": "^0.3.3",
|
||||
"node-fetch": "~2.6.0",
|
||||
"nodemailer": "^6.3.1",
|
||||
"nodemailer": "^6.4.1",
|
||||
"nodemailer-html-to-text": "^3.1.0",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.0",
|
||||
@ -98,12 +98,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.7.4",
|
||||
"@babel/core": "~7.7.4",
|
||||
"@babel/core": "~7.7.5",
|
||||
"@babel/node": "~7.7.4",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.7.4",
|
||||
"@babel/preset-env": "~7.7.4",
|
||||
"@babel/register": "~7.7.0",
|
||||
"apollo-server-testing": "~2.9.12",
|
||||
"apollo-server-testing": "~2.9.13",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.3",
|
||||
"babel-jest": "~24.9.0",
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import { v1 as neo4j } from 'neo4j-driver'
|
||||
import CONFIG from './../config'
|
||||
import setupNeode from './neode'
|
||||
import Neode from 'neode'
|
||||
import models from '../models'
|
||||
|
||||
let driver
|
||||
const defaultOptions = {
|
||||
uri: CONFIG.NEO4J_URI,
|
||||
username: CONFIG.NEO4J_USERNAME,
|
||||
password: CONFIG.NEO4J_PASSWORD,
|
||||
}
|
||||
|
||||
export function getDriver(options = {}) {
|
||||
const {
|
||||
uri = CONFIG.NEO4J_URI,
|
||||
username = CONFIG.NEO4J_USERNAME,
|
||||
password = CONFIG.NEO4J_PASSWORD,
|
||||
} = options
|
||||
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||
if (!driver) {
|
||||
driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
|
||||
}
|
||||
@ -17,10 +19,11 @@ export function getDriver(options = {}) {
|
||||
}
|
||||
|
||||
let neodeInstance
|
||||
export function neode() {
|
||||
export function getNeode(options = {}) {
|
||||
if (!neodeInstance) {
|
||||
const { NEO4J_URI: uri, NEO4J_USERNAME: username, NEO4J_PASSWORD: password } = CONFIG
|
||||
neodeInstance = setupNeode({ uri, username, password })
|
||||
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||
neodeInstance = new Neode(uri, username, password).with(models)
|
||||
return neodeInstance
|
||||
}
|
||||
return neodeInstance
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import Neode from 'neode'
|
||||
import models from '../models'
|
||||
|
||||
export default function setupNeode(options) {
|
||||
const { uri, username, password } = options
|
||||
const neodeInstance = new Neode(uri, username, password)
|
||||
neodeInstance.with(models)
|
||||
return neodeInstance
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory from '../seed/factories/index'
|
||||
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||
import decode from './decode'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let server
|
||||
@ -11,7 +11,7 @@ let hashtagingUser
|
||||
let authenticatedUser
|
||||
const factory = Factory()
|
||||
const driver = getDriver()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
const categoryIds = ['cat9']
|
||||
const createPostMutation = gql`
|
||||
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
||||
@ -36,7 +36,7 @@ beforeAll(() => {
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode: instance,
|
||||
neode,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
@ -48,14 +48,14 @@ beforeAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
hashtagingUser = await instance.create('User', {
|
||||
hashtagingUser = await neode.create('User', {
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
await instance.create('Category', {
|
||||
await neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let server
|
||||
@ -11,7 +11,7 @@ let notifiedUser
|
||||
let authenticatedUser
|
||||
const factory = Factory()
|
||||
const driver = getDriver()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
const categoryIds = ['cat9']
|
||||
const createPostMutation = gql`
|
||||
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
||||
@ -44,7 +44,7 @@ beforeAll(() => {
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode: instance,
|
||||
neode: neode,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
@ -56,14 +56,14 @@ beforeAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
notifiedUser = await instance.create('User', {
|
||||
notifiedUser = await neode.create('User', {
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
await instance.create('Category', {
|
||||
await neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
@ -146,7 +146,7 @@ describe('notifications', () => {
|
||||
describe('commenter is not me', () => {
|
||||
beforeEach(async () => {
|
||||
commentContent = 'Commenters comment.'
|
||||
commentAuthor = await instance.create('User', {
|
||||
commentAuthor = await neode.create('User', {
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
slug: 'mrs-comment',
|
||||
@ -228,7 +228,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
postAuthor = await instance.create('User', {
|
||||
postAuthor = await neode.create('User', {
|
||||
id: 'postAuthor',
|
||||
name: 'Mrs Post',
|
||||
slug: 'mrs-post',
|
||||
@ -371,7 +371,7 @@ describe('notifications', () => {
|
||||
expect(readAfter).toEqual(false)
|
||||
})
|
||||
|
||||
it('updates the `createdAt` attribute', async () => {
|
||||
it('does not update the `createdAt` attribute', async () => {
|
||||
await createPostAction()
|
||||
await markAsReadAction()
|
||||
const {
|
||||
@ -432,7 +432,7 @@ describe('notifications', () => {
|
||||
beforeEach(async () => {
|
||||
commentContent =
|
||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
commentAuthor = await instance.create('User', {
|
||||
commentAuthor = await neode.create('User', {
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
slug: 'mrs-comment',
|
||||
@ -442,7 +442,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
it('sends only one notification with reason mentioned_in_comment', async () => {
|
||||
postAuthor = await instance.create('User', {
|
||||
postAuthor = await neode.create('User', {
|
||||
id: 'MrPostAuthor',
|
||||
name: 'Mr Author',
|
||||
slug: 'mr-author',
|
||||
@ -518,7 +518,7 @@ describe('notifications', () => {
|
||||
await postAuthor.relateTo(notifiedUser, 'blocked')
|
||||
commentContent =
|
||||
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||
commentAuthor = await instance.create('User', {
|
||||
commentAuthor = await neode.create('User', {
|
||||
id: 'commentAuthor',
|
||||
name: 'Mrs Comment',
|
||||
slug: 'mrs-comment',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { gql } from '../helpers/jest'
|
||||
import Factory from '../seed/factories'
|
||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||
import { neode } from '../bootstrap/neo4j'
|
||||
import { getNeode } from '../bootstrap/neo4j'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
const allowExternalErrors = true
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
const isAuthenticated = rule({
|
||||
cache: 'contextual',
|
||||
@ -36,7 +36,7 @@ const isMyOwn = rule({
|
||||
const isMySocialMedia = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_, args, { user }) => {
|
||||
let socialMedia = await instance.find('SocialMedia', args.id)
|
||||
let socialMedia = await neode.find('SocialMedia', args.id)
|
||||
socialMedia = await socialMedia.toJson()
|
||||
return socialMedia.ownedBy.node.id === user.id
|
||||
})
|
||||
@ -112,7 +112,7 @@ export default shield(
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
report: isAuthenticated,
|
||||
fileReport: isAuthenticated,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
UpdateSocialMedia: isMySocialMedia,
|
||||
DeleteSocialMedia: isMySocialMedia,
|
||||
@ -125,8 +125,7 @@ export default shield(
|
||||
shout: isAuthenticated,
|
||||
unshout: isAuthenticated,
|
||||
changePassword: isAuthenticated,
|
||||
enable: isModerator,
|
||||
disable: isModerator,
|
||||
review: isModerator,
|
||||
CreateComment: isAuthenticated,
|
||||
UpdateComment: isAuthor,
|
||||
DeleteComment: isAuthor,
|
||||
|
||||
@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory from '../seed/factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const instance = getNeode()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -8,14 +8,8 @@ const factory = Factory()
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let graphqlQuery
|
||||
const categoryIds = ['cat9']
|
||||
let authenticatedUser
|
||||
let user
|
||||
let moderator
|
||||
let troll
|
||||
let query, graphqlQuery, authenticatedUser, user, moderator, troll
|
||||
|
||||
const action = () => {
|
||||
return query({ query: graphqlQuery })
|
||||
@ -38,18 +32,17 @@ beforeAll(async () => {
|
||||
avatar: '/some/offensive/avatar.jpg',
|
||||
about: 'This self description is very offensive',
|
||||
}),
|
||||
neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
}),
|
||||
])
|
||||
|
||||
user = users[0]
|
||||
moderator = users[1]
|
||||
troll = users[2]
|
||||
|
||||
await neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
user.relateTo(troll, 'following'),
|
||||
factory.create('Post', {
|
||||
@ -70,33 +63,32 @@ beforeAll(async () => {
|
||||
}),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
const resources = await Promise.all([
|
||||
factory.create('Comment', {
|
||||
author: user,
|
||||
id: 'c2',
|
||||
postId: 'p3',
|
||||
content: 'Enabled comment on public post',
|
||||
}),
|
||||
factory.create('Post', {
|
||||
id: 'p2',
|
||||
author: troll,
|
||||
title: 'Disabled post',
|
||||
content: 'This is an offensive post content',
|
||||
contentExcerpt: 'This is an offensive post content',
|
||||
image: '/some/offensive/image.jpg',
|
||||
deleted: false,
|
||||
categoryIds,
|
||||
}),
|
||||
factory.create('Comment', {
|
||||
id: 'c1',
|
||||
author: troll,
|
||||
postId: 'p3',
|
||||
content: 'Disabled comment',
|
||||
contentExcerpt: 'Disabled comment',
|
||||
}),
|
||||
])
|
||||
|
||||
await factory.create('Post', {
|
||||
id: 'p2',
|
||||
author: troll,
|
||||
title: 'Disabled post',
|
||||
content: 'This is an offensive post content',
|
||||
contentExcerpt: 'This is an offensive post content',
|
||||
image: '/some/offensive/image.jpg',
|
||||
deleted: false,
|
||||
categoryIds,
|
||||
})
|
||||
await factory.create('Comment', {
|
||||
id: 'c1',
|
||||
author: troll,
|
||||
postId: 'p3',
|
||||
content: 'Disabled comment',
|
||||
contentExcerpt: 'Disabled comment',
|
||||
})
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
@ -108,20 +100,57 @@ beforeAll(async () => {
|
||||
})
|
||||
const client = createTestClient(server)
|
||||
query = client.query
|
||||
mutate = client.mutate
|
||||
|
||||
authenticatedUser = await moderator.toJson()
|
||||
const disableMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
disable(id: $id)
|
||||
}
|
||||
`
|
||||
await Promise.all([
|
||||
mutate({ mutation: disableMutation, variables: { id: 'c1' } }),
|
||||
mutate({ mutation: disableMutation, variables: { id: 'u2' } }),
|
||||
mutate({ mutation: disableMutation, variables: { id: 'p2' } }),
|
||||
const trollingPost = resources[1]
|
||||
const trollingComment = resources[2]
|
||||
|
||||
const reports = await Promise.all([
|
||||
factory.create('Report'),
|
||||
factory.create('Report'),
|
||||
factory.create('Report'),
|
||||
])
|
||||
const reportAgainstTroll = reports[0]
|
||||
const reportAgainstTrollingPost = reports[1]
|
||||
const reportAgainstTrollingComment = reports[2]
|
||||
|
||||
const reportVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'I am what I am !!!',
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
reportAgainstTroll.relateTo(user, 'filed', { ...reportVariables, resourceId: 'u2' }),
|
||||
reportAgainstTroll.relateTo(troll, 'belongsTo'),
|
||||
reportAgainstTrollingPost.relateTo(user, 'filed', { ...reportVariables, resourceId: 'p2' }),
|
||||
reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'),
|
||||
reportAgainstTrollingComment.relateTo(moderator, 'filed', {
|
||||
...reportVariables,
|
||||
resourceId: 'c1',
|
||||
}),
|
||||
reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
|
||||
])
|
||||
|
||||
const disableVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
disable: true,
|
||||
closed: false,
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
reportAgainstTroll.relateTo(moderator, 'reviewed', { ...disableVariables, resourceId: 'u2' }),
|
||||
troll.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
reportAgainstTrollingPost.relateTo(moderator, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'p2',
|
||||
}),
|
||||
trollingPost.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
reportAgainstTrollingComment.relateTo(moderator, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'c1',
|
||||
}),
|
||||
trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -61,31 +61,58 @@ const validateUpdatePost = async (resolve, root, args, context, info) => {
|
||||
|
||||
const validateReport = async (resolve, root, args, context, info) => {
|
||||
const { resourceId } = args
|
||||
const { user, driver } = context
|
||||
const { user } = context
|
||||
if (resourceId === user.id) throw new Error('You cannot report yourself!')
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
|
||||
const validateReview = async (resolve, root, args, context, info) => {
|
||||
const { resourceId } = args
|
||||
let existingReportedResource
|
||||
const { user, driver } = context
|
||||
if (resourceId === user.id) throw new Error('You cannot review yourself!')
|
||||
const session = driver.session()
|
||||
try {
|
||||
const reportQueryRes = await session.run(
|
||||
const reportReadTxPromise = session.writeTransaction(async txc => {
|
||||
const validateReviewTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (:User {id:$submitterId})-[:REPORTED]->(resource {id:$resourceId})
|
||||
RETURN labels(resource)[0] as label
|
||||
`,
|
||||
MATCH (resource {id: $resourceId})
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
|
||||
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
|
||||
RETURN labels(resource)[0] AS label, author, filed
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
submitterId: user.id,
|
||||
},
|
||||
)
|
||||
const [existingReportedResource] = reportQueryRes.records.map(record => {
|
||||
return {
|
||||
label: record.get('label'),
|
||||
}
|
||||
})
|
||||
|
||||
if (existingReportedResource) throw new Error(`${existingReportedResource.label}`)
|
||||
return resolve(root, args, context, info)
|
||||
return validateReviewTransactionResponse.records.map(record => ({
|
||||
label: record.get('label'),
|
||||
author: record.get('author'),
|
||||
filed: record.get('filed'),
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const txResult = await reportReadTxPromise
|
||||
existingReportedResource = txResult
|
||||
if (!existingReportedResource || !existingReportedResource.length)
|
||||
throw new Error(`Resource not found or is not a Post|Comment|User!`)
|
||||
existingReportedResource = existingReportedResource[0]
|
||||
if (!existingReportedResource.filed)
|
||||
throw new Error(
|
||||
`Before starting the review process, please report the ${existingReportedResource.label}!`,
|
||||
)
|
||||
const authorId =
|
||||
existingReportedResource.label !== 'User' && existingReportedResource.author
|
||||
? existingReportedResource.author.properties.id
|
||||
: null
|
||||
if (authorId && authorId === user.id)
|
||||
throw new Error(`You cannot review your own ${existingReportedResource.label}!`)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
|
||||
export default {
|
||||
@ -94,6 +121,7 @@ export default {
|
||||
UpdateComment: validateUpdateComment,
|
||||
CreatePost: validatePost,
|
||||
UpdatePost: validateUpdatePost,
|
||||
report: validateReport,
|
||||
fileReport: validateReport,
|
||||
review: validateReview,
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
let mutate, authenticatedUser, user
|
||||
let authenticatedUser,
|
||||
mutate,
|
||||
users,
|
||||
offensivePost,
|
||||
reportVariables,
|
||||
disableVariables,
|
||||
reportingUser,
|
||||
moderatingUser,
|
||||
commentingUser
|
||||
|
||||
const createCommentMutation = gql`
|
||||
mutation($id: ID, $postId: ID!, $content: String!) {
|
||||
CreateComment(id: $id, postId: $postId, content: $content) {
|
||||
@ -23,8 +32,14 @@ const updateCommentMutation = gql`
|
||||
}
|
||||
`
|
||||
const createPostMutation = gql`
|
||||
mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
mutation($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) {
|
||||
CreatePost(
|
||||
id: $id
|
||||
title: $title
|
||||
content: $content
|
||||
language: $language
|
||||
categoryIds: $categoryIds
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@ -37,7 +52,25 @@ const updatePostMutation = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const reportMutation = gql`
|
||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
fileReport(
|
||||
resourceId: $resourceId
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const reviewMutation = gql`
|
||||
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
|
||||
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeAll(() => {
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
@ -52,13 +85,42 @@ beforeAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await factory.create('User', {
|
||||
id: 'user-id',
|
||||
})
|
||||
await factory.create('Post', {
|
||||
id: 'post-4-commenting',
|
||||
authorId: 'user-id',
|
||||
})
|
||||
users = await Promise.all([
|
||||
factory.create('User', {
|
||||
id: 'reporting-user',
|
||||
}),
|
||||
factory.create('User', {
|
||||
id: 'moderating-user',
|
||||
role: 'moderator',
|
||||
}),
|
||||
factory.create('User', {
|
||||
id: 'commenting-user',
|
||||
}),
|
||||
])
|
||||
reportVariables = {
|
||||
resourceId: 'whatever',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'Violates code of conduct !!!',
|
||||
}
|
||||
disableVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
disable: true,
|
||||
closed: false,
|
||||
}
|
||||
reportingUser = users[0]
|
||||
moderatingUser = users[1]
|
||||
commentingUser = users[2]
|
||||
const posts = await Promise.all([
|
||||
factory.create('Post', {
|
||||
id: 'offensive-post',
|
||||
authorId: 'moderating-user',
|
||||
}),
|
||||
factory.create('Post', {
|
||||
id: 'post-4-commenting',
|
||||
authorId: 'commenting-user',
|
||||
}),
|
||||
])
|
||||
offensivePost = posts[0]
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -72,7 +134,7 @@ describe('validateCreateComment', () => {
|
||||
postId: 'whatever',
|
||||
content: '',
|
||||
}
|
||||
authenticatedUser = await user.toJson()
|
||||
authenticatedUser = await commentingUser.toJson()
|
||||
})
|
||||
|
||||
it('throws an error if content is empty', async () => {
|
||||
@ -114,13 +176,13 @@ describe('validateCreateComment', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('Comment', {
|
||||
id: 'comment-id',
|
||||
authorId: 'user-id',
|
||||
authorId: 'commenting-user',
|
||||
})
|
||||
updateCommentVariables = {
|
||||
id: 'whatever',
|
||||
content: '',
|
||||
}
|
||||
authenticatedUser = await user.toJson()
|
||||
authenticatedUser = await commentingUser.toJson()
|
||||
})
|
||||
|
||||
it('throws an error if content is empty', async () => {
|
||||
@ -151,7 +213,7 @@ describe('validateCreateComment', () => {
|
||||
title: 'I am a title',
|
||||
content: 'Some content',
|
||||
}
|
||||
authenticatedUser = await user.toJson()
|
||||
authenticatedUser = await commentingUser.toJson()
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
@ -242,3 +304,97 @@ describe('validateCreateComment', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('validateReport', () => {
|
||||
it('throws an error if a user tries to report themself', async () => {
|
||||
authenticatedUser = await reportingUser.toJson()
|
||||
reportVariables = { ...reportVariables, resourceId: 'reporting-user' }
|
||||
await expect(
|
||||
mutate({ mutation: reportMutation, variables: reportVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { fileReport: null },
|
||||
errors: [{ message: 'You cannot report yourself!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('validateReview', () => {
|
||||
beforeEach(async () => {
|
||||
const reportAgainstModerator = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstModerator.relateTo(reportingUser, 'filed', {
|
||||
...reportVariables,
|
||||
resourceId: 'moderating-user',
|
||||
}),
|
||||
reportAgainstModerator.relateTo(moderatingUser, 'belongsTo'),
|
||||
])
|
||||
authenticatedUser = await moderatingUser.toJson()
|
||||
})
|
||||
|
||||
it('throws an error if a user tries to review a report against them', async () => {
|
||||
disableVariables = { ...disableVariables, resourceId: 'moderating-user' }
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: null },
|
||||
errors: [{ message: 'You cannot review yourself!' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error for invaild resource', async () => {
|
||||
disableVariables = { ...disableVariables, resourceId: 'non-existent-resource' }
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: null },
|
||||
errors: [{ message: 'Resource not found or is not a Post|Comment|User!' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error if no report exists', async () => {
|
||||
disableVariables = { ...disableVariables, resourceId: 'offensive-post' }
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: null },
|
||||
errors: [{ message: 'Before starting the review process, please report the Post!' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error if a moderator tries to review their own resource(Post|Comment)', async () => {
|
||||
const reportAgainstOffensivePost = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstOffensivePost.relateTo(reportingUser, 'filed', {
|
||||
...reportVariables,
|
||||
resourceId: 'offensive-post',
|
||||
}),
|
||||
reportAgainstOffensivePost.relateTo(offensivePost, 'belongsTo'),
|
||||
])
|
||||
disableVariables = { ...disableVariables, resourceId: 'offensive-post' }
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: null },
|
||||
errors: [{ message: 'You cannot review your own Post!' }],
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a resource that is not a (Comment|Post|User) ', () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all([factory.create('Tag', { id: 'tag-id' })])
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
disableVariables = {
|
||||
...disableVariables,
|
||||
resourceId: 'tag-id',
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: null },
|
||||
errors: [{ message: 'Resource not found or is not a Post|Comment|User!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -25,12 +25,6 @@ module.exports = {
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
disabledBy: {
|
||||
type: 'relationship',
|
||||
relationship: 'DISABLED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
notified: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
|
||||
@ -17,12 +17,6 @@ module.exports = {
|
||||
image: { type: 'string', allow: [null] },
|
||||
deleted: { type: 'boolean', default: false },
|
||||
disabled: { type: 'boolean', default: false },
|
||||
disabledBy: {
|
||||
type: 'relationship',
|
||||
relationship: 'DISABLED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
notified: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
@ -45,4 +39,5 @@ module.exports = {
|
||||
default: () => new Date().toISOString(),
|
||||
},
|
||||
language: { type: 'string', allow: [null] },
|
||||
imageAspectRatio: { type: 'float', default: 1.0 },
|
||||
}
|
||||
|
||||
52
backend/src/models/Report.js
Normal file
52
backend/src/models/Report.js
Normal file
@ -0,0 +1,52 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
module.exports = {
|
||||
id: { type: 'string', primary: true, default: uuid },
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
rule: { type: 'string', default: 'latestReviewUpdatedAtRules' },
|
||||
closed: { type: 'boolean', default: false },
|
||||
belongsTo: {
|
||||
type: 'relationship',
|
||||
relationship: 'BELONGS_TO',
|
||||
target: ['User', 'Comment', 'Post'],
|
||||
direction: 'out',
|
||||
},
|
||||
filed: {
|
||||
type: 'relationship',
|
||||
relationship: 'FILED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
resourceId: { type: 'string', primary: true, default: uuid },
|
||||
reasonCategory: {
|
||||
type: 'string',
|
||||
valid: [
|
||||
'other',
|
||||
'discrimination_etc',
|
||||
'pornographic_content_links',
|
||||
'glorific_trivia_of_cruel_inhuman_acts',
|
||||
'doxing',
|
||||
'intentional_intimidation_stalking_persecution',
|
||||
'advert_products_services_commercial',
|
||||
'criminal_behavior_violation_german_law',
|
||||
],
|
||||
invalid: [null],
|
||||
},
|
||||
reasonDescription: { type: 'string', allow: [null] },
|
||||
},
|
||||
},
|
||||
reviewed: {
|
||||
type: 'relationship',
|
||||
relationship: 'REVIEWED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
disable: { type: 'boolean', default: false },
|
||||
closed: { type: 'boolean', default: false },
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -42,12 +42,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' },
|
||||
disabledBy: {
|
||||
type: 'relationship',
|
||||
relationship: 'DISABLED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
rewarded: {
|
||||
type: 'relationship',
|
||||
relationship: 'REWARDED',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { neode } from '../bootstrap/neo4j'
|
||||
import { getNeode } from '../bootstrap/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
@ -10,7 +10,7 @@ afterEach(async () => {
|
||||
|
||||
describe('role', () => {
|
||||
it('defaults to `user`', async () => {
|
||||
const user = await instance.create('User', { name: 'John' })
|
||||
const user = await neode.create('User', { name: 'John' })
|
||||
await expect(user.toJson()).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
role: 'user',
|
||||
@ -21,7 +21,7 @@ describe('role', () => {
|
||||
|
||||
describe('slug', () => {
|
||||
it('normalizes to lowercase letters', async () => {
|
||||
const user = await instance.create('User', { slug: 'Matt' })
|
||||
const user = await neode.create('User', { slug: 'Matt' })
|
||||
await expect(user.toJson()).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
slug: 'matt',
|
||||
@ -30,9 +30,9 @@ describe('slug', () => {
|
||||
})
|
||||
|
||||
it('must be unique', async done => {
|
||||
await instance.create('User', { slug: 'Matt' })
|
||||
await neode.create('User', { slug: 'Matt' })
|
||||
try {
|
||||
await expect(instance.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
|
||||
await expect(neode.create('User', { slug: 'Matt' })).rejects.toThrow('already exists')
|
||||
done()
|
||||
} catch (error) {
|
||||
throw new Error(`
|
||||
@ -54,7 +54,7 @@ describe('slug', () => {
|
||||
|
||||
describe('characters', () => {
|
||||
const createUser = attrs => {
|
||||
return instance.create('User', attrs).then(user => user.toJson())
|
||||
return neode.create('User', attrs).then(user => user.toJson())
|
||||
}
|
||||
|
||||
it('-', async () => {
|
||||
|
||||
@ -12,4 +12,5 @@ export default {
|
||||
Tag: require('./Tag.js'),
|
||||
Location: require('./Location.js'),
|
||||
Donations: require('./Donations.js'),
|
||||
Report: require('./Report.js'),
|
||||
}
|
||||
|
||||
@ -17,7 +17,9 @@ export default makeAugmentedSchema({
|
||||
'Location',
|
||||
'SocialMedia',
|
||||
'NOTIFIED',
|
||||
'REPORTED',
|
||||
'FILED',
|
||||
'REVIEWED',
|
||||
'Report',
|
||||
'Donations',
|
||||
],
|
||||
},
|
||||
|
||||
@ -78,7 +78,6 @@ export default {
|
||||
hasOne: {
|
||||
author: '<-[:WROTE]-(related:User)',
|
||||
post: '-[:COMMENTS]->(related:Post)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@ import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let mutate, query, authenticatedUser, variables
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { gql } from '../../helpers/jest'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { neode } from '../../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../../bootstrap/neo4j'
|
||||
|
||||
export const undefinedToNullResolver = list => {
|
||||
const resolvers = {}
|
||||
@ -11,7 +11,7 @@ export const undefinedToNullResolver = list => {
|
||||
}
|
||||
|
||||
export default function Resolver(type, options = {}) {
|
||||
const instance = neode()
|
||||
const instance = getNeode()
|
||||
const {
|
||||
idAttribute = 'id',
|
||||
undefinedToNull = [],
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,47 +1,51 @@
|
||||
const transformReturnType = record => {
|
||||
return {
|
||||
...record.get('review').properties,
|
||||
report: record.get('report').properties,
|
||||
resource: {
|
||||
__typename: record.get('type'),
|
||||
...record.get('resource').properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
disable: async (object, params, { user, driver }) => {
|
||||
const { id } = params
|
||||
const { id: userId } = user
|
||||
const cypher = `
|
||||
MATCH (u:User {id: $userId})
|
||||
MATCH (resource {id: $id})
|
||||
WHERE resource:User OR resource:Comment OR resource:Post
|
||||
SET resource.disabled = true
|
||||
MERGE (resource)<-[:DISABLED]-(u)
|
||||
RETURN resource {.id}
|
||||
`
|
||||
review: async (_object, params, context, _resolveInfo) => {
|
||||
const { user: moderator, driver } = context
|
||||
|
||||
let createdRelationshipWithNestedAttributes = null // return value
|
||||
const session = driver.session()
|
||||
try {
|
||||
const res = await session.run(cypher, { id, userId })
|
||||
const [resource] = res.records.map(record => {
|
||||
return record.get('resource')
|
||||
const cypher = `
|
||||
MATCH (moderator:User {id: $moderatorId})
|
||||
MATCH (resource {id: $params.resourceId})<-[:BELONGS_TO]-(report:Report {closed: false})
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
MERGE (report)<-[review:REVIEWED]-(moderator)
|
||||
ON CREATE SET review.createdAt = $dateTime, review.updatedAt = review.createdAt
|
||||
ON MATCH SET review.updatedAt = $dateTime
|
||||
SET review.disable = $params.disable
|
||||
SET report.updatedAt = $dateTime, report.closed = $params.closed
|
||||
SET resource.disabled = review.disable
|
||||
|
||||
RETURN review, report, resource, labels(resource)[0] AS type
|
||||
`
|
||||
const reviewWriteTxResultPromise = session.writeTransaction(async txc => {
|
||||
const reviewTransactionResponse = await txc.run(cypher, {
|
||||
params,
|
||||
moderatorId: moderator.id,
|
||||
dateTime: new Date().toISOString(),
|
||||
})
|
||||
return reviewTransactionResponse.records.map(transformReturnType)
|
||||
})
|
||||
if (!resource) return null
|
||||
return resource.id
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
enable: async (object, params, { user, driver }) => {
|
||||
const { id } = params
|
||||
const cypher = `
|
||||
MATCH (resource {id: $id})<-[d:DISABLED]-()
|
||||
SET resource.disabled = false
|
||||
DELETE d
|
||||
RETURN resource {.id}
|
||||
`
|
||||
const session = driver.session()
|
||||
try {
|
||||
const res = await session.run(cypher, { id })
|
||||
const [resource] = res.records.map(record => {
|
||||
return record.get('resource')
|
||||
})
|
||||
if (!resource) return null
|
||||
return resource.id
|
||||
const txResult = await reviewWriteTxResultPromise
|
||||
if (!txResult[0]) return null
|
||||
createdRelationshipWithNestedAttributes = txResult[0]
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
return createdRelationshipWithNestedAttributes
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,52 +1,60 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
let query, mutate, authenticatedUser, variables, moderator, nonModerator
|
||||
let mutate,
|
||||
authenticatedUser,
|
||||
disableVariables,
|
||||
enableVariables,
|
||||
moderator,
|
||||
nonModerator,
|
||||
closeReportVariables
|
||||
|
||||
const disableMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
disable(id: $id)
|
||||
}
|
||||
`
|
||||
const enableMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
enable(id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
const commentQuery = gql`
|
||||
query($id: ID!) {
|
||||
Comment(id: $id) {
|
||||
id
|
||||
disabled
|
||||
disabledBy {
|
||||
id
|
||||
const reviewMutation = gql`
|
||||
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
|
||||
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
|
||||
createdAt
|
||||
updatedAt
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
... on Post {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const postQuery = gql`
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
id
|
||||
disabled
|
||||
disabledBy {
|
||||
report {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
closed
|
||||
reviewed {
|
||||
createdAt
|
||||
moderator {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('moderate resources', () => {
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
await factory.cleanDatabase()
|
||||
authenticatedUser = undefined
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
@ -58,11 +66,19 @@ describe('moderate resources', () => {
|
||||
},
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
query = createTestClient(server).query
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
variables = {}
|
||||
disableVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
disable: true,
|
||||
closed: false,
|
||||
}
|
||||
enableVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
disable: false,
|
||||
closed: false,
|
||||
}
|
||||
authenticatedUser = null
|
||||
moderator = await factory.create('User', {
|
||||
id: 'moderator-id',
|
||||
@ -71,155 +87,392 @@ describe('moderate resources', () => {
|
||||
password: '1234',
|
||||
role: 'moderator',
|
||||
})
|
||||
nonModerator = await factory.create('User', {
|
||||
id: 'non-moderator',
|
||||
name: 'Non Moderator',
|
||||
email: 'non.moderator@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('disable', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
id: 'some-resource',
|
||||
}
|
||||
})
|
||||
describe('review to close report, leaving resource enabled', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('non moderator', () => {
|
||||
beforeEach(async () => {
|
||||
nonModerator = await factory.create('User', {
|
||||
id: 'non-moderator',
|
||||
name: 'Non Moderator',
|
||||
email: 'non.moderator@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
authenticatedUser = await nonModerator.toJson()
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await nonModerator.toJson()
|
||||
})
|
||||
|
||||
it('non-moderator receives an authorization error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
it('throws authorization error', async () => {
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderator', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
const questionablePost = await factory.create('Post', {
|
||||
id: 'should-i-be-disabled',
|
||||
})
|
||||
const reportAgainstQuestionablePost = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstQuestionablePost.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'should-i-be-disabled',
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!",
|
||||
}),
|
||||
reportAgainstQuestionablePost.relateTo(questionablePost, 'belongsTo'),
|
||||
])
|
||||
closeReportVariables = {
|
||||
resourceId: 'should-i-be-disabled',
|
||||
disable: false,
|
||||
closed: true,
|
||||
}
|
||||
})
|
||||
|
||||
it('report can be closed without disabling resource', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: closeReportVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Post', id: 'should-i-be-disabled', disabled: false },
|
||||
report: { id: expect.any(String), closed: true },
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates only one review for multiple reviews by the same moderator on same resource', async () => {
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: reviewMutation,
|
||||
variables: { ...disableVariables, resourceId: 'should-i-be-disabled' },
|
||||
}),
|
||||
mutate({
|
||||
mutation: reviewMutation,
|
||||
variables: { ...enableVariables, resourceId: 'should-i-be-disabled' },
|
||||
}),
|
||||
])
|
||||
const cypher =
|
||||
'MATCH (:Report)<-[review:REVIEWED]-(moderator:User {id: "moderator-id"}) RETURN review'
|
||||
const reviews = await neode.cypher(cypher)
|
||||
expect(reviews.records).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('updates the updatedAt attribute', async () => {
|
||||
const [firstReview, secondReview] = await Promise.all([
|
||||
mutate({
|
||||
mutation: reviewMutation,
|
||||
variables: { ...disableVariables, resourceId: 'should-i-be-disabled' },
|
||||
}),
|
||||
mutate({
|
||||
mutation: reviewMutation,
|
||||
variables: { ...enableVariables, resourceId: 'should-i-be-disabled' },
|
||||
}),
|
||||
])
|
||||
expect(firstReview.data.review.updatedAt).toBeTruthy()
|
||||
expect(Date.parse(firstReview.data.review.updatedAt)).toEqual(expect.any(Number))
|
||||
expect(secondReview.data.review.updatedAt).toBeTruthy()
|
||||
expect(Date.parse(secondReview.data.review.updatedAt)).toEqual(expect.any(Number))
|
||||
expect(firstReview.data.review.updatedAt).not.toEqual(secondReview.data.review.updatedAt)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('review to disable', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await nonModerator.toJson()
|
||||
})
|
||||
|
||||
it('non-moderator receives an authorization error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderator', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
})
|
||||
|
||||
describe('moderate a comment', () => {
|
||||
beforeEach(async () => {
|
||||
const trollingComment = await factory.create('Comment', {
|
||||
id: 'comment-id',
|
||||
})
|
||||
const reportAgainstTrollingComment = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'comment-id',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
}),
|
||||
reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
|
||||
])
|
||||
disableVariables = {
|
||||
...disableVariables,
|
||||
resourceId: 'comment-id',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns disabled resource id', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Comment', id: 'comment-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates .disabled on comment', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: true } },
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('can be closed with one review', async () => {
|
||||
closeReportVariables = {
|
||||
...disableVariables,
|
||||
closed: true,
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: closeReportVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Comment', id: 'comment-id' },
|
||||
report: { id: expect.any(String), closed: true },
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderator', () => {
|
||||
describe('moderate a post', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
const trollingPost = await factory.create('Post', {
|
||||
id: 'post-id',
|
||||
})
|
||||
const reportAgainstTrollingPost = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'post-id',
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!",
|
||||
}),
|
||||
reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'),
|
||||
])
|
||||
disableVariables = {
|
||||
...disableVariables,
|
||||
resourceId: 'post-id',
|
||||
}
|
||||
})
|
||||
|
||||
describe('moderate a resource that is not a (Comment|Post|User) ', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
id: 'sample-tag-id',
|
||||
}
|
||||
await factory.create('Tag', { id: 'sample-tag-id' })
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: null },
|
||||
})
|
||||
it('returns disabled resource id', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Post', id: 'post-id' },
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a comment', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {}
|
||||
await factory.create('Comment', {
|
||||
id: 'comment-id',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns disabled resource id', async () => {
|
||||
variables = { id: 'comment-id' }
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'comment-id' },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('changes .disabledBy', async () => {
|
||||
variables = { id: 'comment-id' }
|
||||
const before = { data: { Comment: [{ id: 'comment-id', disabledBy: null }] } }
|
||||
const expected = {
|
||||
data: { Comment: [{ id: 'comment-id', disabledBy: { id: 'moderator-id' } }] },
|
||||
}
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(before)
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'comment-id' },
|
||||
})
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected)
|
||||
})
|
||||
|
||||
it('updates .disabled on comment', async () => {
|
||||
variables = { id: 'comment-id' }
|
||||
const before = { data: { Comment: [{ id: 'comment-id', disabled: false }] } }
|
||||
const expected = { data: { Comment: [{ id: 'comment-id', disabled: true }] } }
|
||||
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(before)
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'comment-id' },
|
||||
})
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected)
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Post', id: 'post-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a post', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {}
|
||||
await factory.create('Post', {
|
||||
id: 'sample-post-id',
|
||||
})
|
||||
it('updates .disabled on post', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'Post', id: 'post-id', disabled: true } } },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns disabled resource id', async () => {
|
||||
variables = { id: 'sample-post-id' }
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'sample-post-id' },
|
||||
})
|
||||
it('can be closed with one review', async () => {
|
||||
closeReportVariables = {
|
||||
...disableVariables,
|
||||
closed: true,
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: closeReportVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Post', id: 'post-id' },
|
||||
report: { id: expect.any(String), closed: true },
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('changes .disabledBy', async () => {
|
||||
variables = { id: 'sample-post-id' }
|
||||
const before = { data: { Post: [{ id: 'sample-post-id', disabledBy: null }] } }
|
||||
const expected = {
|
||||
data: { Post: [{ id: 'sample-post-id', disabledBy: { id: 'moderator-id' } }] },
|
||||
}
|
||||
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(before)
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'sample-post-id' },
|
||||
})
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected)
|
||||
describe('moderate a user', () => {
|
||||
beforeEach(async () => {
|
||||
const troll = await factory.create('User', {
|
||||
id: 'user-id',
|
||||
})
|
||||
const reportAgainstTroll = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTroll.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'user-id',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks!',
|
||||
}),
|
||||
reportAgainstTroll.relateTo(troll, 'belongsTo'),
|
||||
])
|
||||
disableVariables = {
|
||||
...disableVariables,
|
||||
resourceId: 'user-id',
|
||||
}
|
||||
})
|
||||
|
||||
it('updates .disabled on post', async () => {
|
||||
const before = { data: { Post: [{ id: 'sample-post-id', disabled: false }] } }
|
||||
const expected = { data: { Post: [{ id: 'sample-post-id', disabled: true }] } }
|
||||
variables = { id: 'sample-post-id' }
|
||||
it('returns disabled resource id', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'User', id: 'user-id' } } },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(before)
|
||||
await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({
|
||||
data: { disable: 'sample-post-id' },
|
||||
})
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected)
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'User', id: 'user-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates .disabled on user', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: disableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'User', id: 'user-id', disabled: true } } },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('can be closed with one review', async () => {
|
||||
closeReportVariables = {
|
||||
...disableVariables,
|
||||
closed: true,
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: closeReportVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'User', id: 'user-id' },
|
||||
report: { id: expect.any(String), closed: true },
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('enable', () => {
|
||||
describe('review to re-enable after disabled', () => {
|
||||
describe('unautenticated user', () => {
|
||||
it('throws authorization error', async () => {
|
||||
variables = { id: 'sample-post-id' }
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
enableVariables = {
|
||||
...enableVariables,
|
||||
resourceId: 'post-id',
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
@ -228,17 +481,17 @@ describe('moderate resources', () => {
|
||||
describe('authenticated user', () => {
|
||||
describe('non moderator', () => {
|
||||
beforeEach(async () => {
|
||||
nonModerator = await factory.create('User', {
|
||||
id: 'non-moderator',
|
||||
name: 'Non Moderator',
|
||||
email: 'non.moderator@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
authenticatedUser = await nonModerator.toJson()
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
variables = { id: 'sample-post-id' }
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
enableVariables = {
|
||||
...enableVariables,
|
||||
resourceId: 'post-id',
|
||||
}
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
@ -248,101 +501,197 @@ describe('moderate resources', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
})
|
||||
describe('moderate a resource that is not a (Comment|Post|User) ', () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all([factory.create('Tag', { id: 'sample-tag-id' })])
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: enableMutation, variables: { id: 'sample-tag-id' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: { enable: null },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a comment', () => {
|
||||
beforeEach(async () => {
|
||||
variables = { id: 'comment-id' }
|
||||
await factory.create('Comment', {
|
||||
const trollingComment = await factory.create('Comment', {
|
||||
id: 'comment-id',
|
||||
})
|
||||
await mutate({ mutation: disableMutation, variables })
|
||||
const reportAgainstTrollingComment = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'comment-id',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
}),
|
||||
reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
|
||||
])
|
||||
await Promise.all([
|
||||
reportAgainstTrollingComment.relateTo(moderator, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'comment-id',
|
||||
}),
|
||||
trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
enableVariables = {
|
||||
...enableVariables,
|
||||
resourceId: 'comment-id',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns enabled resource id', async () => {
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'comment-id' },
|
||||
errors: undefined,
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } },
|
||||
})
|
||||
})
|
||||
|
||||
it('changes .disabledBy', async () => {
|
||||
const expected = {
|
||||
data: { Comment: [{ id: 'comment-id', disabledBy: null }] },
|
||||
errors: undefined,
|
||||
}
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'comment-id' },
|
||||
errors: undefined,
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Comment', id: 'comment-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected)
|
||||
})
|
||||
|
||||
it('updates .disabled on comment', async () => {
|
||||
const expected = {
|
||||
data: { Comment: [{ id: 'comment-id', disabled: false }] },
|
||||
errors: undefined,
|
||||
}
|
||||
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'comment-id' },
|
||||
errors: undefined,
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: false } },
|
||||
},
|
||||
})
|
||||
await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a post', () => {
|
||||
beforeEach(async () => {
|
||||
variables = { id: 'post-id' }
|
||||
await factory.create('Post', {
|
||||
const trollingPost = await factory.create('Post', {
|
||||
id: 'post-id',
|
||||
})
|
||||
await mutate({ mutation: disableMutation, variables })
|
||||
const reportAgainstTrollingPost = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'post-id',
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription:
|
||||
"This shouldn't be shown to anybody else! It's my private thing!",
|
||||
}),
|
||||
reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'),
|
||||
])
|
||||
await Promise.all([
|
||||
reportAgainstTrollingPost.relateTo(moderator, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'comment-id',
|
||||
}),
|
||||
trollingPost.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
enableVariables = {
|
||||
...enableVariables,
|
||||
resourceId: 'post-id',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns enabled resource id', async () => {
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'post-id' },
|
||||
errors: undefined,
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'Post', id: 'post-id' } } },
|
||||
})
|
||||
})
|
||||
|
||||
it('changes .disabledBy', async () => {
|
||||
const expected = {
|
||||
data: { Post: [{ id: 'post-id', disabledBy: null }] },
|
||||
errors: undefined,
|
||||
}
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'post-id' },
|
||||
errors: undefined,
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'Post', id: 'post-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected)
|
||||
})
|
||||
|
||||
it('updates .disabled on post', async () => {
|
||||
const expected = {
|
||||
data: { Post: [{ id: 'post-id', disabled: false }] },
|
||||
errors: undefined,
|
||||
}
|
||||
|
||||
await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({
|
||||
data: { enable: 'post-id' },
|
||||
errors: undefined,
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: { resource: { __typename: 'Post', id: 'post-id', disabled: false } },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderate a user', () => {
|
||||
beforeEach(async () => {
|
||||
const troll = await factory.create('User', {
|
||||
id: 'user-id',
|
||||
})
|
||||
const reportAgainstTroll = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstTroll.relateTo(nonModerator, 'filed', {
|
||||
resourceId: 'user-id',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks!',
|
||||
}),
|
||||
reportAgainstTroll.relateTo(troll, 'belongsTo'),
|
||||
])
|
||||
await Promise.all([
|
||||
reportAgainstTroll.relateTo(moderator, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'comment-id',
|
||||
}),
|
||||
troll.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
enableVariables = {
|
||||
...enableVariables,
|
||||
resourceId: 'user-id',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns enabled resource id', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: { review: { resource: { __typename: 'User', id: 'user-id' } } },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns .reviewed', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: {
|
||||
resource: { __typename: 'User', id: 'user-id' },
|
||||
report: {
|
||||
id: expect.any(String),
|
||||
reviewed: expect.arrayContaining([
|
||||
{ createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
|
||||
]),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('updates .disabled on user', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: reviewMutation, variables: enableVariables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
review: { resource: { __typename: 'User', id: 'user-id', disabled: false } },
|
||||
},
|
||||
})
|
||||
await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createPasswordReset from './helpers/createPasswordReset'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -5,6 +5,7 @@ import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||
import { mergeWith, isArray, isEmpty } from 'lodash'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const filterForBlockedUsers = async (params, context) => {
|
||||
if (!context.user) return params
|
||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
||||
@ -308,7 +309,15 @@ export default {
|
||||
},
|
||||
Post: {
|
||||
...Resolver('Post', {
|
||||
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt', 'pinned'],
|
||||
undefinedToNull: [
|
||||
'activityId',
|
||||
'objectId',
|
||||
'image',
|
||||
'language',
|
||||
'pinnedAt',
|
||||
'pinned',
|
||||
'imageAspectRatio',
|
||||
],
|
||||
hasMany: {
|
||||
tags: '-[:TAGGED]->(related:Tag)',
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
@ -318,7 +327,6 @@ export default {
|
||||
},
|
||||
hasOne: {
|
||||
author: '<-[:WROTE]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
pinnedBy: '<-[:PINNED]-(related:User)',
|
||||
},
|
||||
count: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const driver = getDriver()
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import fileUpload from './fileUpload'
|
||||
import encryptPassword from '../../helpers/encryptPassword'
|
||||
import generateNonce from './helpers/generateNonce'
|
||||
import existingEmailAddress from './helpers/existingEmailAddress'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
@ -16,7 +16,7 @@ export default {
|
||||
let emailAddress = await existingEmailAddress({ args, context })
|
||||
if (emailAddress) return emailAddress
|
||||
try {
|
||||
emailAddress = await instance.create('EmailAddress', args)
|
||||
emailAddress = await neode.create('EmailAddress', args)
|
||||
return emailAddress.toJson()
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
@ -32,7 +32,7 @@ export default {
|
||||
|
||||
let { nonce, email } = args
|
||||
email = normalizeEmail(email)
|
||||
const result = await instance.cypher(
|
||||
const result = await neode.cypher(
|
||||
`
|
||||
MATCH(email:EmailAddress {nonce: {nonce}, email: {email}})
|
||||
WHERE NOT (email)-[:BELONGS_TO]->()
|
||||
@ -40,12 +40,12 @@ export default {
|
||||
`,
|
||||
{ nonce, email },
|
||||
)
|
||||
const emailAddress = await instance.hydrateFirst(result, 'email', instance.model('Email'))
|
||||
const emailAddress = await neode.hydrateFirst(result, 'email', neode.model('Email'))
|
||||
if (!emailAddress) throw new UserInputError('Invalid email or nonce')
|
||||
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
||||
args = await encryptPassword(args)
|
||||
try {
|
||||
const user = await instance.create('User', args)
|
||||
const user = await neode.create('User', args)
|
||||
await Promise.all([
|
||||
user.relateTo(emailAddress, 'primaryEmail'),
|
||||
emailAddress.relateTo(user, 'belongsTo'),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,57 +1,47 @@
|
||||
const transformReturnType = record => {
|
||||
return {
|
||||
...record.get('report').properties,
|
||||
resource: {
|
||||
__typename: record.get('type'),
|
||||
...record.get('resource').properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
report: async (_parent, params, context, _resolveInfo) => {
|
||||
fileReport: async (_parent, params, context, _resolveInfo) => {
|
||||
let createdRelationshipWithNestedAttributes
|
||||
const { resourceId, reasonCategory, reasonDescription } = params
|
||||
const { driver, user } = context
|
||||
const session = driver.session()
|
||||
try {
|
||||
const writeTxResultPromise = session.writeTransaction(async txc => {
|
||||
const reportRelationshipTransactionResponse = await txc.run(
|
||||
`
|
||||
const reportWriteTxResultPromise = session.writeTransaction(async txc => {
|
||||
const reportTransactionResponse = await txc.run(
|
||||
`
|
||||
MATCH (submitter:User {id: $submitterId})
|
||||
MATCH (resource {id: $resourceId})
|
||||
WHERE resource:User OR resource:Comment OR resource:Post
|
||||
CREATE (resource)<-[report:REPORTED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
|
||||
RETURN report, submitter, resource, labels(resource)[0] as type
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
MERGE (resource)<-[:BELONGS_TO]-(report:Report {closed: false})
|
||||
ON CREATE SET report.id = randomUUID(), report.createdAt = $createdAt, report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.disable = resource.disabled, report.closed = false
|
||||
WITH submitter, resource, report
|
||||
CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
|
||||
|
||||
RETURN report, resource, labels(resource)[0] AS type
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
submitterId: user.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
reasonCategory,
|
||||
reasonDescription,
|
||||
},
|
||||
)
|
||||
return reportRelationshipTransactionResponse.records.map(record => ({
|
||||
report: record.get('report'),
|
||||
submitter: record.get('submitter'),
|
||||
resource: record.get('resource').properties,
|
||||
type: record.get('type'),
|
||||
}))
|
||||
})
|
||||
const txResult = await writeTxResultPromise
|
||||
{
|
||||
resourceId,
|
||||
submitterId: user.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
reasonCategory,
|
||||
reasonDescription,
|
||||
},
|
||||
)
|
||||
return reportTransactionResponse.records.map(transformReturnType)
|
||||
})
|
||||
try {
|
||||
const txResult = await reportWriteTxResultPromise
|
||||
if (!txResult[0]) return null
|
||||
const { report, submitter, resource, type } = txResult[0]
|
||||
createdRelationshipWithNestedAttributes = {
|
||||
...report.properties,
|
||||
post: null,
|
||||
comment: null,
|
||||
user: null,
|
||||
submitter: submitter.properties,
|
||||
type,
|
||||
}
|
||||
switch (type) {
|
||||
case 'Post':
|
||||
createdRelationshipWithNestedAttributes.post = resource
|
||||
break
|
||||
case 'Comment':
|
||||
createdRelationshipWithNestedAttributes.comment = resource
|
||||
break
|
||||
case 'User':
|
||||
createdRelationshipWithNestedAttributes.user = resource
|
||||
break
|
||||
}
|
||||
createdRelationshipWithNestedAttributes = txResult[0]
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
@ -61,8 +51,8 @@ export default {
|
||||
Query: {
|
||||
reports: async (_parent, params, context, _resolveInfo) => {
|
||||
const { driver } = context
|
||||
let response
|
||||
let orderByClause
|
||||
const session = driver.session()
|
||||
let reports, orderByClause, filterClause
|
||||
switch (params.orderBy) {
|
||||
case 'createdAt_asc':
|
||||
orderByClause = 'ORDER BY report.createdAt ASC'
|
||||
@ -73,56 +63,123 @@ export default {
|
||||
default:
|
||||
orderByClause = ''
|
||||
}
|
||||
const session = driver.session()
|
||||
|
||||
switch (params.reviewed) {
|
||||
case true:
|
||||
filterClause = 'AND ((report)<-[:REVIEWED]-(:User))'
|
||||
break
|
||||
case false:
|
||||
filterClause = 'AND NOT ((report)<-[:REVIEWED]-(:User))'
|
||||
break
|
||||
default:
|
||||
filterClause = ''
|
||||
}
|
||||
|
||||
if (params.closed) filterClause = 'AND report.closed = true'
|
||||
|
||||
const offset =
|
||||
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
|
||||
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
|
||||
|
||||
const reportReadTxPromise = session.readTransaction(async tx => {
|
||||
const allReportsTransactionResponse = await tx.run(
|
||||
`
|
||||
MATCH (report:Report)-[:BELONGS_TO]->(resource)
|
||||
WHERE (resource:User OR resource:Post OR resource:Comment)
|
||||
${filterClause}
|
||||
WITH report, resource,
|
||||
[(submitter:User)-[filed:FILED]->(report) | filed {.*, submitter: properties(submitter)} ] as filed,
|
||||
[(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors,
|
||||
[(resource)-[:COMMENTS]->(post:Post) | post {.*} ] as optionalCommentedPosts,
|
||||
resource {.*, __typename: labels(resource)[0] } as resourceWithType
|
||||
WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed,
|
||||
resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource
|
||||
RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed }
|
||||
${orderByClause}
|
||||
${offset} ${limit}
|
||||
`,
|
||||
)
|
||||
return allReportsTransactionResponse.records.map(record => record.get('report'))
|
||||
})
|
||||
try {
|
||||
const cypher = `
|
||||
MATCH (submitter:User)-[report:REPORTED]->(resource)
|
||||
WHERE resource:User OR resource:Comment OR resource:Post
|
||||
RETURN report, submitter, resource, labels(resource)[0] as type
|
||||
${orderByClause}
|
||||
`
|
||||
const result = await session.run(cypher, {})
|
||||
const dbResponse = result.records.map(r => {
|
||||
return {
|
||||
report: r.get('report'),
|
||||
submitter: r.get('submitter'),
|
||||
resource: r.get('resource'),
|
||||
type: r.get('type'),
|
||||
const txResult = await reportReadTxPromise
|
||||
if (!txResult[0]) return null
|
||||
reports = txResult
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
return reports
|
||||
},
|
||||
},
|
||||
Report: {
|
||||
filed: async (parent, _params, context, _resolveInfo) => {
|
||||
if (typeof parent.filed !== 'undefined') return parent.filed
|
||||
const session = context.driver.session()
|
||||
const { id } = parent
|
||||
let filed
|
||||
const readTxPromise = session.readTransaction(async tx => {
|
||||
const allReportsTransactionResponse = await tx.run(
|
||||
`
|
||||
MATCH (submitter:User)-[filed:FILED]->(report:Report {id: $id})
|
||||
RETURN filed, submitter
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
return allReportsTransactionResponse.records.map(record => ({
|
||||
submitter: record.get('submitter').properties,
|
||||
filed: record.get('filed').properties,
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxPromise
|
||||
if (!txResult[0]) return null
|
||||
filed = txResult.map(reportedRecord => {
|
||||
const { submitter, filed } = reportedRecord
|
||||
const relationshipWithNestedAttributes = {
|
||||
...filed,
|
||||
submitter,
|
||||
}
|
||||
})
|
||||
if (!dbResponse) return null
|
||||
|
||||
response = []
|
||||
dbResponse.forEach(ele => {
|
||||
const { report, submitter, resource, type } = ele
|
||||
|
||||
const responseEle = {
|
||||
...report.properties,
|
||||
post: null,
|
||||
comment: null,
|
||||
user: null,
|
||||
submitter: submitter.properties,
|
||||
type,
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'Post':
|
||||
responseEle.post = resource.properties
|
||||
break
|
||||
case 'Comment':
|
||||
responseEle.comment = resource.properties
|
||||
break
|
||||
case 'User':
|
||||
responseEle.user = resource.properties
|
||||
break
|
||||
}
|
||||
response.push(responseEle)
|
||||
return relationshipWithNestedAttributes
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
return response
|
||||
return filed
|
||||
},
|
||||
reviewed: async (parent, _params, context, _resolveInfo) => {
|
||||
if (typeof parent.reviewed !== 'undefined') return parent.reviewed
|
||||
const session = context.driver.session()
|
||||
const { id } = parent
|
||||
let reviewed
|
||||
const readTxPromise = session.readTransaction(async tx => {
|
||||
const allReportsTransactionResponse = await tx.run(
|
||||
`
|
||||
MATCH (resource)<-[:BELONGS_TO]-(report:Report {id: $id})<-[review:REVIEWED]-(moderator:User)
|
||||
RETURN moderator, review
|
||||
ORDER BY report.updatedAt DESC, review.updatedAt DESC
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
return allReportsTransactionResponse.records.map(record => ({
|
||||
review: record.get('review').properties,
|
||||
moderator: record.get('moderator').properties,
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxPromise
|
||||
reviewed = txResult.map(reportedRecord => {
|
||||
const { review, moderator } = reportedRecord
|
||||
const relationshipWithNestedAttributes = {
|
||||
...review,
|
||||
moderator,
|
||||
}
|
||||
return relationshipWithNestedAttributes
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
return reviewed
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,37 +2,47 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const instance = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
describe('report resources', () => {
|
||||
let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser
|
||||
describe('file a report on a resource', () => {
|
||||
let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser, otherReportingUser
|
||||
const categoryIds = ['cat9']
|
||||
const reportMutation = gql`
|
||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
report(
|
||||
fileReport(
|
||||
resourceId: $resourceId
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
id
|
||||
createdAt
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
type
|
||||
submitter {
|
||||
email
|
||||
updatedAt
|
||||
disable
|
||||
closed
|
||||
rule
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
name
|
||||
}
|
||||
... on Post {
|
||||
title
|
||||
}
|
||||
... on Comment {
|
||||
content
|
||||
}
|
||||
}
|
||||
user {
|
||||
name
|
||||
}
|
||||
post {
|
||||
title
|
||||
}
|
||||
comment {
|
||||
content
|
||||
filed {
|
||||
submitter {
|
||||
id
|
||||
}
|
||||
createdAt
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,7 +77,7 @@ describe('report resources', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = null
|
||||
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||
data: { report: null },
|
||||
data: { fileReport: null },
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
})
|
||||
@ -81,6 +91,12 @@ describe('report resources', () => {
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
otherReportingUser = await factory.create('User', {
|
||||
id: 'other-reporting-user-id',
|
||||
role: 'user',
|
||||
email: 'reporting@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
await factory.create('User', {
|
||||
id: 'abusive-user-id',
|
||||
role: 'user',
|
||||
@ -99,15 +115,15 @@ describe('report resources', () => {
|
||||
describe('invalid resource id', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||
data: { report: null },
|
||||
data: { fileReport: null },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid resource', () => {
|
||||
describe('reported resource is a user', () => {
|
||||
it('returns type "User"', async () => {
|
||||
describe('creates report', () => {
|
||||
it('which belongs to resource', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
@ -115,15 +131,28 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
type: 'User',
|
||||
fileReport: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns resource in user attribute', async () => {
|
||||
it('creates only one report for multiple reports on the same resource', async () => {
|
||||
const firstReport = await mutate({
|
||||
mutation: reportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
authenticatedUser = await otherReportingUser.toJson()
|
||||
const secondReport = await mutate({
|
||||
mutation: reportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
expect(firstReport.data.fileReport.id).toEqual(secondReport.data.fileReport.id)
|
||||
})
|
||||
|
||||
it('returns the rule for how the report was decided', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
@ -131,8 +160,46 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
user: {
|
||||
fileReport: {
|
||||
rule: 'latestReviewUpdatedAtRules',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
it.todo('creates multiple filed reports')
|
||||
})
|
||||
|
||||
describe('reported resource is a user', () => {
|
||||
it('returns __typename "User"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'User',
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns user attribute info', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'User',
|
||||
name: 'abusive-user',
|
||||
},
|
||||
},
|
||||
@ -149,10 +216,14 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
submitter: {
|
||||
email: 'test@example.org',
|
||||
},
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
submitter: {
|
||||
id: 'current-user-id',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -167,7 +238,7 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
fileReport: {
|
||||
createdAt: expect.any(String),
|
||||
},
|
||||
},
|
||||
@ -187,8 +258,12 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -228,15 +303,19 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
reasonDescription: 'My reason!',
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonDescription: 'My reason!',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sanitize the reason description', async () => {
|
||||
it('sanitizes the reason description', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
@ -248,8 +327,12 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
reasonDescription: 'My reason !',
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonDescription: 'My reason !',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -278,8 +361,10 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
type: 'Post',
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'Post',
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -297,8 +382,9 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
post: {
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'Post',
|
||||
title: 'This is a post that is going to be reported',
|
||||
},
|
||||
},
|
||||
@ -306,25 +392,6 @@ describe('report resources', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null in user attribute', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'post-to-report-id',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
user: null,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('reported resource is a comment', () => {
|
||||
@ -356,8 +423,10 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
type: 'Comment',
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'Comment',
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -375,8 +444,9 @@ describe('report resources', () => {
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
report: {
|
||||
comment: {
|
||||
fileReport: {
|
||||
resource: {
|
||||
__typename: 'Comment',
|
||||
content: 'Post comment to be reported.',
|
||||
},
|
||||
},
|
||||
@ -403,7 +473,7 @@ describe('report resources', () => {
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: { report: null },
|
||||
data: { fileReport: null },
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
@ -411,25 +481,35 @@ describe('report resources', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('query for reported resource', () => {
|
||||
const reportsQuery = gql`
|
||||
query {
|
||||
reports(orderBy: createdAt_desc) {
|
||||
id
|
||||
createdAt
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
submitter {
|
||||
id
|
||||
updatedAt
|
||||
disable
|
||||
closed
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
}
|
||||
... on Post {
|
||||
id
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
}
|
||||
}
|
||||
type
|
||||
user {
|
||||
id
|
||||
}
|
||||
post {
|
||||
id
|
||||
}
|
||||
comment {
|
||||
id
|
||||
filed {
|
||||
submitter {
|
||||
id
|
||||
}
|
||||
createdAt
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -437,7 +517,6 @@ describe('report resources', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
|
||||
moderator = await factory.create('User', {
|
||||
id: 'moderator-1',
|
||||
role: 'moderator',
|
||||
@ -518,6 +597,7 @@ describe('report resources', () => {
|
||||
])
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = null
|
||||
@ -527,6 +607,7 @@ describe('report resources', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
it('role "user" gets no reports', async () => {
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
@ -538,49 +619,69 @@ describe('report resources', () => {
|
||||
|
||||
it('role "moderator" gets reports', async () => {
|
||||
const expected = {
|
||||
// to check 'orderBy: createdAt_desc' is not possible here, because 'createdAt' does not differ
|
||||
reports: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks',
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
type: 'User',
|
||||
user: expect.objectContaining({
|
||||
updatedAt: expect.any(String),
|
||||
disable: false,
|
||||
closed: false,
|
||||
resource: {
|
||||
__typename: 'User',
|
||||
id: 'abusive-user-1',
|
||||
}),
|
||||
post: null,
|
||||
comment: null,
|
||||
},
|
||||
filed: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
type: 'Post',
|
||||
user: null,
|
||||
post: expect.objectContaining({
|
||||
updatedAt: expect.any(String),
|
||||
disable: false,
|
||||
closed: false,
|
||||
resource: {
|
||||
__typename: 'Post',
|
||||
id: 'abusive-post-1',
|
||||
}),
|
||||
comment: null,
|
||||
},
|
||||
filed: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This post is bigoted',
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
type: 'Comment',
|
||||
user: null,
|
||||
post: null,
|
||||
comment: expect.objectContaining({
|
||||
updatedAt: expect.any(String),
|
||||
disable: false,
|
||||
closed: false,
|
||||
resource: {
|
||||
__typename: 'Comment',
|
||||
id: 'abusive-comment-1',
|
||||
}),
|
||||
},
|
||||
filed: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
submitter: expect.objectContaining({
|
||||
id: 'current-user-id',
|
||||
}),
|
||||
createdAt: expect.any(String),
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This post is bigoted',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
const getUserAndBadge = async ({ badgeKey, userId }) => {
|
||||
const user = await instance.first('User', 'id', userId)
|
||||
const badge = await instance.first('Badge', 'id', badgeKey)
|
||||
const user = await neode.first('User', 'id', userId)
|
||||
const badge = await neode.first('Badge', 'id', badgeKey)
|
||||
if (!user) throw new UserInputError("Couldn't find a user with that id")
|
||||
if (!badge) throw new UserInputError("Couldn't find a badge with that id")
|
||||
return { user, badge }
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let mutate, query, authenticatedUser, variables
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const [user, socialMedia] = await Promise.all([
|
||||
instance.find('User', context.user.id),
|
||||
instance.create('SocialMedia', params),
|
||||
neode.find('User', context.user.id),
|
||||
neode.create('SocialMedia', params),
|
||||
])
|
||||
await socialMedia.relateTo(user, 'ownedBy')
|
||||
const response = await socialMedia.toJson()
|
||||
@ -16,14 +16,14 @@ export default {
|
||||
return response
|
||||
},
|
||||
UpdateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const socialMedia = await instance.find('SocialMedia', params.id)
|
||||
const socialMedia = await neode.find('SocialMedia', params.id)
|
||||
await socialMedia.update({ url: params.url })
|
||||
const response = await socialMedia.toJson()
|
||||
|
||||
return response
|
||||
},
|
||||
DeleteSocialMedia: async (object, { id }, context, resolveInfo) => {
|
||||
const socialMedia = await instance.find('SocialMedia', id)
|
||||
const socialMedia = await neode.find('SocialMedia', id)
|
||||
if (!socialMedia) return null
|
||||
await socialMedia.delete()
|
||||
return socialMedia.toJson()
|
||||
|
||||
@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
describe('SocialMedia', () => {
|
||||
let socialMediaAction, someUser, ownerNode, owner
|
||||
@ -27,15 +27,15 @@ describe('SocialMedia', () => {
|
||||
const newUrl = 'https://twitter.com/bullerby'
|
||||
|
||||
const setUpSocialMedia = async () => {
|
||||
const socialMediaNode = await instance.create('SocialMedia', { url })
|
||||
const socialMediaNode = await neode.create('SocialMedia', { url })
|
||||
await socialMediaNode.relateTo(ownerNode, 'ownedBy')
|
||||
return socialMediaNode.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
const someUserNode = await instance.create('User', userParams)
|
||||
const someUserNode = await neode.create('User', userParams)
|
||||
someUser = await someUserNode.toJson()
|
||||
ownerNode = await instance.create('User', ownerParams)
|
||||
ownerNode = await neode.create('User', ownerParams)
|
||||
owner = await ownerNode.toJson()
|
||||
|
||||
socialMediaAction = async (user, mutation, variables) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let query, authenticatedUser
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import encode from '../../jwt/encode'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { AuthenticationError } from 'apollo-server'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
@ -13,7 +13,7 @@ export default {
|
||||
},
|
||||
currentUser: async (object, params, ctx, resolveInfo) => {
|
||||
if (!ctx.user) return null
|
||||
const user = await instance.find('User', ctx.user.id)
|
||||
const user = await neode.find('User', ctx.user.id)
|
||||
return user.toJson()
|
||||
},
|
||||
},
|
||||
@ -53,7 +53,7 @@ export default {
|
||||
}
|
||||
},
|
||||
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
|
||||
const currentUser = await instance.find('User', user.id)
|
||||
const currentUser = await neode.find('User', user.id)
|
||||
|
||||
const encryptedPassword = currentUser.get('encryptedPassword')
|
||||
if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) {
|
||||
|
||||
@ -5,29 +5,29 @@ import { gql } from '../../helpers/jest'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer, { context } from '../../server'
|
||||
import encode from '../../jwt/encode'
|
||||
import { neode as getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
let query
|
||||
let mutate
|
||||
let variables
|
||||
let req
|
||||
let user
|
||||
let query, mutate, variables, req, user
|
||||
|
||||
const disable = async id => {
|
||||
await factory.create('User', { id: 'u2', role: 'moderator' })
|
||||
const moderatorBearerToken = encode({ id: 'u2' })
|
||||
req = { headers: { authorization: `Bearer ${moderatorBearerToken}` } }
|
||||
await mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
disable(id: $id)
|
||||
}
|
||||
`,
|
||||
variables: { id },
|
||||
})
|
||||
req = { headers: {} }
|
||||
const moderator = await factory.create('User', { id: 'u2', role: 'moderator' })
|
||||
const user = await neode.find('User', id)
|
||||
const reportAgainstUser = await factory.create('Report')
|
||||
await Promise.all([
|
||||
reportAgainstUser.relateTo(moderator, 'filed', {
|
||||
resourceId: id,
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks!',
|
||||
}),
|
||||
reportAgainstUser.relateTo(user, 'belongsTo'),
|
||||
])
|
||||
const disableVariables = { resourceId: user.id, disable: true, closed: false }
|
||||
await Promise.all([
|
||||
reportAgainstUser.relateTo(moderator, 'reviewed', disableVariables),
|
||||
user.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
export const getBlockedUsers = async context => {
|
||||
const { neode } = context
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
block: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await instance.cypher(
|
||||
await neode.cypher(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:FOLLOWS]->(b:User {id: $args.id})
|
||||
DELETE r
|
||||
@ -81,8 +81,8 @@ export default {
|
||||
{ currentUser, args },
|
||||
)
|
||||
const [user, blockedUser] = await Promise.all([
|
||||
instance.find('User', currentUser.id),
|
||||
instance.find('User', args.id),
|
||||
neode.find('User', currentUser.id),
|
||||
neode.find('User', args.id),
|
||||
])
|
||||
await user.relateTo(blockedUser, 'blocked')
|
||||
return blockedUser.toJson()
|
||||
@ -90,14 +90,14 @@ export default {
|
||||
unblock: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await instance.cypher(
|
||||
await neode.cypher(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(b:User {id: $args.id})
|
||||
DELETE r
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
const blockedUser = await instance.find('User', args.id)
|
||||
const blockedUser = await neode.find('User', args.id)
|
||||
return blockedUser.toJson()
|
||||
},
|
||||
UpdateUser: async (object, args, context, resolveInfo) => {
|
||||
@ -111,7 +111,7 @@ export default {
|
||||
}
|
||||
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
||||
try {
|
||||
const user = await instance.find('User', args.id)
|
||||
const user = await neode.find('User', args.id)
|
||||
if (!user) return null
|
||||
await user.update({ ...args, updatedAt: new Date().toISOString() })
|
||||
return user.toJson()
|
||||
@ -173,7 +173,7 @@ export default {
|
||||
if (typeof parent.email !== 'undefined') return parent.email
|
||||
const { id } = parent
|
||||
const statement = `MATCH(u:User {id: {id}})-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
const result = await neode.cypher(statement, { id })
|
||||
const [{ email }] = result.records.map(r => r.get('e').properties)
|
||||
return email
|
||||
},
|
||||
@ -212,7 +212,6 @@ export default {
|
||||
},
|
||||
hasOne: {
|
||||
invitedBy: '<-[:INVITED]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
location: '-[:IS_IN]->(related:Location)',
|
||||
},
|
||||
hasMany: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../../server'
|
||||
import Factory from '../../../seed/factories'
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import { neode, getDriver } from '../../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../../bootstrap/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
const neode = getNeode()
|
||||
|
||||
let currentUser
|
||||
let blockedUser
|
||||
@ -20,7 +20,7 @@ beforeEach(() => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
driver,
|
||||
neode: instance,
|
||||
neode,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
@ -55,11 +55,11 @@ describe('blockedUsers', () => {
|
||||
|
||||
describe('authenticated and given a blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
currentUser = await neode.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
blockedUser = await instance.create('User', {
|
||||
blockedUser = await neode.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
@ -113,7 +113,7 @@ describe('block', () => {
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
currentUser = await neode.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
@ -138,7 +138,7 @@ describe('block', () => {
|
||||
|
||||
describe('given a to-be-blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
blockedUser = await instance.create('User', {
|
||||
blockedUser = await neode.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
@ -181,11 +181,11 @@ describe('block', () => {
|
||||
let postQuery
|
||||
|
||||
beforeEach(async () => {
|
||||
const post1 = await instance.create('Post', {
|
||||
const post1 = await neode.create('Post', {
|
||||
id: 'p12',
|
||||
title: 'A post written by the current user',
|
||||
})
|
||||
const post2 = await instance.create('Post', {
|
||||
const post2 = await neode.create('Post', {
|
||||
id: 'p23',
|
||||
title: 'A post written by the blocked user',
|
||||
})
|
||||
@ -323,7 +323,7 @@ describe('unblock', () => {
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
currentUser = await neode.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
@ -348,7 +348,7 @@ describe('unblock', () => {
|
||||
|
||||
describe('given another user', () => {
|
||||
beforeEach(async () => {
|
||||
blockedUser = await instance.create('User', {
|
||||
blockedUser = await neode.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
|
||||
@ -24,8 +24,6 @@ type Mutation {
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
requestPasswordReset(email: String!): Boolean!
|
||||
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
# Shout the given Type and ID
|
||||
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||
# Unshout the given Type and ID
|
||||
|
||||
@ -47,7 +47,6 @@ type Comment {
|
||||
updatedAt: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
||||
18
backend/src/schema/types/type/FILED.gql
Normal file
18
backend/src/schema/types/type/FILED.gql
Normal file
@ -0,0 +1,18 @@
|
||||
type FILED {
|
||||
createdAt: String!
|
||||
reasonCategory: ReasonCategory!
|
||||
reasonDescription: String!
|
||||
submitter: User
|
||||
}
|
||||
|
||||
# this list equals the strings of an array in file "webapp/constants/modals.js"
|
||||
enum ReasonCategory {
|
||||
other
|
||||
discrimination_etc
|
||||
pornographic_content_links
|
||||
glorific_trivia_of_cruel_inhuman_acts
|
||||
doxing
|
||||
intentional_intimidation_stalking_persecution
|
||||
advert_products_services_commercial
|
||||
criminal_behavior_violation_german_law
|
||||
}
|
||||
@ -26,7 +26,7 @@ enum NotificationReason {
|
||||
type Query {
|
||||
notifications(read: Boolean, orderBy: NotificationOrdering, first: Int, offset: Int): [NOTIFIED]
|
||||
}
|
||||
|
||||
|
||||
type Mutation {
|
||||
markAsRead(id: ID!): NOTIFIED
|
||||
}
|
||||
|
||||
@ -114,16 +114,16 @@ type Post {
|
||||
objectId: String
|
||||
author: User @relation(name: "WROTE", direction: "IN")
|
||||
title: String!
|
||||
slug: String
|
||||
slug: String!
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
image: String
|
||||
imageUpload: Upload
|
||||
imageAspectRatio: Float
|
||||
visibility: Visibility
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
pinned: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
@ -183,6 +183,7 @@ type Mutation {
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
contentExcerpt: String
|
||||
imageAspectRatio: Float
|
||||
): Post
|
||||
UpdatePost(
|
||||
id: ID!
|
||||
@ -195,6 +196,7 @@ type Mutation {
|
||||
visibility: Visibility
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
imageAspectRatio: Float
|
||||
): Post
|
||||
DeletePost(id: ID!): Post
|
||||
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||
@ -219,6 +221,7 @@ type Query {
|
||||
offset: Int
|
||||
orderBy: [_PostOrdering]
|
||||
filter: _PostFilter
|
||||
imageAspectRatio: Float
|
||||
): [Post]
|
||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
type REPORTED {
|
||||
createdAt: String
|
||||
reasonCategory: ReasonCategory
|
||||
reasonDescription: String
|
||||
submitter: User
|
||||
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN user")
|
||||
# not yet supported
|
||||
# resource: ReportResource
|
||||
# @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource")
|
||||
type: String
|
||||
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN labels(resource)[0]")
|
||||
user: User
|
||||
post: Post
|
||||
comment: Comment
|
||||
}
|
||||
|
||||
# this list equals the strings of an array in file "webapp/constants/modals.js"
|
||||
enum ReasonCategory {
|
||||
other
|
||||
discrimination_etc
|
||||
pornographic_content_links
|
||||
glorific_trivia_of_cruel_inhuman_acts
|
||||
doxing
|
||||
intentional_intimidation_stalking_persecution
|
||||
advert_products_services_commercial
|
||||
criminal_behavior_violation_german_law
|
||||
}
|
||||
|
||||
# not yet supported
|
||||
# union ReportResource = User | Post | Comment
|
||||
|
||||
enum ReportOrdering {
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
}
|
||||
|
||||
type Query {
|
||||
reports(orderBy: ReportOrdering): [REPORTED]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
report(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): REPORTED
|
||||
}
|
||||
15
backend/src/schema/types/type/REVIEWED.gql
Normal file
15
backend/src/schema/types/type/REVIEWED.gql
Normal file
@ -0,0 +1,15 @@
|
||||
type REVIEWED {
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
disable: Boolean!
|
||||
closed: Boolean!
|
||||
report: Report
|
||||
# @cypher(statement: "MATCH (report:Report)<-[this:REVIEWED]-(:User) RETURN report")
|
||||
moderator: User
|
||||
resource: ReviewedResource
|
||||
}
|
||||
union ReviewedResource = User | Post | Comment
|
||||
|
||||
type Mutation {
|
||||
review(resourceId: ID!, disable: Boolean, closed: Boolean): REVIEWED
|
||||
}
|
||||
30
backend/src/schema/types/type/Report.gql
Normal file
30
backend/src/schema/types/type/Report.gql
Normal file
@ -0,0 +1,30 @@
|
||||
type Report {
|
||||
id: ID!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
rule: ReportRule!
|
||||
disable: Boolean!
|
||||
closed: Boolean!
|
||||
filed: [FILED]
|
||||
reviewed: [REVIEWED]!
|
||||
resource: ReportedResource
|
||||
}
|
||||
|
||||
union ReportedResource = User | Post | Comment
|
||||
|
||||
enum ReportRule {
|
||||
latestReviewUpdatedAtRules
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): Report
|
||||
}
|
||||
|
||||
type Query {
|
||||
reports(orderBy: ReportOrdering, first: Int, offset: Int, reviewed: Boolean, closed: Boolean): [Report]
|
||||
}
|
||||
|
||||
enum ReportOrdering {
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
}
|
||||
@ -33,7 +33,6 @@ type User {
|
||||
coverImg: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
role: UserGroup!
|
||||
publicKey: String
|
||||
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
||||
@ -44,8 +43,6 @@ type User {
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
||||
|
||||
# createdAt: DateTime
|
||||
# updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver, neode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import createBadge from './badges.js'
|
||||
import createUser from './users.js'
|
||||
import createPost from './posts.js'
|
||||
@ -10,6 +10,7 @@ import createLocation from './locations.js'
|
||||
import createEmailAddress from './emailAddresses.js'
|
||||
import createDonations from './donations.js'
|
||||
import createUnverifiedEmailAddresss from './unverifiedEmailAddresses.js'
|
||||
import createReport from './reports.js'
|
||||
|
||||
const factories = {
|
||||
Badge: createBadge,
|
||||
@ -23,6 +24,7 @@ const factories = {
|
||||
EmailAddress: createEmailAddress,
|
||||
UnverifiedEmailAddress: createUnverifiedEmailAddresss,
|
||||
Donations: createDonations,
|
||||
Report: createReport,
|
||||
}
|
||||
|
||||
export const cleanDatabase = async (options = {}) => {
|
||||
@ -37,7 +39,7 @@ export const cleanDatabase = async (options = {}) => {
|
||||
}
|
||||
|
||||
export default function Factory(options = {}) {
|
||||
const { neo4jDriver = getDriver(), neodeInstance = neode() } = options
|
||||
const { neo4jDriver = getDriver(), neodeInstance = getNeode() } = options
|
||||
|
||||
const result = {
|
||||
neo4jDriver,
|
||||
|
||||
@ -19,6 +19,7 @@ export default function create() {
|
||||
visibility: 'public',
|
||||
deleted: false,
|
||||
categoryIds: [],
|
||||
imageAspectRatio: 1.333,
|
||||
}
|
||||
args = {
|
||||
...defaults,
|
||||
|
||||
7
backend/src/seed/factories/reports.js
Normal file
7
backend/src/seed/factories/reports.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default function create() {
|
||||
return {
|
||||
factory: async ({ args, neodeInstance }) => {
|
||||
return neodeInstance.create('Report', args)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import sample from 'lodash/sample'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory from './factories'
|
||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { gql } from '../helpers/jest'
|
||||
|
||||
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
@ -350,15 +350,17 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
author: peterLustig,
|
||||
id: 'p0',
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.food(),
|
||||
image: faker.image.unsplash.food(300, 169),
|
||||
categoryIds: ['cat16'],
|
||||
imageAspectRatio: 300 / 169,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: bobDerBaumeister,
|
||||
id: 'p1',
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.technology(),
|
||||
image: faker.image.unsplash.technology(300, 1500),
|
||||
categoryIds: ['cat1'],
|
||||
imageAspectRatio: 300 / 1500,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: huey,
|
||||
@ -382,8 +384,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
authorId: 'u1',
|
||||
id: 'p6',
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.buildings(),
|
||||
image: faker.image.unsplash.buildings(300, 857),
|
||||
categoryIds: ['cat6'],
|
||||
imageAspectRatio: 300 / 857,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: huey,
|
||||
@ -400,8 +403,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
author: louie,
|
||||
id: 'p11',
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.people(),
|
||||
image: faker.image.unsplash.people(300, 901),
|
||||
categoryIds: ['cat11'],
|
||||
imageAspectRatio: 300 / 901,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: bobDerBaumeister,
|
||||
@ -413,8 +417,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
author: jennyRostock,
|
||||
id: 'p14',
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.objects(),
|
||||
image: faker.image.unsplash.objects(300, 200),
|
||||
categoryIds: ['cat14'],
|
||||
imageAspectRatio: 300 / 450,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: huey,
|
||||
@ -434,8 +439,20 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
const hashtagAndMention1 =
|
||||
'The new physics of <a class="hashtag" data-hashtag-id="QuantenFlussTheorie" href="/?hashtag=QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" data-hashtag-id="QuantumGravity" href="/?hashtag=QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
|
||||
const createPostMutation = gql`
|
||||
mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
mutation(
|
||||
$id: ID
|
||||
$title: String!
|
||||
$content: String!
|
||||
$categoryIds: [ID]
|
||||
$imageAspectRatio: Float
|
||||
) {
|
||||
CreatePost(
|
||||
id: $id
|
||||
title: $title
|
||||
content: $content
|
||||
categoryIds: $categoryIds
|
||||
imageAspectRatio: $imageAspectRatio
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@ -449,6 +466,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
title: `Nature Philosophy Yoga`,
|
||||
content: hashtag1,
|
||||
categoryIds: ['cat2'],
|
||||
imageAspectRatio: 300 / 200,
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
@ -458,6 +476,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
title: 'This is post #7',
|
||||
content: `${mention1} ${faker.lorem.paragraph()}`,
|
||||
categoryIds: ['cat7'],
|
||||
imageAspectRatio: 300 / 180,
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
@ -468,6 +487,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
title: `Quantum Flow Theory explains Quantum Gravity`,
|
||||
content: hashtagAndMention1,
|
||||
categoryIds: ['cat8'],
|
||||
imageAspectRatio: 300 / 900,
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
@ -477,6 +497,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
title: 'This is post #12',
|
||||
content: `${mention2} ${faker.lorem.paragraph()}`,
|
||||
categoryIds: ['cat12'],
|
||||
imageAspectRatio: 300 / 200,
|
||||
},
|
||||
}),
|
||||
])
|
||||
@ -524,7 +545,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
])
|
||||
authenticatedUser = null
|
||||
|
||||
await Promise.all([
|
||||
const comments = await Promise.all([
|
||||
factory.create('Comment', {
|
||||
author: jennyRostock,
|
||||
id: 'c1',
|
||||
@ -541,7 +562,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
postId: 'p3',
|
||||
}),
|
||||
factory.create('Comment', {
|
||||
author: bobDerBaumeister,
|
||||
author: jennyRostock,
|
||||
id: 'c5',
|
||||
postId: 'p3',
|
||||
}),
|
||||
@ -581,6 +602,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
postId: 'p15',
|
||||
}),
|
||||
])
|
||||
const trollingComment = comments[0]
|
||||
|
||||
await Promise.all([
|
||||
democracy.relateTo(p3, 'post'),
|
||||
@ -644,68 +666,115 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
louie.relateTo(p10, 'shouted'),
|
||||
])
|
||||
|
||||
const disableMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
disable(id: $id)
|
||||
}
|
||||
`
|
||||
authenticatedUser = await bobDerBaumeister.toJson()
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: disableMutation,
|
||||
variables: {
|
||||
id: 'p11',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: disableMutation,
|
||||
variables: {
|
||||
id: 'c5',
|
||||
},
|
||||
}),
|
||||
const reports = await Promise.all([
|
||||
factory.create('Report'),
|
||||
factory.create('Report'),
|
||||
factory.create('Report'),
|
||||
factory.create('Report'),
|
||||
])
|
||||
authenticatedUser = null
|
||||
const reportAgainstDagobert = reports[0]
|
||||
const reportAgainstTrollingPost = reports[1]
|
||||
const reportAgainstTrollingComment = reports[2]
|
||||
const reportAgainstDewey = reports[3]
|
||||
|
||||
// There is no error logged or the 'try' fails if this mutation is wrong. Why?
|
||||
const reportMutation = gql`
|
||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
report(
|
||||
resourceId: $resourceId
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
type
|
||||
}
|
||||
}
|
||||
`
|
||||
authenticatedUser = await huey.toJson()
|
||||
// report resource first time
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: {
|
||||
resourceId: 'c1',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
},
|
||||
reportAgainstDagobert.relateTo(jennyRostock, 'filed', {
|
||||
resourceId: 'u7',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks!',
|
||||
}),
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: {
|
||||
resourceId: 'p1',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This post is bigoted',
|
||||
},
|
||||
reportAgainstDagobert.relateTo(dagobert, 'belongsTo'),
|
||||
reportAgainstTrollingPost.relateTo(jennyRostock, 'filed', {
|
||||
resourceId: 'p2',
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!",
|
||||
}),
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: {
|
||||
resourceId: 'u1',
|
||||
reasonCategory: 'doxing',
|
||||
reasonDescription: 'This user is harassing me with bigoted remarks',
|
||||
},
|
||||
reportAgainstTrollingPost.relateTo(p2, 'belongsTo'),
|
||||
reportAgainstTrollingComment.relateTo(huey, 'filed', {
|
||||
resourceId: 'c1',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'This comment is bigoted',
|
||||
}),
|
||||
reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
|
||||
reportAgainstDewey.relateTo(dagobert, 'filed', {
|
||||
resourceId: 'u5',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This user is harassing me!',
|
||||
}),
|
||||
reportAgainstDewey.relateTo(dewey, 'belongsTo'),
|
||||
])
|
||||
|
||||
// report resource a second time
|
||||
await Promise.all([
|
||||
reportAgainstDagobert.relateTo(louie, 'filed', {
|
||||
resourceId: 'u7',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'this user is attacking me for who I am!',
|
||||
}),
|
||||
reportAgainstDagobert.relateTo(dagobert, 'belongsTo'),
|
||||
reportAgainstTrollingPost.relateTo(peterLustig, 'filed', {
|
||||
resourceId: 'p2',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
reasonDescription: 'This post is bigoted',
|
||||
}),
|
||||
reportAgainstTrollingPost.relateTo(p2, 'belongsTo'),
|
||||
|
||||
reportAgainstTrollingComment.relateTo(bobDerBaumeister, 'filed', {
|
||||
resourceId: 'c1',
|
||||
reasonCategory: 'pornographic_content_links',
|
||||
reasonDescription: 'This comment is porno!!!',
|
||||
}),
|
||||
reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
|
||||
])
|
||||
|
||||
const disableVariables = {
|
||||
resourceId: 'undefined-resource',
|
||||
disable: true,
|
||||
closed: false,
|
||||
}
|
||||
|
||||
// review resource first time
|
||||
await Promise.all([
|
||||
reportAgainstDagobert.relateTo(bobDerBaumeister, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'u7',
|
||||
}),
|
||||
dagobert.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
reportAgainstTrollingPost.relateTo(peterLustig, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'p2',
|
||||
}),
|
||||
p2.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
reportAgainstTrollingComment.relateTo(bobDerBaumeister, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'c1',
|
||||
}),
|
||||
trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }),
|
||||
])
|
||||
|
||||
// second review of resource and close report
|
||||
await Promise.all([
|
||||
reportAgainstDagobert.relateTo(peterLustig, 'reviewed', {
|
||||
resourceId: 'u7',
|
||||
disable: false,
|
||||
closed: true,
|
||||
}),
|
||||
dagobert.update({ disabled: false, updatedAt: new Date().toISOString(), closed: true }),
|
||||
reportAgainstTrollingPost.relateTo(bobDerBaumeister, 'reviewed', {
|
||||
resourceId: 'p2',
|
||||
disable: true,
|
||||
closed: true,
|
||||
}),
|
||||
p2.update({ disabled: true, updatedAt: new Date().toISOString(), closed: true }),
|
||||
reportAgainstTrollingComment.relateTo(peterLustig, 'reviewed', {
|
||||
...disableVariables,
|
||||
resourceId: 'c1',
|
||||
disable: true,
|
||||
closed: true,
|
||||
}),
|
||||
trollingComment.update({ disabled: true, updatedAt: new Date().toISOString(), closed: true }),
|
||||
])
|
||||
authenticatedUser = null
|
||||
|
||||
await Promise.all(
|
||||
[...Array(30).keys()].map(i => {
|
||||
|
||||
@ -3,7 +3,7 @@ import helmet from 'helmet'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import CONFIG, { requiredConfigs } from './config'
|
||||
import middleware from './middleware'
|
||||
import { neode as getNeode, getDriver } from './bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from './bootstrap/neo4j'
|
||||
import decode from './jwt/decode'
|
||||
import schema from './schema'
|
||||
import webfinger from './activitypub/routes/webfinger'
|
||||
|
||||
@ -56,15 +56,15 @@
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.0.0"
|
||||
|
||||
"@babel/core@^7.1.0", "@babel/core@~7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab"
|
||||
integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==
|
||||
"@babel/core@^7.1.0", "@babel/core@~7.7.5":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e"
|
||||
integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.5.5"
|
||||
"@babel/generator" "^7.7.4"
|
||||
"@babel/helpers" "^7.7.4"
|
||||
"@babel/parser" "^7.7.4"
|
||||
"@babel/parser" "^7.7.5"
|
||||
"@babel/template" "^7.7.4"
|
||||
"@babel/traverse" "^7.7.4"
|
||||
"@babel/types" "^7.7.4"
|
||||
@ -280,10 +280,10 @@
|
||||
regenerator-runtime "^0.13.3"
|
||||
v8flags "^3.1.1"
|
||||
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.4.tgz#75ab2d7110c2cf2fa949959afb05fa346d2231bb"
|
||||
integrity sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71"
|
||||
integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==
|
||||
|
||||
"@babel/plugin-proposal-async-generator-functions@^7.7.4":
|
||||
version "7.7.4"
|
||||
@ -1101,60 +1101,72 @@
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||
|
||||
"@sentry/core@5.8.0":
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.8.0.tgz#bbfd2f4711491951a8e3a0e8fa8b172fdf7bff6f"
|
||||
integrity sha512-aAh2KLidIXJVGrxmHSVq2eVKbu7tZiYn5ylW6yzJXFetS5z4MA+JYaSBaG2inVYDEEqqMIkb17TyWxxziUDieg==
|
||||
"@sentry/apm@5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.10.1.tgz#2ec20cef0f87f9f638ff78dd5092e1e9d36c4b7d"
|
||||
integrity sha512-VSFK8giRG5/lN0YSaOw8+Cru/8MVevmoHZ5JC9iDIt0H6sGTUjOBKIqTZ0eq2Y99Vn0N9dkxjeT0rOIvsrg0gA==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.8.0"
|
||||
"@sentry/minimal" "5.8.0"
|
||||
"@sentry/types" "5.7.1"
|
||||
"@sentry/utils" "5.8.0"
|
||||
"@sentry/hub" "5.10.1"
|
||||
"@sentry/minimal" "5.10.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
"@sentry/utils" "5.10.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.8.0":
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.8.0.tgz#56aaeb7324cb66d90db838011cb0127f5558007f"
|
||||
integrity sha512-VdApn1ZCNwH1wwQwoO6pu53PM/qgHG+DQege0hbByluImpLBhAj9w50nXnF/8KzV4UoMIVbzCb6jXzMRmqqp9A==
|
||||
"@sentry/core@5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.1.tgz#356551f111d4df38e60852607cc8cde0ed8ccc76"
|
||||
integrity sha512-MbiasA/cuMB0+9zVBGi5YLWRj7CdFQJOM29Vp8rm3xMaQDH0KHarpny1gOgMiLu/O/r8itjiZwKu+9pxOWGbeA==
|
||||
dependencies:
|
||||
"@sentry/types" "5.7.1"
|
||||
"@sentry/utils" "5.8.0"
|
||||
"@sentry/hub" "5.10.1"
|
||||
"@sentry/minimal" "5.10.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
"@sentry/utils" "5.10.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.8.0":
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.8.0.tgz#b7ad5113504ab67f1ef2b0f465b7ba608e6b8dc5"
|
||||
integrity sha512-MIlFOgd+JvAUrBBmq7vr9ovRH1HvckhnwzHdoUPpKRBN+rQgTyZy1o6+kA2fASCbrRqFCP+Zk7EHMACKg8DpIw==
|
||||
"@sentry/hub@5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.1.tgz#3be4a0705cd0cd074be0aab0dc418ecb72885989"
|
||||
integrity sha512-g+P+0cj6vKdf6Ct4S47MxHwSMIjtIadOwBhb4Lqwij5YPtQ4LpVr10peKbE+FMMvCNQSvQnJEhTDko+AE7AoYw==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.8.0"
|
||||
"@sentry/types" "5.7.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
"@sentry/utils" "5.10.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@^5.9.0":
|
||||
version "5.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.9.0.tgz#9a8da70990e64c88a391ef86dcf29f43e0a52e59"
|
||||
integrity sha512-1CWwSGhRfMr4Bvt1i0vIms+BBZd4dBzlDyWpyCboodCXF1rTJRci9roQ+Wh9XWwFEWvgDD2PzuKzfvu638v2Wg==
|
||||
"@sentry/minimal@5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.1.tgz#37104f81ef3b333c0f9e77ac94bfed348070dea3"
|
||||
integrity sha512-oKrLvKaah0xGVIYbS1I7dVbo73aWssfiT2ypl9DYt8MAFiwfiiXz68FlG4z9dPZ2jSz9Jm2SAYHFaYLvU26TBQ==
|
||||
dependencies:
|
||||
"@sentry/core" "5.8.0"
|
||||
"@sentry/hub" "5.8.0"
|
||||
"@sentry/types" "5.7.1"
|
||||
"@sentry/utils" "5.8.0"
|
||||
"@sentry/hub" "5.10.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@^5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.10.1.tgz#cafbf3b0918c98fb9f99803ffe50056e32194bef"
|
||||
integrity sha512-kard7OXQDvYqmQD93bOkYhznqrbsiFNZ6+dIi13eo/kc2Au+v1Th1mGvr9JDRE/X07z6vJMYMiorKd351G3p/A==
|
||||
dependencies:
|
||||
"@sentry/apm" "5.10.1"
|
||||
"@sentry/core" "5.10.1"
|
||||
"@sentry/hub" "5.10.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
"@sentry/utils" "5.10.1"
|
||||
cookie "^0.3.1"
|
||||
https-proxy-agent "^3.0.0"
|
||||
lru_map "^0.3.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.7.1":
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.7.1.tgz#4c4c1d4d891b6b8c2c3c7b367d306a8b1350f090"
|
||||
integrity sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ==
|
||||
"@sentry/types@5.10.0":
|
||||
version "5.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.10.0.tgz#4f0ba31b6e4d5371112c38279f11f66c73b43746"
|
||||
integrity sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==
|
||||
|
||||
"@sentry/utils@5.8.0":
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.8.0.tgz#34683088159b9935f973b6e6cad1a1cc26bbddac"
|
||||
integrity sha512-KDxUvBSYi0/dHMdunbxAxD3389pcQioLtcO6CI6zt/nJXeVFolix66cRraeQvqupdLhvOk/el649W4fCPayTHw==
|
||||
"@sentry/utils@5.10.1":
|
||||
version "5.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.1.tgz#eeb3ede85a9b5b1cd1aad7e3157052bee0d42551"
|
||||
integrity sha512-zdv03sINfJ8QXSHP49845qhkbdNUrX20AagUY+Arq2zxmM4XxnRVA7dtWDkyy55bTt0ziRuSikBxR3266t8mDg==
|
||||
dependencies:
|
||||
"@sentry/types" "5.7.1"
|
||||
"@sentry/types" "5.10.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
@ -1638,10 +1650,10 @@ apollo-engine-reporting-protobuf@^0.4.4:
|
||||
dependencies:
|
||||
"@apollo/protobufjs" "^1.0.3"
|
||||
|
||||
apollo-engine-reporting@^1.4.10:
|
||||
version "1.4.10"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.10.tgz#cca245133906ed4ece125e48cb95dd959f3af2f6"
|
||||
integrity sha512-0nEawO9cudbXHCxRvnDUWKqCxPAGEstghUFd5sB67lIGuh91MYeLuwN1iTfqUdwF1feEGHn636zVVUYlXGOlvQ==
|
||||
apollo-engine-reporting@^1.4.11:
|
||||
version "1.4.11"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.11.tgz#ea4501925c201e62729a11ce36284a89f1eaa4f5"
|
||||
integrity sha512-7ZkbOGvPfWppN8+1KHzyHPrJTMOmrMUy38unao2c9TTToOAnEvx2MtUTo6mr3aw/g8UQYUf0x2Cq+K2YSlUTPw==
|
||||
dependencies:
|
||||
apollo-engine-reporting-protobuf "^0.4.4"
|
||||
apollo-graphql "^0.3.4"
|
||||
@ -1719,10 +1731,10 @@ apollo-server-caching@^0.5.0:
|
||||
dependencies:
|
||||
lru-cache "^5.0.0"
|
||||
|
||||
apollo-server-core@^2.9.12:
|
||||
version "2.9.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.12.tgz#c8ed48540762913242eef5fce0da8b59b131a1e8"
|
||||
integrity sha512-jhGr2R655PSwUUBweXDl+0F3oa74Elu5xXF+88ymUUej34EwBUCqz97wPqR07BEuyxaAlRfZwPMvKaHhMUKg5g==
|
||||
apollo-server-core@^2.9.13:
|
||||
version "2.9.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.13.tgz#29fee69be56d30605b0a06cd755fd39e0409915f"
|
||||
integrity sha512-iXTGNCtouB0Xe37ySovuZO69NBYOByJlZfUc87gj0pdcz0WbdfUp7qUtNzy3onp63Zo60TFkHWhGNcBJYFluzw==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.4.0"
|
||||
"@apollographql/graphql-playground-html" "1.6.24"
|
||||
@ -1730,7 +1742,7 @@ apollo-server-core@^2.9.12:
|
||||
"@types/ws" "^6.0.0"
|
||||
apollo-cache-control "^0.8.8"
|
||||
apollo-datasource "^0.6.3"
|
||||
apollo-engine-reporting "^1.4.10"
|
||||
apollo-engine-reporting "^1.4.11"
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
apollo-server-errors "^2.3.4"
|
||||
@ -1759,10 +1771,10 @@ apollo-server-errors@^2.3.4:
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz#b70ef01322f616cbcd876f3e0168a1a86b82db34"
|
||||
integrity sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==
|
||||
|
||||
apollo-server-express@^2.9.12, apollo-server-express@^2.9.7:
|
||||
version "2.9.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.12.tgz#e779ea2c107fcc63b0c9b888a4cbf0f65af6d505"
|
||||
integrity sha512-4Ev8MY7m23mSzwO/BvLTy97a/68IP/wZoCRBn2R81OoZt9/GxlvvYZGvozJCXYsQt1qAbIT4Sn05LmqawsI98w==
|
||||
apollo-server-express@^2.9.13, apollo-server-express@^2.9.7:
|
||||
version "2.9.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.13.tgz#abb00bcf85d86a6e0e9105ce3b7fae9a7748156b"
|
||||
integrity sha512-M306e07dpZ8YpZx4VBYa0FWlt+wopj4Bwn0Iy1iJ6VjaRyGx2HCUJvLpHZ+D0TIXtQ2nX3DTYeOouVaDDwJeqQ==
|
||||
dependencies:
|
||||
"@apollographql/graphql-playground-html" "1.6.24"
|
||||
"@types/accepts" "^1.3.5"
|
||||
@ -1770,7 +1782,7 @@ apollo-server-express@^2.9.12, apollo-server-express@^2.9.7:
|
||||
"@types/cors" "^2.8.4"
|
||||
"@types/express" "4.17.1"
|
||||
accepts "^1.3.5"
|
||||
apollo-server-core "^2.9.12"
|
||||
apollo-server-core "^2.9.13"
|
||||
apollo-server-types "^0.2.8"
|
||||
body-parser "^1.18.3"
|
||||
cors "^2.8.4"
|
||||
@ -1788,12 +1800,12 @@ apollo-server-plugin-base@^0.6.8:
|
||||
dependencies:
|
||||
apollo-server-types "^0.2.8"
|
||||
|
||||
apollo-server-testing@~2.9.12:
|
||||
version "2.9.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.12.tgz#2dcad49f399f50bf3d8bbaa0c753eb7eca48ff10"
|
||||
integrity sha512-TFHXA8HdD++FzbCvrQryFqALvX2Mrea1bNu7pi5L5wpjB5Ug3FudasYGhy6tl8BaStPxsugWngchuD3IPSBrgg==
|
||||
apollo-server-testing@~2.9.13:
|
||||
version "2.9.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.13.tgz#7a4efc0eb01d7297716f089121c7440a620bb640"
|
||||
integrity sha512-c1xl4g5KhMfPpL5xdzxPJLY53+yK/kMAWxIASthRrOSZNgStTe7pCAJ06Nk3NB8M5GwfJK3cJiVkLfZRSt9+jQ==
|
||||
dependencies:
|
||||
apollo-server-core "^2.9.12"
|
||||
apollo-server-core "^2.9.13"
|
||||
|
||||
apollo-server-types@^0.2.8:
|
||||
version "0.2.8"
|
||||
@ -1804,13 +1816,13 @@ apollo-server-types@^0.2.8:
|
||||
apollo-server-caching "^0.5.0"
|
||||
apollo-server-env "^2.4.3"
|
||||
|
||||
apollo-server@~2.9.12:
|
||||
version "2.9.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.12.tgz#3fe28c361ee373d52ae38ca190869508b0c532c0"
|
||||
integrity sha512-Q+qaBTgTxb2vwqyh7NTHs9rOmadbuKw34SgeAOLsCnr3MLVjisa50fL3nQrGbhOGfRaroF8SSZYgya0tvnefig==
|
||||
apollo-server@~2.9.13:
|
||||
version "2.9.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.13.tgz#f93005a2a9d2b29a047f170eeb900bf464bfe62d"
|
||||
integrity sha512-Aedj/aHRMCDMUwtM+hXiliX1OkFNl1NyiQUADbwm6AMV3OrfT9TUbbSI1AN2qsx+rg6dIhpAiHLUf73uDy3V/g==
|
||||
dependencies:
|
||||
apollo-server-core "^2.9.12"
|
||||
apollo-server-express "^2.9.12"
|
||||
apollo-server-core "^2.9.13"
|
||||
apollo-server-express "^2.9.13"
|
||||
express "^4.0.0"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tools "^4.0.0"
|
||||
@ -5842,10 +5854,10 @@ metascraper-url@^5.8.7:
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.8.7"
|
||||
|
||||
metascraper-video@^5.8.7:
|
||||
version "5.8.7"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.7.tgz#7a5d1e8955f9a65891908eef319683b6176765a2"
|
||||
integrity sha512-J4OJlB+nla8ITwqH2H6dgQ+nrecYILVhsGFKG54p2qsSokXwgZrQ4P7WhUMd0VpBsYuebcRgdzY8OGUDb+7l0Q==
|
||||
metascraper-video@^5.8.9:
|
||||
version "5.8.9"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.8.9.tgz#23c0fe71fae5088bc8e11bfa537eff80658aa6d9"
|
||||
integrity sha512-xaimkGz1Txsd9qHUN2U5HyFMP8tkrb5LuW8bCo+0kdTu5c00HGurvs0/BpWrTW/CzUQBNl/uEybeDXm8J++03g==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.8.7"
|
||||
lodash "~4.17.15"
|
||||
@ -5860,10 +5872,10 @@ metascraper-youtube@^5.8.9:
|
||||
is-reachable "~4.0.0"
|
||||
p-locate "~4.1.0"
|
||||
|
||||
metascraper@^5.8.8:
|
||||
version "5.8.8"
|
||||
resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.8.8.tgz#9fbf6913f55bb448a9195e40e38f3599bc5a818f"
|
||||
integrity sha512-z4G3SXGBVnd0+FSHqR3LJF+6emO03GlY2KoOTqsFCnRuY0B72nJyR/NRRYLn4PRX6PMQ6QZ+GWKa7oxBX6hZqQ==
|
||||
metascraper@^5.8.9:
|
||||
version "5.8.9"
|
||||
resolved "https://registry.yarnpkg.com/metascraper/-/metascraper-5.8.9.tgz#7bb468f9660bd86be8dd774cab3457d098b87e61"
|
||||
integrity sha512-vuOwnSaGIG8346ZAQCE+YqvpzFVXfaMvCUdLbb8spobz7BG3945WNa43NjSl2HK5iH1WYOibvSYRZdL6wQsRJg==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.8.7"
|
||||
cheerio "~1.0.0-rc.2"
|
||||
@ -6097,10 +6109,10 @@ neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.6:
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
neo4j-graphql-js@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.9.3.tgz#91afb0631eb35014110022a74e572c9eb065d281"
|
||||
integrity sha512-SzIX3BYE3EsKp/XU8Wog97TzfsrQdrKp/t7le7tnODojcBd5eSVJyKPrbaKqcnWMkLzKzO/SRX9PMQ2cDdXUKw==
|
||||
neo4j-graphql-js@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.10.0.tgz#4298793756d839dedb98bc3e50a2bd40a311874d"
|
||||
integrity sha512-jRdIyw+DHg9gfB6pWKb1ZHMR9rXIl7qf51efjUHIRHRbVR3RCcw1cKyONkq4LE8v2bHc7QDrKwJs+GQ1SRxDug==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
"@babel/runtime-corejs2" "^7.5.5"
|
||||
@ -6206,10 +6218,10 @@ nodemailer-html-to-text@^3.1.0:
|
||||
dependencies:
|
||||
html-to-text "^5.1.1"
|
||||
|
||||
nodemailer@^6.3.1:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
|
||||
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
|
||||
nodemailer@^6.4.1:
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.1.tgz#f70b40355b7b08f1f80344b353970a4f8f664370"
|
||||
integrity sha512-mSQAzMim8XIC1DemK9TifDTIgASfoJEllG5aC1mEtZeZ+FQyrSOdGBRth6JRA1ERzHQCET3QHVSd9Kc6mh356g==
|
||||
|
||||
nodemon@~2.0.1:
|
||||
version "2.0.1"
|
||||
@ -7440,11 +7452,6 @@ serve-static@1.14.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.3"
|
||||
send "0.17.1"
|
||||
|
||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"BACKEND_HOST": "http://localhost:4000",
|
||||
"NEO4J_URI": "bolt://localhost:7687",
|
||||
"NEO4J_USERNAME": "neo4j",
|
||||
"NEO4J_PASSWORD": "letmein"
|
||||
}
|
||||
@ -16,12 +16,7 @@ First, you have to tell cypress how to connect to your local neo4j database
|
||||
among other things. You can copy our template configuration and change the new
|
||||
file according to your needs.
|
||||
|
||||
Make sure you are at the root level of the project. Then:
|
||||
```bash
|
||||
# in the top level folder Human-Connection/
|
||||
$ cp cypress.env.template.json cypress.env.json
|
||||
```
|
||||
To start the services that are required for cypress testing, run this:
|
||||
To start the services that are required for cypress testing, run:
|
||||
|
||||
```bash
|
||||
# in the top level folder Human-Connection/
|
||||
|
||||
@ -3,6 +3,14 @@ import { When, Then } from "cypress-cucumber-preprocessor/steps";
|
||||
const narratorAvatar =
|
||||
"https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg";
|
||||
|
||||
When("I type in a comment with {int} characters", size => {
|
||||
var c="";
|
||||
for (var i = 0; i < size; i++) {
|
||||
c += "c"
|
||||
}
|
||||
cy.get(".editor .ProseMirror").type(c);
|
||||
});
|
||||
|
||||
Then("I click on the {string} button", text => {
|
||||
cy.get("button")
|
||||
.contains(text)
|
||||
@ -23,6 +31,16 @@ Then("I should see my comment", () => {
|
||||
.should("contain", "today at");
|
||||
});
|
||||
|
||||
Then("I should see the entirety of my comment", () => {
|
||||
cy.get("div.comment")
|
||||
.should("not.contain", "show more")
|
||||
});
|
||||
|
||||
Then("I should see an abreviated version of my comment", () => {
|
||||
cy.get("div.comment")
|
||||
.should("contain", "show more")
|
||||
});
|
||||
|
||||
Then("the editor should be cleared", () => {
|
||||
cy.get(".ProseMirror p").should("have.class", "is-empty");
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||
import { VERSION } from '../../constants/terms-and-conditions-version.js'
|
||||
import { gql } from '../../../backend/src/helpers/jest'
|
||||
|
||||
/* global cy */
|
||||
|
||||
@ -128,9 +129,9 @@ Given('somebody reported the following posts:', table => {
|
||||
cy.factory()
|
||||
.create('User', submitter)
|
||||
.authenticateAs(submitter)
|
||||
.mutate(`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
report(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
|
||||
type
|
||||
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
|
||||
id
|
||||
}
|
||||
}`, {
|
||||
resourceId,
|
||||
|
||||
@ -20,3 +20,19 @@ Feature: Post Comment
|
||||
Then my comment should be successfully created
|
||||
And I should see my comment
|
||||
And the editor should be cleared
|
||||
|
||||
Scenario: View medium length comments
|
||||
Given I visit "post/bWBjpkTKZp/101-essays"
|
||||
And I type in a comment with 305 characters
|
||||
And I click on the "Comment" button
|
||||
Then my comment should be successfully created
|
||||
And I should see the entirety of my comment
|
||||
And the editor should be cleared
|
||||
|
||||
Scenario: View long comments
|
||||
Given I visit "post/bWBjpkTKZp/101-essays"
|
||||
And I type in a comment with 1205 characters
|
||||
And I click on the "Comment" button
|
||||
Then my comment should be successfully created
|
||||
And I should see an abreviated version of my comment
|
||||
And the editor should be cleared
|
||||
|
||||
@ -18,8 +18,8 @@ import helpers from "./helpers";
|
||||
import users from "../fixtures/users.json";
|
||||
import { GraphQLClient, request } from 'graphql-request'
|
||||
import { gql } from '../../backend/src/helpers/jest'
|
||||
import config from '../../backend/src/config'
|
||||
|
||||
const backendHost = Cypress.env('BACKEND_HOST')
|
||||
const switchLang = name => {
|
||||
cy.get(".locale-menu").click();
|
||||
cy.contains(".locale-menu-popover a", name).click();
|
||||
@ -31,7 +31,7 @@ const authenticatedHeaders = async (variables) => {
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
`
|
||||
const response = await request(backendHost, mutation, variables)
|
||||
const response = await request(config.GRAPHQL_URI, mutation, variables)
|
||||
return { authorization: `Bearer ${response.login}` }
|
||||
}
|
||||
|
||||
@ -100,8 +100,7 @@ Cypress.Commands.add(
|
||||
'authenticateAs',
|
||||
async ({email, password}) => {
|
||||
const headers = await authenticatedHeaders({ email, password })
|
||||
console.log(headers)
|
||||
return new GraphQLClient(backendHost, { headers })
|
||||
return new GraphQLClient(config.GRAPHQL_URI, { headers })
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import Factory from '../../backend/src/seed/factories'
|
||||
import { getDriver, neode as getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||
import setupNeode from '../../backend/src/bootstrap/neode'
|
||||
import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||
import neode from 'neode'
|
||||
|
||||
const backendHost = Cypress.env('SEED_SERVER_HOST')
|
||||
const neo4jConfigs = {
|
||||
uri: Cypress.env('NEO4J_URI'),
|
||||
username: Cypress.env('NEO4J_USERNAME'),
|
||||
password: Cypress.env('NEO4J_PASSWORD')
|
||||
}
|
||||
const neo4jDriver = getDriver(neo4jConfigs)
|
||||
const factoryOptions = { seedServerHost: backendHost, neo4jDriver, neodeInstance: setupNeode(neo4jConfigs)}
|
||||
const neo4jDriver = getDriver()
|
||||
const neodeInstance = getNeode()
|
||||
const factoryOptions = { neo4jDriver, neodeInstance }
|
||||
const factory = Factory(factoryOptions)
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -18,7 +12,7 @@ beforeEach(async () => {
|
||||
})
|
||||
|
||||
Cypress.Commands.add('neode', () => {
|
||||
return setupNeode(neo4jConfigs)
|
||||
return neodeInstance
|
||||
})
|
||||
Cypress.Commands.add(
|
||||
'first',
|
||||
|
||||
55
neo4j/change_disabled_relationship_to_report_node.sh
Executable file
55
neo4j/change_disabled_relationship_to_report_node.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Database manipulation is not possible without connecting to the database."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "
|
||||
// convert old DISABLED to new REVIEWED-Report-BELONGS_TO structure
|
||||
MATCH (moderator:User)-[disabled:DISABLED]->(disabledResource)
|
||||
WHERE disabledResource:User OR disabledResource:Comment OR disabledResource:Post
|
||||
DELETE disabled
|
||||
CREATE (moderator)-[review:REVIEWED]->(report:Report)-[:BELONGS_TO]->(disabledResource)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
|
||||
// if disabledResource has no filed report, then create a moderators default filed report
|
||||
WITH moderator, disabledResource, report
|
||||
OPTIONAL MATCH (disabledResourceReporter:User)-[existingFiledReport:FILED]->(disabledResource)
|
||||
FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NULL THEN [1] ELSE [] END |
|
||||
CREATE (moderator)-[addModeratorReport:FILED]->(report)
|
||||
SET addModeratorReport.createdAt = toString(datetime()), addModeratorReport.reasonCategory = 'other', addModeratorReport.reasonDescription = 'Old DISABLED relations didn't enforce mandatory reporting !!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.'
|
||||
)
|
||||
FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NOT NULL THEN [1] ELSE [] END |
|
||||
CREATE (disabledResourceReporter)-[moveModeratorReport:FILED]->(report)
|
||||
SET moveModeratorReport = existingFiledReport
|
||||
DELETE existingFiledReport
|
||||
)
|
||||
|
||||
RETURN disabledResource {.id};
|
||||
" | cypher-shell
|
||||
|
||||
echo "
|
||||
// for FILED resources without DISABLED relation which are handled above, create new FILED-Report-BELONGS_TO structure
|
||||
MATCH (reporter:User)-[oldReport:REPORTED]->(notDisabledResource)
|
||||
WHERE notDisabledResource:User OR notDisabledResource:Comment OR notDisabledResource:Post
|
||||
MERGE (report:Report)-[:BELONGS_TO]->(notDisabledResource)
|
||||
ON CREATE SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
CREATE (reporter)-[filed:FILED]->(report)
|
||||
SET report = oldReport
|
||||
DELETE oldReport
|
||||
|
||||
RETURN notDisabledResource {.id};
|
||||
" | cypher-shell
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Database manipulation is not possible without connecting to the database."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "
|
||||
MATCH (submitter:User)-[:REPORTED]->(report:Report)-[:REPORTED]->(resource)
|
||||
DETACH DELETE report
|
||||
CREATE (submitter)-[reported:REPORTED]->(resource)
|
||||
SET reported.createdAt = toString(datetime())
|
||||
SET reported.reasonCategory = 'other'
|
||||
SET reported.reasonDescription = '!!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.'
|
||||
RETURN reported;
|
||||
" | cypher-shell
|
||||
30
neo4j/db_manipulation/add_image_aspect_ratio.sh
Executable file
30
neo4j/db_manipulation/add_image_aspect_ratio.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Database manipulation is not possible without connecting to the database."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
for image in uploads/*; do
|
||||
[ -e "$image" ] || continue
|
||||
IMAGE_WIDTH=$( identify -format '%w' "$image" )
|
||||
IMAGE_HEIGHT=$( identify -format '%h' "$image" )
|
||||
IMAGE_ASPECT_RATIO=$(echo | awk "{ print ${IMAGE_WIDTH}/${IMAGE_HEIGHT}}")
|
||||
|
||||
|
||||
echo "$image"
|
||||
echo "$IMAGE_ASPECT_RATIO"
|
||||
echo "
|
||||
match (post:Post {image: '/"${image}"'})
|
||||
set post.imageAspectRatio = "${IMAGE_ASPECT_RATIO}"
|
||||
return post;
|
||||
" | cypher-shell
|
||||
done
|
||||
51
neo4j/db_manipulation/change_disabled_relationship_to_report_node.sh
Executable file
51
neo4j/db_manipulation/change_disabled_relationship_to_report_node.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Database manipulation is not possible without connecting to the database."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "
|
||||
:begin
|
||||
MATCH(user)-[reported:REPORTED]->(resource)
|
||||
WITH reported, resource, COLLECT(user) as users
|
||||
MERGE(report:Report)-[:BELONGS_TO]->(resource)
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
WITH report, users, reported
|
||||
UNWIND users as user
|
||||
MERGE (user)-[filed:FILED]->(report)
|
||||
SET filed = reported
|
||||
DELETE reported;
|
||||
|
||||
MATCH(moderator)-[disabled:DISABLED]->(resource)
|
||||
MATCH(report:Report)-[:BELONGS_TO]->(resource)
|
||||
WITH disabled, resource, COLLECT(moderator) as moderators, report
|
||||
DELETE disabled
|
||||
WITH report, moderators, disabled
|
||||
UNWIND moderators as moderator
|
||||
MERGE (moderator)-[review:REVIEWED {disable: true}]->(report)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true;
|
||||
|
||||
MATCH(moderator)-[disabled:DISABLED]->(resource)
|
||||
WITH disabled, resource, COLLECT(moderator) as moderators
|
||||
MERGE(report:Report)-[:BELONGS_TO]->(resource)
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
DELETE disabled
|
||||
WITH report, moderators, disabled
|
||||
UNWIND moderators as moderator
|
||||
MERGE(moderator)-[filed:FILED]->(report)
|
||||
SET filed.createdAt = toString(datetime()), filed.reasonCategory = 'other', filed.reasonDescription = 'Old DISABLED relations didn\'t enforce mandatory reporting !!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.'
|
||||
MERGE (moderator)-[review:REVIEWED {disable: true}]->(report)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true;
|
||||
:commit
|
||||
" | cypher-shell
|
||||
@ -30,7 +30,7 @@
|
||||
"cross-env": "^6.0.3",
|
||||
"cucumber": "^6.0.5",
|
||||
"cypress": "^3.7.0",
|
||||
"cypress-cucumber-preprocessor": "^1.17.0",
|
||||
"cypress-cucumber-preprocessor": "^1.18.0",
|
||||
"cypress-file-upload": "^3.5.0",
|
||||
"cypress-plugin-retries": "^1.5.0",
|
||||
"date-fns": "^2.8.1",
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 808b3c5a9523505cb80b20b50348d29ba9932845
|
||||
Subproject commit 7ef83405006b016fe45b476ed6e34ec189d7d283
|
||||
@ -6,14 +6,15 @@
|
||||
|
||||
```bash
|
||||
# install all dependencies
|
||||
$ cd webapp/
|
||||
$ yarn install
|
||||
```
|
||||
|
||||
Copy:
|
||||
|
||||
```text
|
||||
# in webapp/
|
||||
cp .env.template .env
|
||||
cp cypress.env.template.json cypress.env.json
|
||||
```
|
||||
|
||||
Configure the files according to your needs and your local setup.
|
||||
|
||||
@ -1,19 +1,9 @@
|
||||
<template>
|
||||
<div id="comments">
|
||||
<h3 style="margin-top: -10px;">
|
||||
<span>
|
||||
<base-icon name="comments" />
|
||||
<ds-tag
|
||||
v-if="post.comments.length"
|
||||
style="margin-top: -4px; margin-left: -12px; position: absolute;"
|
||||
color="primary"
|
||||
size="small"
|
||||
round
|
||||
>
|
||||
{{ post.comments.length }}
|
||||
</ds-tag>
|
||||
<span class="list-title">{{ $t('common.comment', null, 0) }}</span>
|
||||
</span>
|
||||
<counter-icon icon="comments" :count="post.comments.length">
|
||||
{{ $t('common.comment', null, 0) }}
|
||||
</counter-icon>
|
||||
</h3>
|
||||
<ds-space margin-bottom="large" />
|
||||
<div v-if="post.comments && post.comments.length" id="comments" class="comments">
|
||||
@ -31,12 +21,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||
import Comment from '~/components/Comment/Comment'
|
||||
import scrollToAnchor from '~/mixins/scrollToAnchor'
|
||||
|
||||
export default {
|
||||
mixins: [scrollToAnchor],
|
||||
components: {
|
||||
CounterIcon,
|
||||
Comment,
|
||||
},
|
||||
props: {
|
||||
@ -58,9 +50,3 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-title {
|
||||
margin-left: $space-x-small;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -85,7 +85,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'post.menu.delete')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('delete')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('confirm', 'delete')
|
||||
})
|
||||
})
|
||||
|
||||
@ -166,7 +166,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'comment.menu.delete')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('delete')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('confirm', 'delete')
|
||||
})
|
||||
})
|
||||
|
||||
@ -332,7 +332,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'release.contribution.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release', 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release comments', () => {
|
||||
@ -350,7 +350,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'release.comment.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release', 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release users', () => {
|
||||
@ -368,7 +368,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'release.user.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release', 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
|
||||
it('can release organizations', () => {
|
||||
@ -386,7 +386,7 @@ describe('ContentMenu.vue', () => {
|
||||
.filter(item => item.text() === 'release.organization.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release', 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8')
|
||||
expect(openModalSpy).toHaveBeenCalledWith('release')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ export default {
|
||||
routes.push({
|
||||
name: this.$t(`post.menu.delete`),
|
||||
callback: () => {
|
||||
this.openModal('delete')
|
||||
this.openModal('confirm', 'delete')
|
||||
},
|
||||
icon: 'trash',
|
||||
})
|
||||
@ -108,7 +108,7 @@ export default {
|
||||
routes.push({
|
||||
name: this.$t(`comment.menu.delete`),
|
||||
callback: () => {
|
||||
this.openModal('delete')
|
||||
this.openModal('confirm', 'delete')
|
||||
},
|
||||
icon: 'trash',
|
||||
})
|
||||
@ -137,7 +137,7 @@ export default {
|
||||
routes.push({
|
||||
name: this.$t(`release.${this.resourceType}.title`),
|
||||
callback: () => {
|
||||
this.openModal('release', this.resource.id)
|
||||
this.openModal('release')
|
||||
},
|
||||
icon: 'eye',
|
||||
})
|
||||
@ -190,13 +190,13 @@ export default {
|
||||
}
|
||||
toggleMenu()
|
||||
},
|
||||
openModal(dialog) {
|
||||
openModal(dialog, modalDataName = null) {
|
||||
this.$store.commit('modal/SET_OPEN', {
|
||||
name: dialog,
|
||||
data: {
|
||||
type: this.resourceType,
|
||||
resource: this.resource,
|
||||
modalsData: this.modalsData,
|
||||
modalData: modalDataName ? this.modalsData[modalDataName] : {},
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@ -198,6 +198,7 @@ describe('ContributionForm.vue', () => {
|
||||
id: null,
|
||||
categoryIds: ['cat12'],
|
||||
imageUpload: null,
|
||||
imageAspectRatio: null,
|
||||
image: null,
|
||||
},
|
||||
}
|
||||
@ -352,6 +353,7 @@ describe('ContributionForm.vue', () => {
|
||||
categoryIds: ['cat12'],
|
||||
image,
|
||||
imageUpload: null,
|
||||
imageAspectRatio: null,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -7,7 +7,11 @@
|
||||
@submit="submit"
|
||||
>
|
||||
<template slot-scope="{ errors }">
|
||||
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
|
||||
<hc-teaser-image
|
||||
:contribution="contribution"
|
||||
@addTeaserImage="addTeaserImage"
|
||||
@addImageAspectRatio="addImageAspectRatio"
|
||||
>
|
||||
<img
|
||||
v-if="contribution"
|
||||
class="contribution-image"
|
||||
@ -128,6 +132,7 @@ export default {
|
||||
title: '',
|
||||
content: '',
|
||||
teaserImage: null,
|
||||
imageAspectRatio: null,
|
||||
image: null,
|
||||
language: null,
|
||||
categoryIds: [],
|
||||
@ -190,6 +195,7 @@ export default {
|
||||
content,
|
||||
image,
|
||||
teaserImage,
|
||||
imageAspectRatio,
|
||||
categoryIds,
|
||||
} = this.form
|
||||
this.loading = true
|
||||
@ -204,6 +210,7 @@ export default {
|
||||
language,
|
||||
image,
|
||||
imageUpload: teaserImage,
|
||||
imageAspectRatio,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
@ -227,6 +234,9 @@ export default {
|
||||
addTeaserImage(file) {
|
||||
this.form.teaserImage = file
|
||||
},
|
||||
addImageAspectRatio(aspectRatio) {
|
||||
this.form.imageAspectRatio = aspectRatio
|
||||
},
|
||||
categoryIds(categories) {
|
||||
return categories.map(c => c.id)
|
||||
},
|
||||
|
||||
@ -64,9 +64,9 @@ describe('DropdownFilter.vue', () => {
|
||||
expect(unreadLink.text()).toEqual('Unread')
|
||||
})
|
||||
|
||||
it('clicking on menu item emits filterNotifications', () => {
|
||||
it('clicking on menu item emits filter', () => {
|
||||
allLink.trigger('click')
|
||||
expect(wrapper.emitted().filterNotifications[0]).toEqual(
|
||||
expect(wrapper.emitted().filter[0]).toEqual(
|
||||
propsData.filterOptions.filter(option => option.label === 'All'),
|
||||
)
|
||||
})
|
||||
|
||||
@ -20,10 +20,10 @@ storiesOf('DropdownFilter', module)
|
||||
selected: filterOptions[0].label,
|
||||
}),
|
||||
methods: {
|
||||
filterNotifications: action('filterNotifications'),
|
||||
filter: action('filter'),
|
||||
},
|
||||
template: `<dropdown-filter
|
||||
@filterNotifications="filterNotifications"
|
||||
@filter="filter"
|
||||
:filterOptions="filterOptions"
|
||||
:selected="selected"
|
||||
/>`,
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
class="dropdown-menu-item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="filterNotifications(item.route, toggleMenu)"
|
||||
@click.stop.prevent="filter(item.route, toggleMenu)"
|
||||
>
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
@ -44,8 +44,8 @@ export default {
|
||||
filterOptions: { type: Array, default: () => [] },
|
||||
},
|
||||
methods: {
|
||||
filterNotifications(option, toggleMenu) {
|
||||
this.$emit('filterNotifications', option)
|
||||
filter(option, toggleMenu) {
|
||||
this.$emit('filter', option)
|
||||
toggleMenu()
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,23 +6,36 @@ const localVue = global.localVue
|
||||
|
||||
describe('MasonryGrid', () => {
|
||||
let wrapper
|
||||
let masonryGrid
|
||||
let masonryGridItem
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(MasonryGrid, { localVue })
|
||||
masonryGrid = wrapper.vm.$children[0]
|
||||
masonryGridItem = wrapper.vm.$children[0]
|
||||
})
|
||||
|
||||
it('adds the "reset-grid-height" class when one or more children are updating', () => {
|
||||
masonryGrid.$emit('calculating-item-height')
|
||||
it('adds the "reset-grid-height" class when itemsCalculating is more than 0', () => {
|
||||
wrapper.setData({ itemsCalculating: 1 })
|
||||
|
||||
expect(wrapper.classes()).toContain('reset-grid-height')
|
||||
})
|
||||
|
||||
it('removes the "reset-grid-height" class when all children have completed updating', () => {
|
||||
wrapper.setData({ itemsCalculating: 1 })
|
||||
masonryGrid.$emit('finished-calculating-item-height')
|
||||
it('removes the "reset-grid-height" class when itemsCalculating is 0', () => {
|
||||
wrapper.setData({ itemsCalculating: 0 })
|
||||
|
||||
expect(wrapper.classes()).not.toContain('reset-grid-height')
|
||||
})
|
||||
|
||||
it('adds 1 to itemsCalculating when a child emits "calculating-item-height"', () => {
|
||||
wrapper.setData({ itemsCalculating: 0 })
|
||||
masonryGridItem.$emit('calculating-item-height')
|
||||
|
||||
expect(wrapper.vm.itemsCalculating).toBe(1)
|
||||
})
|
||||
|
||||
it('subtracts 1 from itemsCalculating when a child emits "finished-calculating-item-height"', () => {
|
||||
wrapper.setData({ itemsCalculating: 2 })
|
||||
masonryGridItem.$emit('finished-calculating-item-height')
|
||||
|
||||
expect(wrapper.vm.itemsCalculating).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<ds-grid
|
||||
:min-column-width="300"
|
||||
v-on:calculating-item-height="startCalculation"
|
||||
v-on:finished-calculating-item-height="endCalculation"
|
||||
:class="[itemsCalculating ? 'reset-grid-height' : '']"
|
||||
@ -27,7 +26,14 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
/* dirty fix to override broken styleguide inline-styles */
|
||||
.ds-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)) !important;
|
||||
gap: 16px !important;
|
||||
grid-auto-rows: 20px;
|
||||
}
|
||||
|
||||
.reset-grid-height {
|
||||
grid-auto-rows: auto !important;
|
||||
align-items: self-start;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { config, shallowMount } from '@vue/test-utils'
|
||||
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import MasonryGridItem from './MasonryGridItem'
|
||||
|
||||
const localVue = global.localVue
|
||||
@ -9,24 +8,41 @@ config.stubs['ds-grid-item'] = '<span><slot /></span>'
|
||||
describe('MasonryGridItem', () => {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(MasonryGridItem, { localVue })
|
||||
wrapper.vm.$parent.$emit = jest.fn()
|
||||
describe('given an imageAspectRatio', () => {
|
||||
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
|
||||
const propsData = { imageAspectRatio: 2 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(13)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => {
|
||||
const propsData = { imageAspectRatio: 1.1 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(15)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.7 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(18)
|
||||
})
|
||||
|
||||
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => {
|
||||
const propsData = { imageAspectRatio: 0.3 }
|
||||
wrapper = mount(MasonryGridItem, { localVue, propsData })
|
||||
|
||||
expect(wrapper.vm.rowSpan).toBe(25)
|
||||
})
|
||||
})
|
||||
|
||||
it('emits "calculating-item-height" when starting calculation', async () => {
|
||||
wrapper.vm.calculateItemHeight()
|
||||
await wrapper.vm.$nextTick()
|
||||
describe('given no aspect ratio', () => {
|
||||
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
|
||||
wrapper = mount(MasonryGridItem, { localVue })
|
||||
|
||||
const firstCallArgument = wrapper.vm.$parent.$emit.mock.calls[0][0]
|
||||
expect(firstCallArgument).toBe('calculating-item-height')
|
||||
})
|
||||
|
||||
it('emits "finished-calculating-item-height" after the calculation', async () => {
|
||||
wrapper.vm.calculateItemHeight()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const secondCallArgument = wrapper.vm.$parent.$emit.mock.calls[1][0]
|
||||
expect(secondCallArgument).toBe('finished-calculating-item-height')
|
||||
expect(wrapper.vm.rowSpan).toBe(8)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,15 +5,33 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const landscapeRatio = 1.3
|
||||
const squareRatio = 1
|
||||
const portraitRatio = 0.7
|
||||
|
||||
const getRowSpan = aspectRatio => {
|
||||
if (aspectRatio >= landscapeRatio) return 13
|
||||
else if (aspectRatio >= squareRatio) return 15
|
||||
else if (aspectRatio >= portraitRatio) return 18
|
||||
else return 25
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
imageAspectRatio: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rowSpan: 10,
|
||||
rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 8,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
calculateItemHeight() {
|
||||
this.$parent.$emit('calculating-item-height')
|
||||
|
||||
this.$nextTick(() => {
|
||||
const gridStyle = this.$parent.$el.style
|
||||
const rowHeight = parseInt(gridStyle.gridAutoRows)
|
||||
@ -27,13 +45,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
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)
|
||||
}
|
||||
this.calculateItemHeight()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -23,11 +23,11 @@
|
||||
@close="close"
|
||||
/>
|
||||
<confirm-modal
|
||||
v-if="open === 'delete'"
|
||||
v-if="open === 'confirm'"
|
||||
:id="data.resource.id"
|
||||
:type="data.type"
|
||||
:name="name"
|
||||
:modalData="data.modalsData.delete"
|
||||
:modalData="data.modalData"
|
||||
@close="close"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -77,7 +77,7 @@ export default {
|
||||
}, 500)
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
this.success = false
|
||||
this.isOpen = false
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@ -26,9 +26,7 @@ describe('DisableModal.vue', () => {
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
enable: 'u4711',
|
||||
})
|
||||
.mockResolvedValueOnce()
|
||||
.mockRejectedValue({
|
||||
message: 'Not Authorised!',
|
||||
}),
|
||||
@ -159,11 +157,13 @@ describe('DisableModal.vue', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('passes id to mutation', () => {
|
||||
it('passes parameters to mutation', () => {
|
||||
const calls = mocks.$apollo.mutate.mock.calls
|
||||
const [[{ variables }]] = calls
|
||||
expect(variables).toEqual({
|
||||
id: 'u4711',
|
||||
expect(variables).toMatchObject({
|
||||
resourceId: 'u4711',
|
||||
disable: true,
|
||||
closed: false,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -54,11 +54,13 @@ export default {
|
||||
// await this.modalData.buttons.confirm.callback()
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
disable(id: $id)
|
||||
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
|
||||
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
|
||||
disable
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: this.id },
|
||||
variables: { resourceId: this.id, disable: true, closed: false },
|
||||
})
|
||||
this.$toast.success(this.$t('disable.success'))
|
||||
this.isOpen = false
|
||||
@ -67,6 +69,7 @@ export default {
|
||||
}, 1000)
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
this.isOpen = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -149,6 +149,7 @@ export default {
|
||||
default:
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
this.isOpen = false
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</nuxt-link>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Username, Image & Date of Post -->
|
||||
<div>
|
||||
<div class="user-wrapper">
|
||||
<client-only>
|
||||
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
|
||||
</client-only>
|
||||
@ -141,10 +141,19 @@ export default {
|
||||
this.$emit('unpinPost', post)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const width = this.$el.offsetWidth
|
||||
const height = Math.min(width / this.post.imageAspectRatio, 2000)
|
||||
const imageElement = this.$el.querySelector('.ds-card-image')
|
||||
|
||||
if (imageElement) {
|
||||
imageElement.style.height = `${height}px`
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.ds-card-image img {
|
||||
width: 100%;
|
||||
max-height: 2000px;
|
||||
@ -159,9 +168,21 @@ export default {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
|
||||
/*.ds-card-footer {
|
||||
}*/
|
||||
> .ds-card-content {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
/* workaround to avoid jumping layout when footer is rendered */
|
||||
> .ds-card-footer {
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
/* workaround to avoid jumping layout when hc-user is rendered */
|
||||
.user-wrapper {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.content-menu {
|
||||
display: inline-block;
|
||||
|
||||
@ -27,7 +27,7 @@ describe('ReleaseModal.vue', () => {
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ enable: 'u4711' })
|
||||
.mockResolvedValueOnce()
|
||||
.mockRejectedValue({ message: 'Not Authorised!' }),
|
||||
},
|
||||
location: {
|
||||
@ -154,11 +154,13 @@ describe('ReleaseModal.vue', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('passes id to mutation', () => {
|
||||
it('passes parameters to mutation', () => {
|
||||
const calls = mocks.$apollo.mutate.mock.calls
|
||||
const [[{ variables }]] = calls
|
||||
expect(variables).toEqual({
|
||||
id: 'u4711',
|
||||
expect(variables).toMatchObject({
|
||||
resourceId: 'u4711',
|
||||
disable: false,
|
||||
closed: false,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -53,11 +53,13 @@ export default {
|
||||
// await this.modalData.buttons.confirm.callback()
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
enable(id: $id)
|
||||
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
|
||||
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
|
||||
disable
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: this.id },
|
||||
variables: { resourceId: this.id, disable: false, closed: false },
|
||||
})
|
||||
this.$toast.success(this.$t('release.success'))
|
||||
this.isOpen = false
|
||||
@ -66,6 +68,7 @@ export default {
|
||||
}, 1000)
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
this.isOpen = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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