From c67f92eb44ca00ec2ad920930684dc57e31e0ce0 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Tue, 25 Jul 2017 12:08:36 -0400 Subject: [PATCH 01/29] add toxic comments plugin to main api --- .gitignore | 3 +- plugins/talk-plugin-toxic-comments/.gitignore | 1 + plugins/talk-plugin-toxic-comments/index.js | 7 + .../package-lock.json | 144 +++++++++++++++++ .../talk-plugin-toxic-comments/package.json | 14 ++ .../server/perspective.js | 6 + .../server/router.js | 40 +++++ plugins/talk-plugin-toxic-comments/yarn.lock | 151 ++++++++++++++++++ 8 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-toxic-comments/.gitignore create mode 100644 plugins/talk-plugin-toxic-comments/index.js create mode 100644 plugins/talk-plugin-toxic-comments/package-lock.json create mode 100644 plugins/talk-plugin-toxic-comments/package.json create mode 100644 plugins/talk-plugin-toxic-comments/server/perspective.js create mode 100644 plugins/talk-plugin-toxic-comments/server/router.js create mode 100644 plugins/talk-plugin-toxic-comments/yarn.lock diff --git a/.gitignore b/.gitignore index bd8edcd94..aed295c13 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,6 @@ plugins/* !plugins/coral-plugin-comment-content !plugins/talk-plugin-permalink !plugins/talk-plugin-featured-comments - +!plugins/talk-plugin-toxic-comments **/node_modules/* +story.html diff --git a/plugins/talk-plugin-toxic-comments/.gitignore b/plugins/talk-plugin-toxic-comments/.gitignore new file mode 100644 index 000000000..b051c6c57 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/.gitignore @@ -0,0 +1 @@ +client diff --git a/plugins/talk-plugin-toxic-comments/index.js b/plugins/talk-plugin-toxic-comments/index.js new file mode 100644 index 000000000..9bceaf494 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/index.js @@ -0,0 +1,7 @@ +const perspective = require('./server/perspective'); +const router = require('./server/router'); + +module.exports = { + perspective, + router +}; diff --git a/plugins/talk-plugin-toxic-comments/package-lock.json b/plugins/talk-plugin-toxic-comments/package-lock.json new file mode 100644 index 000000000..690852f34 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/package-lock.json @@ -0,0 +1,144 @@ +{ + "name": "@coralproject/talk-plugin-toxicity", + "version": "0.0.1", + "lockfileVersion": 1, + "dependencies": { + "axios": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz", + "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=" + }, + "body-parser": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", + "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", + "dependencies": { + "debug": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" + } + } + }, + "boom": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-3.2.2.tgz", + "integrity": "sha1-DwzF0ErcUAO4x9cfQsynJx/vDng=" + }, + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "content-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" + }, + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "express-boom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-boom/-/express-boom-2.0.0.tgz", + "integrity": "sha1-1AC5QOlhqKou2OP3fFlfoeQbZLM=" + }, + "follow-redirects": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.4.tgz", + "integrity": "sha512-Suw6KewLV2hReSyEOeql+UUkBVyiBm3ok1VPrVFRZnQInWpdoZbbiG5i8aJVSjTr0yQ4Ava0Sh6/joCg1Brdqw==" + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "http-errors": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", + "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" + }, + "iconv-lite": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "raw-body": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", + "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + } + } +} diff --git a/plugins/talk-plugin-toxic-comments/package.json b/plugins/talk-plugin-toxic-comments/package.json new file mode 100644 index 000000000..b288f95c4 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/package.json @@ -0,0 +1,14 @@ +{ + "name": "@coralproject/talk-plugin-toxicity", + "pluginName": "talk-plugin-toxicity", + "version": "0.0.1", + "description": "Provides support for measuring the toxicity of user comments using the Perspectives API", + "main": "index.js", + "author": "The Coral Project Team ", + "license": "Apache-2.0", + "dependencies": { + "axios": "^0.16.2", + "body-parser": "^1.17.2", + "express-boom": "^2.0.0" + } +} diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js new file mode 100644 index 000000000..0dc73840c --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -0,0 +1,6 @@ +module.exports = (perspective) => { + console.log("hello world from perpsctive"); + if(!process.env.PERSPECTIVE_API_KEY) { + throw new Error('Please set the PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); + } +} diff --git a/plugins/talk-plugin-toxic-comments/server/router.js b/plugins/talk-plugin-toxic-comments/server/router.js new file mode 100644 index 000000000..16a5e5e0b --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/router.js @@ -0,0 +1,40 @@ +const http = require('axios'); +const boom = require('express-boom'); +const bodyParser = require('body-parser'); +var count = 0; + +module.exports = (router) => { + router.use(boom()); + router.use(bodyParser.text()); + router.post('/api/v1/toxicity/comments', (req, res) => { + console.log(req.body); + if(req.body) { + var body = { + comment: { + text: req.body, + languages: ["en"], + requestedAttributes: { + TOXICITY: {} + } + } + }; + var headers = { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data) + }; + http.post('https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+process.env.PERSPECTIVE_API_KEY, { + headers: headers, + body: body + }) + .then(function(response) { + return res.json(response); + }) + .catch(function(err) { + return res.json(err); + }) + } + else { + res.boom.notFound(); + } + }); +}; diff --git a/plugins/talk-plugin-toxic-comments/yarn.lock b/plugins/talk-plugin-toxic-comments/yarn.lock new file mode 100644 index 000000000..8097789e1 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/yarn.lock @@ -0,0 +1,151 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +axios@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" + dependencies: + follow-redirects "^1.2.3" + is-buffer "^1.1.5" + +body-parser@^1.17.2: + version "1.17.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" + dependencies: + bytes "2.4.0" + content-type "~1.0.2" + debug "2.6.7" + depd "~1.1.0" + http-errors "~1.6.1" + iconv-lite "0.4.15" + on-finished "~2.3.0" + qs "6.4.0" + raw-body "~2.2.0" + type-is "~1.6.15" + +boom@3.2.x: + version "3.2.2" + resolved "https://registry.yarnpkg.com/boom/-/boom-3.2.2.tgz#0f0cc5d04adc5003b8c7d71f42cca7271fef0e78" + dependencies: + hoek "4.x.x" + +bytes@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" + +content-type@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" + +debug@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" + dependencies: + ms "2.0.0" + +debug@^2.4.5: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +depd@1.1.0, depd@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +express-boom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/express-boom/-/express-boom-2.0.0.tgz#d400b940e961a8aa2ed8e3f77c595fa1e41b64b3" + dependencies: + boom "3.2.x" + +follow-redirects@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" + dependencies: + debug "^2.4.5" + +hoek@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" + dependencies: + depd "1.1.0" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +iconv-lite@0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@~2.1.15: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +qs@6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +raw-body@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" + dependencies: + bytes "2.4.0" + iconv-lite "0.4.15" + unpipe "1.0.0" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +"statuses@>= 1.3.1 < 2": + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From b62ab9bff09712b590184b68f2e0e832a4ea5b88 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Mon, 31 Jul 2017 08:21:52 -0400 Subject: [PATCH 02/29] pass comment to presubmit hooks --- client/talk-plugin-commentbox/CommentBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index 084e67fc4..d0fa7d72f 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -62,7 +62,7 @@ class CommentBox extends React.Component { }; // Execute preSubmit Hooks - this.state.hooks.preSubmit.forEach((hook) => hook()); + this.state.hooks.preSubmit.forEach((hook) => hook(comment)); this.setState({loadingState: 'loading'}); postComment(comment, 'comments') From 4b13ccc5c1b394f20dcdb6a8865bfc6fddc4f264 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Mon, 31 Jul 2017 08:25:39 -0400 Subject: [PATCH 03/29] enforce API key --- .../server/perspective.js | 6 ----- .../server/router.js | 24 ++++++++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 plugins/talk-plugin-toxic-comments/server/perspective.js diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js deleted file mode 100644 index 0dc73840c..000000000 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = (perspective) => { - console.log("hello world from perpsctive"); - if(!process.env.PERSPECTIVE_API_KEY) { - throw new Error('Please set the PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); - } -} diff --git a/plugins/talk-plugin-toxic-comments/server/router.js b/plugins/talk-plugin-toxic-comments/server/router.js index 16a5e5e0b..2779191c3 100644 --- a/plugins/talk-plugin-toxic-comments/server/router.js +++ b/plugins/talk-plugin-toxic-comments/server/router.js @@ -4,14 +4,22 @@ const bodyParser = require('body-parser'); var count = 0; module.exports = (router) => { + + const key = process.env.TALK_PERSPECTIVE_API_KEY; + if(!key) { + throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); + } + + router.use(boom()); router.use(bodyParser.text()); - router.post('/api/v1/toxicity/comments', (req, res) => { - console.log(req.body); - if(req.body) { + + router.get('/api/v1/toxicity', (req, res) => { + var comment = req.query.comment; + if(comment) { var body = { comment: { - text: req.body, + text: comment, languages: ["en"], requestedAttributes: { TOXICITY: {} @@ -20,16 +28,13 @@ module.exports = (router) => { }; var headers = { 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) }; - http.post('https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+process.env.PERSPECTIVE_API_KEY, { - headers: headers, - body: body - }) + http.post('https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+key, body) .then(function(response) { return res.json(response); }) .catch(function(err) { + console.log(err); return res.json(err); }) } @@ -37,4 +42,5 @@ module.exports = (router) => { res.boom.notFound(); } }); + }; From 8c4612a4dda17c313d7d3a764b9efcb322d91048 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Mon, 31 Jul 2017 08:30:38 -0400 Subject: [PATCH 04/29] remove unused import --- plugins/talk-plugin-toxic-comments/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/talk-plugin-toxic-comments/index.js b/plugins/talk-plugin-toxic-comments/index.js index 9bceaf494..10651ec9d 100644 --- a/plugins/talk-plugin-toxic-comments/index.js +++ b/plugins/talk-plugin-toxic-comments/index.js @@ -1,7 +1,5 @@ -const perspective = require('./server/perspective'); const router = require('./server/router'); module.exports = { - perspective, router }; From 2c1cd508288dfdc03f1392b203c9e581549b1192 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Mon, 31 Jul 2017 09:46:50 -0400 Subject: [PATCH 05/29] turn router method from get to post --- .../server/router.js | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/plugins/talk-plugin-toxic-comments/server/router.js b/plugins/talk-plugin-toxic-comments/server/router.js index 2779191c3..4f3011efc 100644 --- a/plugins/talk-plugin-toxic-comments/server/router.js +++ b/plugins/talk-plugin-toxic-comments/server/router.js @@ -1,7 +1,6 @@ const http = require('axios'); const boom = require('express-boom'); const bodyParser = require('body-parser'); -var count = 0; module.exports = (router) => { @@ -14,32 +13,44 @@ module.exports = (router) => { router.use(boom()); router.use(bodyParser.text()); - router.get('/api/v1/toxicity', (req, res) => { - var comment = req.query.comment; + /** + * POST /api/v1/toxicity/score + * args: + * - provide the comment in the request body + */ + router.post('/api/v1/toxicity/score', (req, res) => { + var comment = req.body; if(comment) { var body = { comment: { text: comment, - languages: ["en"], - requestedAttributes: { - TOXICITY: {} - } + }, + languages: ["en"], + requestedAttributes: { + TOXICITY: {} } }; var headers = { 'Content-Type': 'application/json', }; - http.post('https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+key, body) + http.post( + 'https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+key, + body) .then(function(response) { - return res.json(response); + var data = response.data; + var score = { + comment: comment, + score: data.attributeScores.TOXICITY.summaryScore.value + } + return res.json(score); }) .catch(function(err) { console.log(err); - return res.json(err); + res.boom.badRequest('The Perspective API returned an error. Please check the server logs for details.'); }) } else { - res.boom.notFound(); + res.boom.badRequest('No comment provided'); } }); From 88f06744b41595c7cf5a77e4eb1a1b9416c23a43 Mon Sep 17 00:00:00 2001 From: Jeff Nelson Date: Mon, 31 Jul 2017 11:57:04 -0400 Subject: [PATCH 06/29] unignore --- plugins/talk-plugin-toxic-comments/.gitignore | 1 - .../client/.babelrc | 14 +++++ .../client/.eslintrc.json | 23 +++++++ .../client/actions.js | 5 ++ .../client/components/ToxicCommentAlert.js | 62 +++++++++++++++++++ .../client/components/styles.css | 5 ++ .../client/constants.js | 1 + .../client/index.js | 14 +++++ .../client/reducer.js | 18 ++++++ .../client/translations.yml | 8 +++ 10 files changed, 150 insertions(+), 1 deletion(-) delete mode 100644 plugins/talk-plugin-toxic-comments/.gitignore create mode 100644 plugins/talk-plugin-toxic-comments/client/.babelrc create mode 100644 plugins/talk-plugin-toxic-comments/client/.eslintrc.json create mode 100644 plugins/talk-plugin-toxic-comments/client/actions.js create mode 100644 plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js create mode 100644 plugins/talk-plugin-toxic-comments/client/components/styles.css create mode 100644 plugins/talk-plugin-toxic-comments/client/constants.js create mode 100644 plugins/talk-plugin-toxic-comments/client/index.js create mode 100644 plugins/talk-plugin-toxic-comments/client/reducer.js create mode 100644 plugins/talk-plugin-toxic-comments/client/translations.yml diff --git a/plugins/talk-plugin-toxic-comments/.gitignore b/plugins/talk-plugin-toxic-comments/.gitignore deleted file mode 100644 index b051c6c57..000000000 --- a/plugins/talk-plugin-toxic-comments/.gitignore +++ /dev/null @@ -1 +0,0 @@ -client diff --git a/plugins/talk-plugin-toxic-comments/client/.babelrc b/plugins/talk-plugin-toxic-comments/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/talk-plugin-toxic-comments/client/.eslintrc.json b/plugins/talk-plugin-toxic-comments/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/talk-plugin-toxic-comments/client/actions.js b/plugins/talk-plugin-toxic-comments/client/actions.js new file mode 100644 index 000000000..9cb3947f8 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/actions.js @@ -0,0 +1,5 @@ +import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants'; + +export const toggleCheckbox = () => ({ + type: OFFTOPIC_TOGGLE_CHECKBOX +}); diff --git a/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js b/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js new file mode 100644 index 000000000..51ad5ef6d --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js @@ -0,0 +1,62 @@ +import React from 'react'; +import styles from './styles.css'; +import {t} from 'plugin-api/beta/client/services'; +import {isTagged} from 'plugin-api/beta/client/utils'; + +export default class ToxicCommentAlert extends React.Component { + + constructor(props) { + super(props); + this.state = { + toxic: false + }; + } + + componentDidMount() { + this.toxicityHook = this.props.registerHook('preSubmit', (data) => { + const comment = data.body; + (async() => { + var toxicity = await fetch('/api/v1/toxicity/score', { + method: 'POST', + body: comment + }) + .then(response => response.json()) + .then(function(json) { + return json.score; + }) + .catch(function(err) { + console.log(err); + return 0; + }); + console.log(toxicity); + if(toxicity > 0.3){ + this.setState({ + toxic: true + }); + } + else { + this.setState({ + toxic: false + }); + } + })(); + }); + } + + componentWillUnmount() { + this.props.unregisterHook(this.toxicityHook); + } + + render() { + return( +
+ { + this.state.toxic ? ( + Are you sure you want to post this? Other members of the community my view your comment as toxic, so please take a moment to reconsider. + ) : null + } +
+ ); + } + +} diff --git a/plugins/talk-plugin-toxic-comments/client/components/styles.css b/plugins/talk-plugin-toxic-comments/client/components/styles.css new file mode 100644 index 000000000..d898dd111 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/components/styles.css @@ -0,0 +1,5 @@ +.toxicComment { + color: red; + font-weight: bold; + padding: 12px 15px 10px 15px +} diff --git a/plugins/talk-plugin-toxic-comments/client/constants.js b/plugins/talk-plugin-toxic-comments/client/constants.js new file mode 100644 index 000000000..122d7de17 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/constants.js @@ -0,0 +1 @@ +export const OFFTOPIC_TOGGLE_CHECKBOX = 'OFFTOPIC_TOGGLE_CHECKBOX'; diff --git a/plugins/talk-plugin-toxic-comments/client/index.js b/plugins/talk-plugin-toxic-comments/client/index.js new file mode 100644 index 000000000..dc9aa5348 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/index.js @@ -0,0 +1,14 @@ +import translations from './translations.yml'; +import ToxicCommentAlert from './components/ToxicCommentAlert'; + +/** + * coral-plugin-offtopic depends on coral-plugin-viewing-options + * in other to display filter and use the streamViewingOptions slot + */ + +export default { + translations, + slots: { + commentInputDetailArea: [ToxicCommentAlert], + } +}; diff --git a/plugins/talk-plugin-toxic-comments/client/reducer.js b/plugins/talk-plugin-toxic-comments/client/reducer.js new file mode 100644 index 000000000..adab987a0 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/reducer.js @@ -0,0 +1,18 @@ +import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants'; + +const initialState = { + checked: false +}; + +export default function offTopic (state = initialState, action) { + switch (action.type) { + case OFFTOPIC_TOGGLE_CHECKBOX: { + return { + ...state, + checked: !state.checked + }; + } + default : + return state; + } +} diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml new file mode 100644 index 000000000..7f6db9370 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -0,0 +1,8 @@ +en: + talk-plugin-featured-comments: + featured: Featured + go_to_conversation: Go to conversation +es: + talk-plugin-featured-comments: + featured: Remarcado + go_to_conversation: Ir al comentario From deb69d4e8c1139455bc14f307af82cc830b40b8a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 28 Aug 2017 12:03:20 -0600 Subject: [PATCH 07/29] fixed prop-types deprecation --- client/coral-admin/src/AppRouter.js | 2 +- client/coral-admin/src/components/ActionButton.js | 4 +++- client/coral-admin/src/components/ActionsMenu.js | 3 ++- client/coral-admin/src/components/AdminLogin.js | 3 ++- client/coral-admin/src/components/BanUserDialog.js | 3 ++- client/coral-admin/src/components/CommentType.js | 3 ++- client/coral-admin/src/components/EmptyCard.js | 3 ++- client/coral-admin/src/components/FlagBox.js | 3 ++- client/coral-admin/src/components/LoadMore.js | 3 ++- client/coral-admin/src/components/ModerationKeysModal.js | 3 ++- client/coral-admin/src/components/ModerationList.js | 3 ++- client/coral-admin/src/components/SuspendUserDialog.js | 3 ++- client/coral-admin/src/components/UserDetailComment.js | 3 ++- client/coral-admin/src/components/ui/Drawer.js | 3 ++- client/coral-admin/src/components/ui/Header.js | 7 ++++--- client/coral-admin/src/components/ui/Layout.js | 3 ++- .../routes/Community/components/RejectUsernameDialog.js | 3 ++- .../src/routes/Configure/components/ModerationSettings.js | 3 ++- .../src/routes/Configure/components/TechSettings.js | 3 ++- .../src/routes/Dashboard/components/ActivityWidget.js | 3 ++- .../src/routes/Dashboard/components/CountdownTimer.js | 3 ++- .../src/routes/Dashboard/components/FlagWidget.js | 3 ++- .../src/routes/Dashboard/components/LikeWidget.js | 3 ++- .../src/routes/Moderation/components/Comment.js | 3 ++- .../src/routes/Moderation/components/CommentCount.js | 3 ++- .../src/routes/Moderation/components/ModerationHeader.js | 3 ++- .../src/routes/Moderation/components/ModerationMenu.js | 3 ++- .../src/routes/Moderation/components/ModerationQueue.js | 3 ++- .../coral-admin/src/routes/Moderation/components/Story.js | 3 ++- client/coral-configure/components/Markdown.js | 3 ++- .../coral-embed-stream/src/components/CountdownSeconds.js | 3 ++- .../src/components/EditableCommentContent.js | 3 ++- .../coral-embed-stream/src/components/IgnoreUserWizard.js | 3 ++- .../src/components/InactiveCommentLabel.js | 3 ++- client/coral-embed-stream/src/components/NewCount.js | 3 ++- .../coral-embed-stream/src/components/SuspendedAccount.js | 3 ++- client/coral-embed-stream/src/components/TopRightMenu.js | 3 ++- client/coral-framework/components/Markdown.js | 3 ++- client/coral-framework/components/MarkdownEditor.js | 3 ++- client/coral-settings/components/IgnoredUsers.js | 3 ++- client/coral-ui/components/CoralLogo.js | 3 ++- client/coral-ui/components/Dialog.js | 5 +++-- client/coral-ui/components/Drawer.js | 3 ++- client/coral-ui/components/Icon.js | 3 ++- client/coral-ui/components/Pager.js | 3 ++- client/coral-ui/components/TextArea.js | 3 ++- client/coral-ui/components/TextField.js | 3 ++- client/coral-ui/components/Wizard.js | 3 ++- client/coral-ui/components/WizardNav.js | 3 ++- client/talk-plugin-commentbox/CommentBox.js | 3 ++- client/talk-plugin-commentbox/CommentForm.js | 3 ++- client/talk-plugin-history/Comment.js | 3 ++- client/talk-plugin-history/CommentHistory.js | 3 ++- client/talk-plugin-moderation/ModerationLink.js | 3 ++- client/talk-plugin-replies/ReplyBox.js | 3 ++- client/talk-plugin-replies/ReplyButton.js | 3 ++- .../talk-plugin-auth/client/components/SignInContent.js | 3 ++- 57 files changed, 117 insertions(+), 60 deletions(-) diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index dcd660c25..d50aeb3f6 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,6 +1,6 @@ import React from 'react'; -import {Router, Route, IndexRedirect, IndexRoute} from 'react-router'; import PropTypes from 'prop-types'; +import {Router, Route, IndexRedirect, IndexRoute} from 'react-router'; import Configure from 'routes/Configure'; import Dashboard from 'routes/Dashboard'; diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 3dbac206b..b8778bef0 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,4 +1,6 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; + import styles from './ModerationList.css'; import {Button} from 'coral-ui'; import {menuActionsMap} from '../utils/moderationQueueActionsMap'; diff --git a/client/coral-admin/src/components/ActionsMenu.js b/client/coral-admin/src/components/ActionsMenu.js index ff4a9c5b3..6e236bf1c 100644 --- a/client/coral-admin/src/components/ActionsMenu.js +++ b/client/coral-admin/src/components/ActionsMenu.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Button, Icon} from 'coral-ui'; import {Menu} from 'react-mdl'; import cn from 'classnames'; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index e75e66d23..ce832be8f 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Layout from 'coral-admin/src/components/ui/Layout'; import styles from './NotFound.css'; import {Button, TextField, Alert, Success} from 'coral-ui'; diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/components/BanUserDialog.js index bade3578d..169afe043 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/components/BanUserDialog.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Dialog} from 'coral-ui'; import styles from './BanUserDialog.css'; diff --git a/client/coral-admin/src/components/CommentType.js b/client/coral-admin/src/components/CommentType.js index 075ec5a78..4d1ff7e67 100644 --- a/client/coral-admin/src/components/CommentType.js +++ b/client/coral-admin/src/components/CommentType.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './CommentType.css'; import {Icon} from 'coral-ui'; import cn from 'classnames'; diff --git a/client/coral-admin/src/components/EmptyCard.js b/client/coral-admin/src/components/EmptyCard.js index ae291bcad..8adc63747 100644 --- a/client/coral-admin/src/components/EmptyCard.js +++ b/client/coral-admin/src/components/EmptyCard.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Card} from 'coral-ui'; const EmptyCard = (props) => ( diff --git a/client/coral-admin/src/components/FlagBox.js b/client/coral-admin/src/components/FlagBox.js index 240d52078..05fd14128 100644 --- a/client/coral-admin/src/components/FlagBox.js +++ b/client/coral-admin/src/components/FlagBox.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import {Icon} from 'coral-ui'; import styles from './FlagBox.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/components/LoadMore.js b/client/coral-admin/src/components/LoadMore.js index 969c6734f..eaacb4413 100644 --- a/client/coral-admin/src/components/LoadMore.js +++ b/client/coral-admin/src/components/LoadMore.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Button} from 'coral-ui'; import styles from './LoadMore.css'; import cn from 'classnames'; diff --git a/client/coral-admin/src/components/ModerationKeysModal.js b/client/coral-admin/src/components/ModerationKeysModal.js index c4f3f1e1d..abdffa2b1 100644 --- a/client/coral-admin/src/components/ModerationKeysModal.js +++ b/client/coral-admin/src/components/ModerationKeysModal.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Modal from 'components/Modal'; import styles from './ModerationKeysModal.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js index e2b536f2c..adfe63051 100644 --- a/client/coral-admin/src/components/ModerationList.js +++ b/client/coral-admin/src/components/ModerationList.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './ModerationList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; diff --git a/client/coral-admin/src/components/SuspendUserDialog.js b/client/coral-admin/src/components/SuspendUserDialog.js index d2ef23891..bb0cff84b 100644 --- a/client/coral-admin/src/components/SuspendUserDialog.js +++ b/client/coral-admin/src/components/SuspendUserDialog.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Dialog} from 'coral-ui'; import {RadioGroup, Radio} from 'react-mdl'; import styles from './SuspendUserDialog.css'; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index dc809b9c7..c8daa8019 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Link} from 'react-router'; import {Icon} from 'coral-ui'; diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index b49caf30a..2b728a33a 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Navigation, Drawer} from 'react-mdl'; import {IndexLink, Link} from 'react-router'; import styles from './Drawer.css'; diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index ca1cc1f30..0b4ff0e5d 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl'; import {Link, IndexLink} from 'react-router'; import styles from './Header.css'; @@ -87,8 +88,8 @@ const CoralHeader = ({ -
  • - {`v${process.env.VERSION}`} +
  • + {`v${process.env.VERSION}`}
  • diff --git a/client/coral-admin/src/components/ui/Layout.js b/client/coral-admin/src/components/ui/Layout.js index 10433048e..3aa056d9e 100644 --- a/client/coral-admin/src/components/ui/Layout.js +++ b/client/coral-admin/src/components/ui/Layout.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Layout as LayoutMDL} from 'react-mdl'; import Header from './Header'; import Drawer from './Drawer'; diff --git a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js index 3ff764b2b..abc9b59af 100644 --- a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js +++ b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import {Dialog, Button} from 'coral-ui'; import styles from './RejectUsernameDialog.css'; diff --git a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js index 99f3435e2..92a5413e2 100644 --- a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js +++ b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Configure.css'; import {Card} from 'coral-ui'; import {Checkbox} from 'react-mdl'; diff --git a/client/coral-admin/src/routes/Configure/components/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js index 2dca7b207..4f1202a67 100644 --- a/client/coral-admin/src/routes/Configure/components/TechSettings.js +++ b/client/coral-admin/src/routes/Configure/components/TechSettings.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Card} from 'coral-ui'; import Domainlist from './Domainlist'; import EmbedLink from './EmbedLink'; diff --git a/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js index d92b2bd14..393b670a7 100644 --- a/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js +++ b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Link} from 'react-router'; import styles from './Widget.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js index 6864acb6b..a2e6ea9f1 100644 --- a/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js +++ b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Dashboard.css'; import {Icon} from 'coral-ui'; diff --git a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js index ef4a6857e..1cf011cef 100644 --- a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js +++ b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Link} from 'react-router'; import styles from './Widget.css'; diff --git a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js index a61f888bf..3c61cd1a3 100644 --- a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js +++ b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Link} from 'react-router'; import styles from './Widget.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index c73d15eaa..f4a378e1d 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Link} from 'react-router'; import {Icon} from 'coral-ui'; diff --git a/client/coral-admin/src/routes/Moderation/components/CommentCount.js b/client/coral-admin/src/routes/Moderation/components/CommentCount.js index 75af89584..53d611ed6 100644 --- a/client/coral-admin/src/routes/Moderation/components/CommentCount.js +++ b/client/coral-admin/src/routes/Moderation/components/CommentCount.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './CommentCount.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js b/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js index 9cc0bfed9..b9bd0caff 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Icon} from 'coral-ui'; import styles from './styles.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js index a28e90c65..25b90cf64 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import CommentCount from './CommentCount'; import styles from './styles.css'; import {SelectField, Option} from 'react-mdl-selectfield'; diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index 7f96c7c90..3da727f99 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Comment from '../containers/Comment'; import styles from './styles.css'; diff --git a/client/coral-admin/src/routes/Moderation/components/Story.js b/client/coral-admin/src/routes/Moderation/components/Story.js index 6c3a4b02f..eaad6c2a6 100644 --- a/client/coral-admin/src/routes/Moderation/components/Story.js +++ b/client/coral-admin/src/routes/Moderation/components/Story.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './StorySearch.css'; const formatDate = (date) => { diff --git a/client/coral-configure/components/Markdown.js b/client/coral-configure/components/Markdown.js index 482467987..d9d1bc4a7 100644 --- a/client/coral-configure/components/Markdown.js +++ b/client/coral-configure/components/Markdown.js @@ -1,4 +1,5 @@ -import React, {PureComponent, PropTypes} from 'react'; +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; import marked from 'marked'; const renderer = new marked.Renderer(); diff --git a/client/coral-embed-stream/src/components/CountdownSeconds.js b/client/coral-embed-stream/src/components/CountdownSeconds.js index d350c803b..789b674b5 100644 --- a/client/coral-embed-stream/src/components/CountdownSeconds.js +++ b/client/coral-embed-stream/src/components/CountdownSeconds.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; /** diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js index eeab7f164..b10624cdf 100644 --- a/client/coral-embed-stream/src/components/EditableCommentContent.js +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {notifyForNewCommentStatus} from 'talk-plugin-commentbox/CommentBox'; import {CommentForm} from 'talk-plugin-commentbox/CommentForm'; import styles from './Comment.css'; diff --git a/client/coral-embed-stream/src/components/IgnoreUserWizard.js b/client/coral-embed-stream/src/components/IgnoreUserWizard.js index b7612b9f8..68b92ae29 100644 --- a/client/coral-embed-stream/src/components/IgnoreUserWizard.js +++ b/client/coral-embed-stream/src/components/IgnoreUserWizard.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Comment.css'; import {Button} from 'coral-ui'; diff --git a/client/coral-embed-stream/src/components/InactiveCommentLabel.js b/client/coral-embed-stream/src/components/InactiveCommentLabel.js index 824692558..6655c4881 100644 --- a/client/coral-embed-stream/src/components/InactiveCommentLabel.js +++ b/client/coral-embed-stream/src/components/InactiveCommentLabel.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; import styles from './InactiveCommentLabel.css'; import {Icon} from 'coral-ui'; diff --git a/client/coral-embed-stream/src/components/NewCount.js b/client/coral-embed-stream/src/components/NewCount.js index 328c76481..1413a600e 100644 --- a/client/coral-embed-stream/src/components/NewCount.js +++ b/client/coral-embed-stream/src/components/NewCount.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Button} from 'coral-ui'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-embed-stream/src/components/SuspendedAccount.js b/client/coral-embed-stream/src/components/SuspendedAccount.js index 1a41bac91..7c0b5933c 100644 --- a/client/coral-embed-stream/src/components/SuspendedAccount.js +++ b/client/coral-embed-stream/src/components/SuspendedAccount.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; import styles from './SuspendAccount.css'; import {Button} from 'coral-ui'; diff --git a/client/coral-embed-stream/src/components/TopRightMenu.js b/client/coral-embed-stream/src/components/TopRightMenu.js index 7ae123b90..b0ea55c08 100644 --- a/client/coral-embed-stream/src/components/TopRightMenu.js +++ b/client/coral-embed-stream/src/components/TopRightMenu.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {IgnoreUserWizard} from './IgnoreUserWizard'; import Toggleable from './Toggleable'; diff --git a/client/coral-framework/components/Markdown.js b/client/coral-framework/components/Markdown.js index 482467987..d9d1bc4a7 100644 --- a/client/coral-framework/components/Markdown.js +++ b/client/coral-framework/components/Markdown.js @@ -1,4 +1,5 @@ -import React, {PureComponent, PropTypes} from 'react'; +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; import marked from 'marked'; const renderer = new marked.Renderer(); diff --git a/client/coral-framework/components/MarkdownEditor.js b/client/coral-framework/components/MarkdownEditor.js index 0d2088e51..65f603b0c 100644 --- a/client/coral-framework/components/MarkdownEditor.js +++ b/client/coral-framework/components/MarkdownEditor.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import SimpleMDE from 'simplemde'; import cn from 'classnames'; import noop from 'lodash/noop'; diff --git a/client/coral-settings/components/IgnoredUsers.js b/client/coral-settings/components/IgnoredUsers.js index 32e485e4c..9cb314373 100644 --- a/client/coral-settings/components/IgnoredUsers.js +++ b/client/coral-settings/components/IgnoredUsers.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; import styles from './IgnoredUsers.css'; diff --git a/client/coral-ui/components/CoralLogo.js b/client/coral-ui/components/CoralLogo.js index 002b0cabc..fe4706429 100644 --- a/client/coral-ui/components/CoralLogo.js +++ b/client/coral-ui/components/CoralLogo.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './CoralLogo.css'; const CoralLogo = ({className = ''}) => ( diff --git a/client/coral-ui/components/Dialog.js b/client/coral-ui/components/Dialog.js index 85ab570d3..98bf92074 100644 --- a/client/coral-ui/components/Dialog.js +++ b/client/coral-ui/components/Dialog.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import dialogPolyfill from 'dialog-polyfill'; import 'dialog-polyfill/dialog-polyfill.css'; @@ -43,7 +44,7 @@ export default class Dialog extends Component { componentWillUnmount() { const dialog = this.dialog; if (dialog) { - dialog.removeEventListener('cancel', this.props.onCancel); + dialog.removeEventListener('cancel', this.props.onCancel); } } diff --git a/client/coral-ui/components/Drawer.js b/client/coral-ui/components/Drawer.js index 3c4f051bd..d50237bfb 100644 --- a/client/coral-ui/components/Drawer.js +++ b/client/coral-ui/components/Drawer.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Drawer.css'; const Drawer = ({children, onClose}) => { diff --git a/client/coral-ui/components/Icon.js b/client/coral-ui/components/Icon.js index 025b6df9e..e0dfea3a7 100644 --- a/client/coral-ui/components/Icon.js +++ b/client/coral-ui/components/Icon.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Icon as IconMDL} from 'react-mdl'; import cn from 'classnames'; import styles from './Icon.css'; diff --git a/client/coral-ui/components/Pager.js b/client/coral-ui/components/Pager.js index ffd1b9d0a..2383d9171 100644 --- a/client/coral-ui/components/Pager.js +++ b/client/coral-ui/components/Pager.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './Pager.css'; const Rows = (curr, total, onClickHandler) => Array.from(Array(total)).map((e, i) => diff --git a/client/coral-ui/components/TextArea.js b/client/coral-ui/components/TextArea.js index fedc7914b..547c0fc2e 100644 --- a/client/coral-ui/components/TextArea.js +++ b/client/coral-ui/components/TextArea.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './TextArea.css'; const TextArea = ({className, value = '', ...props}) => ( diff --git a/client/coral-ui/components/TextField.js b/client/coral-ui/components/TextField.js index ab5fc08ba..80c8173e7 100644 --- a/client/coral-ui/components/TextField.js +++ b/client/coral-ui/components/TextField.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './TextField.css'; const TextField = ({className, showErrors = false, errorMsg, label, ...props}) => ( diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js index eefa2a0b1..d75f30927 100644 --- a/client/coral-ui/components/Wizard.js +++ b/client/coral-ui/components/Wizard.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; const Wizard = (props) => { const {children, currentStep, ...rest} = props; diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js index cbcb664be..c465c1da4 100644 --- a/client/coral-ui/components/WizardNav.js +++ b/client/coral-ui/components/WizardNav.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './WizardNav.css'; import Icon from './Icon'; diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index d4cc44f22..ea6b13fb1 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; diff --git a/client/talk-plugin-commentbox/CommentForm.js b/client/talk-plugin-commentbox/CommentForm.js index fe0f9f6d9..f4bbbd139 100644 --- a/client/talk-plugin-commentbox/CommentForm.js +++ b/client/talk-plugin-commentbox/CommentForm.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Button} from 'coral-ui'; import cn from 'classnames'; import Slot from 'coral-framework/components/Slot'; diff --git a/client/talk-plugin-history/Comment.js b/client/talk-plugin-history/Comment.js index 4cf9d875c..f79bb5e0f 100644 --- a/client/talk-plugin-history/Comment.js +++ b/client/talk-plugin-history/Comment.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Icon} from '../coral-ui'; import styles from './Comment.css'; import Slot from 'coral-framework/components/Slot'; diff --git a/client/talk-plugin-history/CommentHistory.js b/client/talk-plugin-history/CommentHistory.js index 91883cdc8..2e6f627e7 100644 --- a/client/talk-plugin-history/CommentHistory.js +++ b/client/talk-plugin-history/CommentHistory.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Comment from './Comment'; import styles from './CommentHistory.css'; import LoadMore from './LoadMore'; diff --git a/client/talk-plugin-moderation/ModerationLink.js b/client/talk-plugin-moderation/ModerationLink.js index 1a10160ee..f01f6d7f4 100644 --- a/client/talk-plugin-moderation/ModerationLink.js +++ b/client/talk-plugin-moderation/ModerationLink.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import styles from './styles.css'; import t from 'coral-framework/services/i18n'; diff --git a/client/talk-plugin-replies/ReplyBox.js b/client/talk-plugin-replies/ReplyBox.js index 50bd9a538..606aad000 100644 --- a/client/talk-plugin-replies/ReplyBox.js +++ b/client/talk-plugin-replies/ReplyBox.js @@ -1,4 +1,5 @@ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import CommentBox from '../talk-plugin-commentbox/CommentBox'; const name = 'talk-plugin-replies'; diff --git a/client/talk-plugin-replies/ReplyButton.js b/client/talk-plugin-replies/ReplyButton.js index 8ea113c97..13ff4de75 100644 --- a/client/talk-plugin-replies/ReplyButton.js +++ b/client/talk-plugin-replies/ReplyButton.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; diff --git a/plugins/talk-plugin-auth/client/components/SignInContent.js b/plugins/talk-plugin-auth/client/components/SignInContent.js index c92000232..c261f5fca 100644 --- a/plugins/talk-plugin-auth/client/components/SignInContent.js +++ b/plugins/talk-plugin-auth/client/components/SignInContent.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {Button, TextField, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui'; import styles from './styles.css'; import t from 'coral-framework/services/i18n'; From 17ae62c66bd189677e3ba59984920921ff2cb1b6 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 5 Sep 2017 17:28:23 +0100 Subject: [PATCH 08/29] Bump version to 3.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c45239e3..3d054e0d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "3.4.0", + "version": "3.5.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "scripts": { From 48875294c8852c6e75e2462cc7649dc399d24511 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 5 Sep 2017 13:57:35 -0600 Subject: [PATCH 09/29] initial support for changing the uri --- client/coral-framework/constants/config.js | 3 ++ client/coral-framework/services/bootstrap.js | 3 +- config.js | 3 ++ docs/_data/navigation.yml | 2 + docs/_docs/00-01-faq.md | 2 +- docs/_docs/02-01-configuration.md | 10 +++++ docs/_docs/02-05-persistence.md | 45 ++++++++++++++++++++ routes/admin/index.js | 6 ++- routes/embed/index.js | 27 ++++++------ 9 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 client/coral-framework/constants/config.js create mode 100644 docs/_docs/02-05-persistence.md diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js new file mode 100644 index 000000000..81eab50e2 --- /dev/null +++ b/client/coral-framework/constants/config.js @@ -0,0 +1,3 @@ +const configElement = document.querySelector('#data'); + +export const CONFIG = JSON.parse(configElement ? configElement.textContent : '{}'); diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index ac94d8b29..eed8313d4 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -8,6 +8,7 @@ import thunk from 'redux-thunk'; import {loadTranslations} from './i18n'; import bowser from 'bowser'; import {BASE_PATH} from 'coral-framework/constants/url'; +import {CONFIG} from 'coral-framework/constants/config'; import {createPluginsService} from './plugins'; import {createNotificationService} from './notification'; import {createGraphQLRegistry} from './graphqlRegistry'; @@ -69,7 +70,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi }); const client = createClient({ uri: `${BASE_PATH}api/v1/graph/ql`, - liveUri: `${protocol}://${location.host}${BASE_PATH}api/v1/live`, + liveUri: CONFIG.LIVE_URI || `${protocol}://${location.host}${BASE_PATH}api/v1/live`, token, }); const plugins = createPluginsService(pluginsConfig); diff --git a/config.js b/config.js index f48159444..d7e247ec3 100644 --- a/config.js +++ b/config.js @@ -135,6 +135,9 @@ const CONFIG = { RECAPTCHA_PUBLIC: process.env.TALK_RECAPTCHA_PUBLIC, RECAPTCHA_SECRET: process.env.TALK_RECAPTCHA_SECRET, + // WEBSOCKET_LIVE_URI is the absolute url to the live endpoint. + WEBSOCKET_LIVE_URI: process.env.TALK_WEBSOCKET_LIVE_URI || null, + //------------------------------------------------------------------------------ // SMTP Server configuration //------------------------------------------------------------------------------ diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 7226f6003..9fe39d103 100755 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -38,6 +38,8 @@ docs: url: /docs/running/plugins/ - title: "Database Migrations" url: /docs/running/migrations/ + - title: "Persistence" + url: /docs/running/persistence/ - title: "Architecture" url: /docs/architecture/ children: diff --git a/docs/_docs/00-01-faq.md b/docs/_docs/00-01-faq.md index 01172f84a..e114fce72 100644 --- a/docs/_docs/00-01-faq.md +++ b/docs/_docs/00-01-faq.md @@ -91,7 +91,7 @@ source process), then: ``` cd docs -docker run --rm --volume=$PWD:/srv/jekyll -p 127.0.0.1:4000:4000 -it jekyll/jekyll:pages jekyll serve +docker run --rm --volume=$PWD:/srv/jekyll -p 127.0.0.1:4000:4000 -it jekyll/jekyll:pages bash -c "bundle install && jekyll serve" ``` You can edit the files in docs with any editor and view the live updates in a diff --git a/docs/_docs/02-01-configuration.md b/docs/_docs/02-01-configuration.md index df790b24b..6ffb9afc0 100644 --- a/docs/_docs/02-01-configuration.md +++ b/docs/_docs/02-01-configuration.md @@ -53,6 +53,7 @@ These are only used during the webpack build. - `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database. #### Advanced +{:.no_toc} - `TALK_REDIS_RECONNECTION_MAX_ATTEMPTS` (_optional_) - the amount of attempts that a redis connection will attempt to reconnect before aborting with an @@ -76,11 +77,19 @@ These are only used during the webpack build. - `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - When `TRUE`, disables the dynamic setup endpoint. (Default `FALSE`) #### Advanced +{:.no_toc} - `TALK_ROOT_URL_MOUNT_PATH` (_optional_) - when set to `TRUE`, the routes will be mounted onto the `` component of the `TALK_ROOT_URL`. You would use this when your upstream proxy cannot strip the prefix from the url. (Default `FALSE`) +- `TALK_WEBSOCKET_LIVE_URI` (_optional_) - used to override the location to + connect to the websocket endpoint to potentially another host. This should + be used when you need to route websocket requests out of your CDN in order to + serve traffic more efficiently for websockets. **Warning: if used without + managing the auth state manually, auth cannot be persisted, for further + information refer to the [Persistence Documentation]({{ "/docs/running/persistence/" | absolute_url }})** + (Default `${ssl ? 'ws' : 'wss'}://${location.host}${TALK_ROOT_URL_MOUNT_PATH}api/v1/live`) ### Word Filter @@ -105,6 +114,7 @@ variable. Refer to the [Secrets Documentation]({{ "/docs/running/secrets/" | abs on the contents of those variables.** #### Advanced +{:.no_toc} These are advanced settings for fine tuning the auth integration, and is not needed in most situations. diff --git a/docs/_docs/02-05-persistence.md b/docs/_docs/02-05-persistence.md new file mode 100644 index 000000000..fc67a9970 --- /dev/null +++ b/docs/_docs/02-05-persistence.md @@ -0,0 +1,45 @@ +--- +title: User Persistence +permalink: /docs/running/persistence/ +--- + +One of the biggest problems on the internet today is the proliferation of +sophisticated tracking systems and the outcome of invading user's privacy. This +has had quite a big impact on our design of Talk, as we've had to work around +the safeguards that are there to keep your data safe while still allowing our +application to run smoothly on the page it's embedded on. + +--- + +**Problem**: Safari has inconsistent behavior around localStorage when used +within an iFrame. + +**Solution**: We set a cookie instead when Safari is detected to store the auth +state. + +--- + +**Problem**: Safari's default privacy settings block cookies from domains that +do not match the current domain. + +**Solution**: When using Talk's built in auth, we will open a pop-out when +setting the cookie, so that the domain of the setting domain matches the issuer. + +--- + +**Problem**: When using a different domain for websockets, and using the built +in auth solution, cookies are not set on that domain for use with Safari. + +**No Solution Exists**: It is our expectation that for users that must deploy +Talk in environments that must run the websockets out of a separate domain will +use and integrate their own auth solution. During the login process in Talk, +users submit their user credentials to an auth endpoint, and receive a token +back, or for Safari, a cookie. Aggressive defaults in Safari make it not +possible to have one domain set cookies for another domain during this process. +This results in a situation where we have no way to persist the auth credentials +for this specific situation for the time being. + +--- + +It's important to understand that these problems are in fact there to improve +privacy for end users. \ No newline at end of file diff --git a/routes/admin/index.js b/routes/admin/index.js index 30bb69f5e..ab6189e85 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -1,7 +1,8 @@ const express = require('express'); const router = express.Router(); const { - RECAPTCHA_PUBLIC + RECAPTCHA_PUBLIC, + WEBSOCKET_LIVE_URI, } = require('../../config'); // Get /email-confirmation expects a signed JWT in the hash @@ -20,7 +21,8 @@ router.get('/password-reset', (req, res) => { router.get('*', (req, res) => { const data = { - TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC + TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC, + LIVE_URI: WEBSOCKET_LIVE_URI, }; res.render('admin', {data}); diff --git a/routes/embed/index.js b/routes/embed/index.js index 0aaa9ff9c..256d03b30 100644 --- a/routes/embed/index.js +++ b/routes/embed/index.js @@ -2,25 +2,24 @@ const express = require('express'); const router = express.Router(); const SettingsService = require('../../services/settings'); const { - RECAPTCHA_PUBLIC + RECAPTCHA_PUBLIC, + WEBSOCKET_LIVE_URI, } = require('../../config'); -router.use('/:embed', (req, res, next) => { +router.use('/:embed', async (req, res, next) => { switch (req.params.embed) { - case 'stream': - return SettingsService.retrieve() - .then(({customCssUrl}) => { - const data = { - TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC - }; + case 'stream': { + const {customCssUrl} = await SettingsService.retrieve(); + const data = { + TALK_RECAPTCHA_PUBLIC: RECAPTCHA_PUBLIC, + LIVE_URI: WEBSOCKET_LIVE_URI, + }; - return res.render('embed/stream', {customCssUrl, data}); - }); - default: - - // will return a 404. - return next(); + return res.render('embed/stream', {customCssUrl, data}); } + } + + return next(); }); module.exports = router; From 4a9b24f0964ce0882e3fa8ca796d96d228571228 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 5 Sep 2017 14:08:43 -0600 Subject: [PATCH 10/29] added more docs related to cookies --- docs/_docs/02-05-persistence.md | 9 ++++++-- docs/_docs/04-04-plugins-server.md | 34 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/_docs/02-05-persistence.md b/docs/_docs/02-05-persistence.md index fc67a9970..a1d3a5e2f 100644 --- a/docs/_docs/02-05-persistence.md +++ b/docs/_docs/02-05-persistence.md @@ -41,5 +41,10 @@ for this specific situation for the time being. --- -It's important to understand that these problems are in fact there to improve -privacy for end users. \ No newline at end of file +If you are using a custom auth solution, (Which involves providing the user's +jwt token via the `auth_token` parameter on the call to +`Talk.render(... {auth_token})` and creating the +[tokenUserNotFound]({{ "/docs/plugins/server/" | absolute_url }}) hook to lookup +that user) then concerns related to cookies/localStorage are moot! As it isn't +necessary for Talk to maintain any state beyond what is passed to it via the +pym bridge. \ No newline at end of file diff --git a/docs/_docs/04-04-plugins-server.md b/docs/_docs/04-04-plugins-server.md index f807da141..e639cd308 100644 --- a/docs/_docs/04-04-plugins-server.md +++ b/docs/_docs/04-04-plugins-server.md @@ -85,7 +85,7 @@ configure the context plugin before it would be mounted at `context.plugins`. This plugin above would mount at: `context.plugins.Slack`, or, if you're using [object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`. -##### `Sort` +##### Field: `Sort` A special context hook, `Sort` will allow plugin authors to provide new methods to sort data. An example is as follows: @@ -269,22 +269,6 @@ The function is async, and should return the user object that was created in the database, or null if the user wasn't found. The `jwt` parameter of the object is the unpacked token, while `token` is the original jwt token string. -### Routes - -#### Field: `router` - -```js -(router) => { - router.get('/api/v1/people', (req, res) => { - res.json({people: [{name: 'Bob'}]}); - }); -} -``` - -The Router hook allows you to create a function that accepts the base express -router where you can mount any amount of middleware/routes to do any form of -action needed by external applications. - #### Field: `tags` The tags hook allows a plugin to define tags that are code controlled (added @@ -309,6 +293,22 @@ on how to create a hook for the `OFF_TOPIC` name: You can refer to `models/schema/tag.js` for the available schema to match when creating models to enable/disable specific features. +### Routes + +#### Field: `router` + +```js +(router) => { + router.get('/api/v1/people', (req, res) => { + res.json({people: [{name: 'Bob'}]}); + }); +} +``` + +The Router hook allows you to create a function that accepts the base express +router where you can mount any amount of middleware/routes to do any form of +action needed by external applications. + ### Authorization middleware The following example creates the requisite callback route and passport From 6f82fd76f5acace588f4d892ce16f7c701747c0e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 6 Sep 2017 22:11:48 +0700 Subject: [PATCH 11/29] First draft of toxic-comments --- .eslintignore | 1 + .gitignore | 1 + .../coral-embed-stream/src/graphql/index.js | 2 +- client/coral-framework/graphql/fragments.js | 1 + client/coral-framework/graphql/mutations.js | 8 +- graph/errorHandler.js | 70 +++++++++ graph/mutators/comment.js | 5 +- graph/resolvers/root_mutation.js | 69 ++++---- graph/schema.js | 10 +- graph/typeDefs.graphql | 8 +- plugins.default.json | 6 +- .../client/actions.js | 5 - .../client/components/CheckToxicityHook.js | 27 ++++ .../client/components/ToxicCommentAlert.js | 62 -------- .../client/components/styles.css | 5 - .../client/constants.js | 1 - .../client/index.js | 6 +- .../client/reducer.js | 18 --- .../client/translations.yml | 4 + plugins/talk-plugin-toxic-comments/index.js | 7 +- .../package-lock.json | 144 ----------------- .../talk-plugin-toxic-comments/package.json | 7 +- .../server/apiKey.js | 6 + .../server/constants.js | 3 + .../server/errors.js | 15 ++ .../server/hooks.js | 37 +++++ .../server/perspective.js | 37 +++++ .../server/router.js | 65 +++----- .../server/typeDefs.graphql | 4 + plugins/talk-plugin-toxic-comments/yarn.lock | 147 ------------------ test/server/graph/mutations/createComment.js | 6 +- test/server/graph/mutations/removeTag.js | 2 +- 32 files changed, 298 insertions(+), 491 deletions(-) create mode 100644 graph/errorHandler.js delete mode 100644 plugins/talk-plugin-toxic-comments/client/actions.js create mode 100644 plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js delete mode 100644 plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js delete mode 100644 plugins/talk-plugin-toxic-comments/client/components/styles.css delete mode 100644 plugins/talk-plugin-toxic-comments/client/constants.js delete mode 100644 plugins/talk-plugin-toxic-comments/client/reducer.js delete mode 100644 plugins/talk-plugin-toxic-comments/package-lock.json create mode 100644 plugins/talk-plugin-toxic-comments/server/apiKey.js create mode 100644 plugins/talk-plugin-toxic-comments/server/constants.js create mode 100644 plugins/talk-plugin-toxic-comments/server/errors.js create mode 100644 plugins/talk-plugin-toxic-comments/server/hooks.js create mode 100644 plugins/talk-plugin-toxic-comments/server/perspective.js create mode 100644 plugins/talk-plugin-toxic-comments/server/typeDefs.graphql diff --git a/.eslintignore b/.eslintignore index 4805e34a4..d23f5dfb4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -22,5 +22,6 @@ plugins/* !plugins/talk-plugin-author-menu !plugins/talk-plugin-member-since !plugins/talk-plugin-ignore-user +!plugins/talk-plugin-toxic-comments node_modules diff --git a/.gitignore b/.gitignore index 98abecb32..c3c17fac3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ plugins/* !plugins/talk-plugin-author-menu !plugins/talk-plugin-member-since !plugins/talk-plugin-ignore-user +!plugins/talk-plugin-toxic-comments **/node_modules/* story.html diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index 09470de14..ad281ce0c 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -109,7 +109,7 @@ export default { }, mutations: { PostComment: ({ - variables: {comment: {asset_id, body, parent_id, tags = []}}, + variables: {input: {asset_id, body, parent_id, tags = []}}, state: {auth}, }) => ({ optimisticResponse: { diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index 110f0796b..5b3fe5498 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -6,6 +6,7 @@ export default { 'SetCommentStatusResponse', 'SuspendUserResponse', 'RejectUsernameResponse', + 'CreateCommentResponse', 'SetUserStatusResponse', 'CreateFlagResponse', 'EditCommentResponse', diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 4254400bb..c09ce8751 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -192,17 +192,17 @@ export const withSetUserStatus = withMutation( export const withPostComment = withMutation( gql` - mutation PostComment($comment: CreateCommentInput!) { - createComment(comment: $comment) { + mutation PostComment($input: CreateCommentInput!) { + createComment(input: $input) { ...CreateCommentResponse } } `, { props: ({mutate}) => ({ - postComment: (comment) => { + postComment: (input) => { return mutate({ variables: { - comment + input }, }); } diff --git a/graph/errorHandler.js b/graph/errorHandler.js new file mode 100644 index 000000000..066ae3319 --- /dev/null +++ b/graph/errorHandler.js @@ -0,0 +1,70 @@ +const { + GraphQLObjectType, + GraphQLInterfaceType +} = require('graphql'); +const {maskErrors} = require('graphql-errors'); +const errors = require('../errors'); +const {Error: {ValidationError}} = require('mongoose'); + +// This function is pretty much copied verbatim from the graphql-tools repo: +// https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194 +const forEachField = (schema, fn) => { + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach((typeName) => { + const type = typeMap[typeName]; + + if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) { + const fields = type.getFields(); + Object.keys(fields).forEach((fieldName) => { + const field = fields[fieldName]; + fn(field, typeName, fieldName); + }); + } + }); +}; + +// If an APIError happens in a mutation, then respond with `{errors: Array}` +// according to the schema. +const decorateWithMutationErrorHandler = (field) => { + const fieldResolver = field.resolve; + field.resolve = async (obj, args, ctx, info) => { + try { + return await fieldResolver(obj, args, ctx, info); + } + catch(err) { + if (err instanceof errors.APIError) { + return { + errors: [err] + }; + } else if (err instanceof ValidationError) { + + // TODO: wrap this with one of our internal errors. + throw err; + } + + throw err; + } + }; +}; + +// Masks errors during production and handle mutation errors inside the schema. +const decorateWithErrorHandler = (schema) => { + forEachField(schema, (field, typeName) => { + + // Handle mutation errors. + if (typeName === 'RootMutation') { + decorateWithMutationErrorHandler(field); + } + + // If we are in production mode, don't show server errors to the front end. + if (process.env.NODE_ENV === 'production') { + + // Mask errors that are thrown if we are in a production environment. + maskErrors(field); + } + }); +}; + +module.exports = { + decorateWithErrorHandler, +}; diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 4ca044fcf..71e341547 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -154,7 +154,7 @@ const adjustKarma = (Comments, id, status) => async () => { * @param {String} [status='NONE'] the status of the new comment * @return {Promise} resolves to the created comment */ -const createComment = async (context, {tags = [], body, asset_id, parent_id = null}, status = 'NONE') => { +const createComment = async (context, {tags = [], body, asset_id, parent_id = null, metadata = {}}, status = 'NONE') => { const {user, loaders: {Comments}, pubsub} = context; // Resolve the tags for the comment. @@ -166,7 +166,8 @@ const createComment = async (context, {tags = [], body, asset_id, parent_id = nu parent_id, status, tags, - author_id: user.id + author_id: user.id, + metadata, }); // If the loaders are present, clear the caches for these values because we diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 685c26a17..437d2ab5b 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -1,35 +1,41 @@ -const wrapResponse = require('../helpers/response'); - const RootMutation = { - createComment(_, {comment}, {mutators: {Comment}}) { - return wrapResponse('comment')(Comment.create(comment)); + async createComment(_, {input}, {mutators: {Comment}}) { + return { + comment: await Comment.create(input), + }; }, - editComment(_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) { - return wrapResponse('comment')(Comment.edit({id, asset_id, edit: {body}})); + async editComment(_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) { + return { + comment: await Comment.edit({id, asset_id, edit: {body}}), + }; }, - createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) { - return wrapResponse('flag')(Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}})); + async createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) { + return { + flag: Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}), + }; }, - createDontAgree(_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) { - return wrapResponse('dontagree')(Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}})); + async createDontAgree(_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) { + return { + dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}), + }; }, - deleteAction(_, {id}, {mutators: {Action}}) { - return wrapResponse(null)(Action.delete({id})); + async deleteAction(_, {id}, {mutators: {Action}}) { + await Action.delete({id}); }, - setUserStatus(_, {id, status}, {mutators: {User}}) { - return wrapResponse(null)(User.setUserStatus({id, status})); + async setUserStatus(_, {id, status}, {mutators: {User}}) { + await User.setUserStatus({id, status}); }, - suspendUser(_, {input: {id, message, until}}, {mutators: {User}}) { - return wrapResponse(null)(User.suspendUser({id, message, until})); + async suspendUser(_, {input: {id, message, until}}, {mutators: {User}}) { + await User.suspendUser({id, message, until}); }, - rejectUsername(_, {input: {id, message}}, {mutators: {User}}) { - return wrapResponse(null)(User.rejectUsername({id, message})); + async rejectUsername(_, {input: {id, message}}, {mutators: {User}}) { + await User.rejectUsername({id, message}); }, - ignoreUser(_, {id}, {mutators: {User}}) { - return wrapResponse(null)(User.ignoreUser({id})); + async ignoreUser(_, {id}, {mutators: {User}}) { + await User.ignoreUser({id}); }, - stopIgnoringUser(_, {id}, {mutators: {User}}) { - return wrapResponse(null)(User.stopIgnoringUser({id})); + async stopIgnoringUser(_, {id}, {mutators: {User}}) { + await User.stopIgnoringUser({id}); }, async setCommentStatus(_, {id, status}, {mutators: {Comment}, pubsub}) { const comment = await Comment.setStatus({id, status}); @@ -42,19 +48,20 @@ const RootMutation = { // Publish the comment status change via the subscription. pubsub.publish('commentRejected', comment); } - return wrapResponse(null)(comment); }, - addTag(_, {tag}, {mutators: {Tag}}) { - return wrapResponse(null)(Tag.add(tag)); + async addTag(_, {tag}, {mutators: {Tag}}) { + await Tag.add(tag); }, - removeTag(_, {tag}, {mutators: {Tag}}) { - return wrapResponse(null)(Tag.remove(tag)); + async removeTag(_, {tag}, {mutators: {Tag}}) { + await Tag.remove(tag); }, - createToken(_, {input}, {mutators: {Token}}) { - return wrapResponse('token')(Token.create(input)); + async createToken(_, {input}, {mutators: {Token}}) { + return { + token: await Token.create(input), + }; }, - revokeToken(_, {input}, {mutators: {Token}}) { - return wrapResponse(null)(Token.revoke(input)); + async revokeToken(_, {input}, {mutators: {Token}}) { + await Token.revoke(input); } }; diff --git a/graph/schema.js b/graph/schema.js index 53360e701..d4cd9f890 100644 --- a/graph/schema.js +++ b/graph/schema.js @@ -1,6 +1,6 @@ const {makeExecutableSchema} = require('graphql-tools'); -const {maskErrors} = require('graphql-errors'); const {decorateWithHooks} = require('./hooks'); +const {decorateWithErrorHandler} = require('./errorHandler'); const plugins = require('../services/plugins'); const resolvers = require('./resolvers'); @@ -11,11 +11,7 @@ const schema = makeExecutableSchema({typeDefs, resolvers}); // Plugin to the schema level resolvers to provide an before/after hook. decorateWithHooks(schema, plugins.get('server', 'hooks')); -// If we are in production mode, don't show server errors to the front end. -if (process.env.NODE_ENV === 'production') { - - // Mask errors that are thrown if we are in a production environment. - maskErrors(schema); -} +// Handle errors like masking in production and mutation errors. +decorateWithErrorHandler(schema); module.exports = schema; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 9d8e25f12..c3cbc2e57 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -1033,7 +1033,7 @@ type RevokeTokenResponse implements Response { type RootMutation { # Creates a comment on the asset. - createComment(comment: CreateCommentInput!): CreateCommentResponse + createComment(input: CreateCommentInput!): CreateCommentResponse # Creates a flag on an entity. createFlag(flag: CreateFlagInput!): CreateFlagResponse @@ -1060,10 +1060,10 @@ type RootMutation { setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse # Add a tag. - addTag(tag: ModifyTagInput!): ModifyTagResponse! + addTag(tag: ModifyTagInput!): ModifyTagResponse # Removes a tag. - removeTag(tag: ModifyTagInput!): ModifyTagResponse! + removeTag(tag: ModifyTagInput!): ModifyTagResponse # Ignore comments by another user ignoreUser(id: ID!): IgnoreUserResponse @@ -1072,7 +1072,7 @@ type RootMutation { createToken(input: CreateTokenInput!): CreateTokenResponse! # RevokeToken will revoke an existing token. - revokeToken(input: RevokeTokenInput!): RevokeTokenResponse! + revokeToken(input: RevokeTokenInput!): RevokeTokenResponse # Stop Ignoring comments by another user stopIgnoringUser(id: ID!): StopIgnoringUserResponse diff --git a/plugins.default.json b/plugins.default.json index 950cec229..46762865f 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -4,7 +4,8 @@ "talk-plugin-respect", "talk-plugin-offtopic", "talk-plugin-facebook-auth", - "talk-plugin-featured-comments" + "talk-plugin-featured-comments", + "talk-plugin-toxic-comments" ], "client": [ "talk-plugin-respect", @@ -20,6 +21,7 @@ "talk-plugin-sort-most-replied", "talk-plugin-author-menu", "talk-plugin-member-since", - "talk-plugin-ignore-user" + "talk-plugin-ignore-user", + "talk-plugin-toxic-comments" ] } diff --git a/plugins/talk-plugin-toxic-comments/client/actions.js b/plugins/talk-plugin-toxic-comments/client/actions.js deleted file mode 100644 index 9cb3947f8..000000000 --- a/plugins/talk-plugin-toxic-comments/client/actions.js +++ /dev/null @@ -1,5 +0,0 @@ -import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants'; - -export const toggleCheckbox = () => ({ - type: OFFTOPIC_TOGGLE_CHECKBOX -}); diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js new file mode 100644 index 000000000..2e4c66d3e --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -0,0 +1,27 @@ +import React from 'react'; + +export default class CheckToxicityHook extends React.Component { + checked = false; + + componentDidMount() { + this.toxicityPreHook = this.props.registerHook('preSubmit', (input) => { + if (!this.checked) { + input.checkToxicity = true; + this.checked = true; + } + }); + + this.toxicityPostHook = this.props.registerHook('postSubmit', () => { + this.checked = false; + }); + } + + componentWillUnmount() { + this.props.unregisterHook(this.toxicityPreHook); + this.props.unregisterHook(this.toxicityPostHook); + } + + render() { + return null; + } +} diff --git a/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js b/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js deleted file mode 100644 index 51ad5ef6d..000000000 --- a/plugins/talk-plugin-toxic-comments/client/components/ToxicCommentAlert.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; -import {t} from 'plugin-api/beta/client/services'; -import {isTagged} from 'plugin-api/beta/client/utils'; - -export default class ToxicCommentAlert extends React.Component { - - constructor(props) { - super(props); - this.state = { - toxic: false - }; - } - - componentDidMount() { - this.toxicityHook = this.props.registerHook('preSubmit', (data) => { - const comment = data.body; - (async() => { - var toxicity = await fetch('/api/v1/toxicity/score', { - method: 'POST', - body: comment - }) - .then(response => response.json()) - .then(function(json) { - return json.score; - }) - .catch(function(err) { - console.log(err); - return 0; - }); - console.log(toxicity); - if(toxicity > 0.3){ - this.setState({ - toxic: true - }); - } - else { - this.setState({ - toxic: false - }); - } - })(); - }); - } - - componentWillUnmount() { - this.props.unregisterHook(this.toxicityHook); - } - - render() { - return( -
    - { - this.state.toxic ? ( - Are you sure you want to post this? Other members of the community my view your comment as toxic, so please take a moment to reconsider. - ) : null - } -
    - ); - } - -} diff --git a/plugins/talk-plugin-toxic-comments/client/components/styles.css b/plugins/talk-plugin-toxic-comments/client/components/styles.css deleted file mode 100644 index d898dd111..000000000 --- a/plugins/talk-plugin-toxic-comments/client/components/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -.toxicComment { - color: red; - font-weight: bold; - padding: 12px 15px 10px 15px -} diff --git a/plugins/talk-plugin-toxic-comments/client/constants.js b/plugins/talk-plugin-toxic-comments/client/constants.js deleted file mode 100644 index 122d7de17..000000000 --- a/plugins/talk-plugin-toxic-comments/client/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const OFFTOPIC_TOGGLE_CHECKBOX = 'OFFTOPIC_TOGGLE_CHECKBOX'; diff --git a/plugins/talk-plugin-toxic-comments/client/index.js b/plugins/talk-plugin-toxic-comments/client/index.js index dc9aa5348..46f8c0fc0 100644 --- a/plugins/talk-plugin-toxic-comments/client/index.js +++ b/plugins/talk-plugin-toxic-comments/client/index.js @@ -1,5 +1,5 @@ import translations from './translations.yml'; -import ToxicCommentAlert from './components/ToxicCommentAlert'; +import CheckToxicityHook from './components/CheckToxicityHook'; /** * coral-plugin-offtopic depends on coral-plugin-viewing-options @@ -9,6 +9,6 @@ import ToxicCommentAlert from './components/ToxicCommentAlert'; export default { translations, slots: { - commentInputDetailArea: [ToxicCommentAlert], - } + commentInputDetailArea: [CheckToxicityHook], + }, }; diff --git a/plugins/talk-plugin-toxic-comments/client/reducer.js b/plugins/talk-plugin-toxic-comments/client/reducer.js deleted file mode 100644 index adab987a0..000000000 --- a/plugins/talk-plugin-toxic-comments/client/reducer.js +++ /dev/null @@ -1,18 +0,0 @@ -import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants'; - -const initialState = { - checked: false -}; - -export default function offTopic (state = initialState, action) { - switch (action.type) { - case OFFTOPIC_TOGGLE_CHECKBOX: { - return { - ...state, - checked: !state.checked - }; - } - default : - return state; - } -} diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml index 7f6db9370..b1553a3d9 100644 --- a/plugins/talk-plugin-toxic-comments/client/translations.yml +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -1,4 +1,8 @@ en: + error: + COMMENT_IS_TOXIC: | + Are you sure? The language in this comment might violate our community guidelines. + You can edit the comment or submit it for moderator review. talk-plugin-featured-comments: featured: Featured go_to_conversation: Go to conversation diff --git a/plugins/talk-plugin-toxic-comments/index.js b/plugins/talk-plugin-toxic-comments/index.js index 10651ec9d..232f45052 100644 --- a/plugins/talk-plugin-toxic-comments/index.js +++ b/plugins/talk-plugin-toxic-comments/index.js @@ -1,5 +1,10 @@ +const {readFileSync} = require('fs'); +const path = require('path'); const router = require('./server/router'); +const hooks = require('./server/hooks'); module.exports = { - router + typeDefs: readFileSync(path.join(__dirname, 'server/typeDefs.graphql'), 'utf8'), + router, + hooks, }; diff --git a/plugins/talk-plugin-toxic-comments/package-lock.json b/plugins/talk-plugin-toxic-comments/package-lock.json deleted file mode 100644 index 690852f34..000000000 --- a/plugins/talk-plugin-toxic-comments/package-lock.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "name": "@coralproject/talk-plugin-toxicity", - "version": "0.0.1", - "lockfileVersion": 1, - "dependencies": { - "axios": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz", - "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=" - }, - "body-parser": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", - "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", - "dependencies": { - "debug": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", - "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" - } - } - }, - "boom": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/boom/-/boom-3.2.2.tgz", - "integrity": "sha1-DwzF0ErcUAO4x9cfQsynJx/vDng=" - }, - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" - }, - "content-type": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", - "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" - }, - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" - }, - "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "express-boom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/express-boom/-/express-boom-2.0.0.tgz", - "integrity": "sha1-1AC5QOlhqKou2OP3fFlfoeQbZLM=" - }, - "follow-redirects": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.4.tgz", - "integrity": "sha512-Suw6KewLV2hReSyEOeql+UUkBVyiBm3ok1VPrVFRZnQInWpdoZbbiG5i8aJVSjTr0yQ4Ava0Sh6/joCg1Brdqw==" - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" - }, - "http-errors": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", - "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" - }, - "iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" - }, - "mime-types": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "raw-body": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", - "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - } - } -} diff --git a/plugins/talk-plugin-toxic-comments/package.json b/plugins/talk-plugin-toxic-comments/package.json index b288f95c4..848e6d948 100644 --- a/plugins/talk-plugin-toxic-comments/package.json +++ b/plugins/talk-plugin-toxic-comments/package.json @@ -5,10 +5,5 @@ "description": "Provides support for measuring the toxicity of user comments using the Perspectives API", "main": "index.js", "author": "The Coral Project Team ", - "license": "Apache-2.0", - "dependencies": { - "axios": "^0.16.2", - "body-parser": "^1.17.2", - "express-boom": "^2.0.0" - } + "license": "Apache-2.0" } diff --git a/plugins/talk-plugin-toxic-comments/server/apiKey.js b/plugins/talk-plugin-toxic-comments/server/apiKey.js new file mode 100644 index 000000000..43e02d154 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/apiKey.js @@ -0,0 +1,6 @@ +const apiKey = process.env.TALK_PERSPECTIVE_API_KEY; +if(!apiKey) { + throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); +} + +module.exports = apiKey; diff --git a/plugins/talk-plugin-toxic-comments/server/constants.js b/plugins/talk-plugin-toxic-comments/server/constants.js new file mode 100644 index 000000000..a335c519c --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/constants.js @@ -0,0 +1,3 @@ +module.exports = { + TOXICITY_THRESHOLD: 0.8, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/errors.js b/plugins/talk-plugin-toxic-comments/server/errors.js new file mode 100644 index 000000000..57f6ef959 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/errors.js @@ -0,0 +1,15 @@ +const {APIError} = require('../../../errors'); + +const ErrNoComment = new APIError('Comment must be provided', { + status: 400, +}); + +const ErrToxic = new APIError('Comment is toxic', { + status: 400, + translation_key: 'COMMENT_IS_TOXIC', +}); + +module.exports = { + ErrNoComment, + ErrToxic, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js new file mode 100644 index 000000000..07166c73a --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -0,0 +1,37 @@ +const perspective = require('./perspective'); +const {ADD_COMMENT_TAG} = require('../../../perms/constants'); +const {ErrToxic} = require('./errors'); +const {TOXICITY_THRESHOLD} = require('./constants'); + +module.exports = { + Comment: { + tags: { + post(comment, input, {user}, _info, result) { + if (comment.metadata.perspective && user && user.can(ADD_COMMENT_TAG)) { + return result.concat({tag: {name: 'TOXIC', created_at: new Date()}}); + } + return result; + } + }, + }, + RootMutation: { + createComment: { + async pre(_, {input}, _context, _info) { + + // Don't call out to perspective when running tests. + if (process.env.NODE_ENV === 'test') { + return; + } + + const apiKey = require('./apiKey'); + const scores = await perspective.getScores(apiKey, input.body); + if (input.checkToxicity && scores.SEVERE_TOXICITY.summaryScore > TOXICITY_THRESHOLD) { + throw ErrToxic; + } + input.metadata = Object.assign({}, input.metadata, { + perspective: scores, + }); + }, + }, + }, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js new file mode 100644 index 000000000..e863b4777 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -0,0 +1,37 @@ +const fetch = require('node-fetch'); + +const API_ENPOINT = 'https://commentanalyzer.googleapis.com/v1alpha1'; + +async function getScores(apiKey, text) { + const response = await fetch(`${API_ENPOINT}/comments:analyze?key=${apiKey}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + comment: { + text, + }, + + // TODO: support other languages. + languages: ['en'], + requestedAttributes: { + TOXICITY: {}, + SEVERE_TOXICITY: {}, + } + }), + }); + const data = await response.json(); + return { + TOXICITY: { + summaryScore: data.attributeScores.TOXICITY.summaryScore.value + }, + SEVERE_TOXICITY: { + summaryScore: data.attributeScores.SEVERE_TOXICITY.summaryScore.value + }, + }; +} + +module.exports = { + getScores, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/router.js b/plugins/talk-plugin-toxic-comments/server/router.js index 4f3011efc..42f189d26 100644 --- a/plugins/talk-plugin-toxic-comments/server/router.js +++ b/plugins/talk-plugin-toxic-comments/server/router.js @@ -1,56 +1,33 @@ -const http = require('axios'); -const boom = require('express-boom'); -const bodyParser = require('body-parser'); +const perspective = require('./perspective'); +const {ErrNoComment} = require('./errors'); module.exports = (router) => { - const key = process.env.TALK_PERSPECTIVE_API_KEY; - if(!key) { - throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); - } - - - router.use(boom()); - router.use(bodyParser.text()); - /** * POST /api/v1/toxicity/score * args: * - provide the comment in the request body */ - router.post('/api/v1/toxicity/score', (req, res) => { - var comment = req.body; - if(comment) { - var body = { - comment: { - text: comment, - }, - languages: ["en"], - requestedAttributes: { - TOXICITY: {} - } - }; - var headers = { - 'Content-Type': 'application/json', - }; - http.post( - 'https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key='+key, - body) - .then(function(response) { - var data = response.data; - var score = { - comment: comment, - score: data.attributeScores.TOXICITY.summaryScore.value - } - return res.json(score); - }) - .catch(function(err) { - console.log(err); - res.boom.badRequest('The Perspective API returned an error. Please check the server logs for details.'); - }) + router.post('/api/v1/toxicity/score', async (req, res, next) => { + const apiKey = process.env.TALK_PERSPECTIVE_API_KEY; + if(!apiKey) { + throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); } - else { - res.boom.badRequest('No comment provided'); + + const {comment} = req.body; + + if(!comment) { + return next(ErrNoComment); + } + + try { + const scores = await perspective.getScores(apiKey, comment); + return res.json({ + comment, + score: scores.SEVERE_TOXICITY.summaryScore, + }); + } catch(err) { + return next(err); } }); diff --git a/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql b/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql new file mode 100644 index 000000000..7352db00f --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql @@ -0,0 +1,4 @@ +input CreateCommentInput { + checkToxicity: Boolean +} + diff --git a/plugins/talk-plugin-toxic-comments/yarn.lock b/plugins/talk-plugin-toxic-comments/yarn.lock index 8097789e1..fb57ccd13 100644 --- a/plugins/talk-plugin-toxic-comments/yarn.lock +++ b/plugins/talk-plugin-toxic-comments/yarn.lock @@ -2,150 +2,3 @@ # yarn lockfile v1 -axios@^0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" - dependencies: - follow-redirects "^1.2.3" - is-buffer "^1.1.5" - -body-parser@^1.17.2: - version "1.17.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" - dependencies: - bytes "2.4.0" - content-type "~1.0.2" - debug "2.6.7" - depd "~1.1.0" - http-errors "~1.6.1" - iconv-lite "0.4.15" - on-finished "~2.3.0" - qs "6.4.0" - raw-body "~2.2.0" - type-is "~1.6.15" - -boom@3.2.x: - version "3.2.2" - resolved "https://registry.yarnpkg.com/boom/-/boom-3.2.2.tgz#0f0cc5d04adc5003b8c7d71f42cca7271fef0e78" - dependencies: - hoek "4.x.x" - -bytes@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" - -content-type@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" - -debug@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" - dependencies: - ms "2.0.0" - -debug@^2.4.5: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - dependencies: - ms "2.0.0" - -depd@1.1.0, depd@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -express-boom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/express-boom/-/express-boom-2.0.0.tgz#d400b940e961a8aa2ed8e3f77c595fa1e41b64b3" - dependencies: - boom "3.2.x" - -follow-redirects@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" - dependencies: - debug "^2.4.5" - -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" - -http-errors@~1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" - dependencies: - depd "1.1.0" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -iconv-lite@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -is-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" - -mime-types@~2.1.15: - version "2.1.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" - dependencies: - mime-db "~1.27.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - -qs@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - -raw-body@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" - dependencies: - bytes "2.4.0" - iconv-lite "0.4.15" - unpipe "1.0.0" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" - -"statuses@>= 1.3.1 < 2": - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" - -type-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.15" - -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" diff --git a/test/server/graph/mutations/createComment.js b/test/server/graph/mutations/createComment.js index 8b99dd557..72c163d35 100644 --- a/test/server/graph/mutations/createComment.js +++ b/test/server/graph/mutations/createComment.js @@ -16,8 +16,8 @@ describe('graph.mutations.createComment', () => { beforeEach(() => SettingsService.init()); const query = ` - mutation CreateComment($comment: CreateCommentInput = {asset_id: 123, body: "Here's my comment!"}) { - createComment(comment: $comment) { + mutation CreateComment($input: CreateCommentInput = {asset_id: 123, body: "Here's my comment!"}) { + createComment(input: $input) { comment { id status @@ -176,7 +176,7 @@ describe('graph.mutations.createComment', () => { const context = new Context({user: new UserModel({status: 'ACTIVE'})}); return graphql(schema, query, {}, context, { - comment: { + input: { asset_id: '123', body } diff --git a/test/server/graph/mutations/removeTag.js b/test/server/graph/mutations/removeTag.js index 4cb4f1e6a..761cd44c9 100644 --- a/test/server/graph/mutations/removeTag.js +++ b/test/server/graph/mutations/removeTag.js @@ -45,7 +45,7 @@ describe('graph.mutations.removeTag', () => { console.error(response.errors); } expect(response.errors).to.be.empty; - expect(response.data.removeTag.errors).to.be.null; + expect(response.data.removeTag).to.be.null; let retrievedComment = await CommentsService.findById(comment.id); From a6b517b3dd647e49ea3c2555bd1701afde3106c8 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 6 Sep 2017 22:14:58 +0700 Subject: [PATCH 12/29] Remove unused code --- .../client/translations.yml | 6 ---- plugins/talk-plugin-toxic-comments/index.js | 2 -- .../server/errors.js | 5 --- .../server/hooks.js | 11 ------ .../server/router.js | 34 ------------------- 5 files changed, 58 deletions(-) delete mode 100644 plugins/talk-plugin-toxic-comments/server/router.js diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml index b1553a3d9..ebcc31903 100644 --- a/plugins/talk-plugin-toxic-comments/client/translations.yml +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -3,10 +3,4 @@ en: COMMENT_IS_TOXIC: | Are you sure? The language in this comment might violate our community guidelines. You can edit the comment or submit it for moderator review. - talk-plugin-featured-comments: - featured: Featured - go_to_conversation: Go to conversation es: - talk-plugin-featured-comments: - featured: Remarcado - go_to_conversation: Ir al comentario diff --git a/plugins/talk-plugin-toxic-comments/index.js b/plugins/talk-plugin-toxic-comments/index.js index 232f45052..0802a6c11 100644 --- a/plugins/talk-plugin-toxic-comments/index.js +++ b/plugins/talk-plugin-toxic-comments/index.js @@ -1,10 +1,8 @@ const {readFileSync} = require('fs'); const path = require('path'); -const router = require('./server/router'); const hooks = require('./server/hooks'); module.exports = { typeDefs: readFileSync(path.join(__dirname, 'server/typeDefs.graphql'), 'utf8'), - router, hooks, }; diff --git a/plugins/talk-plugin-toxic-comments/server/errors.js b/plugins/talk-plugin-toxic-comments/server/errors.js index 57f6ef959..17a2d20c6 100644 --- a/plugins/talk-plugin-toxic-comments/server/errors.js +++ b/plugins/talk-plugin-toxic-comments/server/errors.js @@ -1,15 +1,10 @@ const {APIError} = require('../../../errors'); -const ErrNoComment = new APIError('Comment must be provided', { - status: 400, -}); - const ErrToxic = new APIError('Comment is toxic', { status: 400, translation_key: 'COMMENT_IS_TOXIC', }); module.exports = { - ErrNoComment, ErrToxic, }; diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 07166c73a..991c243a0 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -1,19 +1,8 @@ const perspective = require('./perspective'); -const {ADD_COMMENT_TAG} = require('../../../perms/constants'); const {ErrToxic} = require('./errors'); const {TOXICITY_THRESHOLD} = require('./constants'); module.exports = { - Comment: { - tags: { - post(comment, input, {user}, _info, result) { - if (comment.metadata.perspective && user && user.can(ADD_COMMENT_TAG)) { - return result.concat({tag: {name: 'TOXIC', created_at: new Date()}}); - } - return result; - } - }, - }, RootMutation: { createComment: { async pre(_, {input}, _context, _info) { diff --git a/plugins/talk-plugin-toxic-comments/server/router.js b/plugins/talk-plugin-toxic-comments/server/router.js deleted file mode 100644 index 42f189d26..000000000 --- a/plugins/talk-plugin-toxic-comments/server/router.js +++ /dev/null @@ -1,34 +0,0 @@ -const perspective = require('./perspective'); -const {ErrNoComment} = require('./errors'); - -module.exports = (router) => { - - /** - * POST /api/v1/toxicity/score - * args: - * - provide the comment in the request body - */ - router.post('/api/v1/toxicity/score', async (req, res, next) => { - const apiKey = process.env.TALK_PERSPECTIVE_API_KEY; - if(!apiKey) { - throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); - } - - const {comment} = req.body; - - if(!comment) { - return next(ErrNoComment); - } - - try { - const scores = await perspective.getScores(apiKey, comment); - return res.json({ - comment, - score: scores.SEVERE_TOXICITY.summaryScore, - }); - } catch(err) { - return next(err); - } - }); - -}; From a11f149c5ada814198c2b9715c920511f78a1292 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 6 Sep 2017 22:15:57 +0700 Subject: [PATCH 13/29] Refactor --- client/talk-plugin-commentbox/CommentBox.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index 6ee2009ee..2c671946a 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -54,7 +54,7 @@ class CommentBox extends React.Component { return; } - let comment = { + let input = { asset_id: assetId, parent_id: parentId, body: this.state.body, @@ -62,10 +62,10 @@ class CommentBox extends React.Component { }; // Execute preSubmit Hooks - this.state.hooks.preSubmit.forEach((hook) => hook(comment)); + this.state.hooks.preSubmit.forEach((hook) => hook(input)); this.setState({loadingState: 'loading'}); - postComment(comment, 'comments') + postComment(input, 'comments') .then(({data}) => { this.setState({loadingState: 'success', body: ''}); const postedComment = data.createComment.comment; From e96e9c0d5c95ee8df27614799a72ec1b558a46a1 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 6 Sep 2017 09:18:22 -0600 Subject: [PATCH 14/29] Fixed old story search query --- .../Moderation/components/StorySearch.js | 6 +- .../Moderation/containers/StorySearch.js | 14 +- test/server/routes/api/assets/index.js | 162 ++++++++---------- 3 files changed, 85 insertions(+), 97 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/components/StorySearch.js b/client/coral-admin/src/routes/Moderation/components/StorySearch.js index 748f80b0f..7ac18f907 100644 --- a/client/coral-admin/src/routes/Moderation/components/StorySearch.js +++ b/client/coral-admin/src/routes/Moderation/components/StorySearch.js @@ -8,7 +8,7 @@ const StorySearch = (props) => { const { root: { - assets = [] + assets, }, data: {loading} } = props; @@ -62,7 +62,7 @@ const StorySearch = (props) => { { loading ? - : assets.map((story, i) => { + : assets.nodes.map((story, i) => { const storyOpen = story.closedAt === null || new Date(story.closedAt) > new Date(); return { }) } - {assets.length === 0 &&
    No results
    } + {assets.nodes.length === 0 &&
    No results
    } diff --git a/client/coral-admin/src/routes/Moderation/containers/StorySearch.js b/client/coral-admin/src/routes/Moderation/containers/StorySearch.js index 04472d219..04aab2d84 100644 --- a/client/coral-admin/src/routes/Moderation/containers/StorySearch.js +++ b/client/coral-admin/src/routes/Moderation/containers/StorySearch.js @@ -89,12 +89,14 @@ class StorySearchContainer extends React.Component { export const withAssetSearchQuery = withQuery(gql` query SearchStories($value: String = "") { assets(query: {value: $value, limit: 10}) { - id - title - url - created_at - closedAt - author + nodes { + id + title + url + created_at + closedAt + author + } } } `, { diff --git a/test/server/routes/api/assets/index.js b/test/server/routes/api/assets/index.js index daccd5511..855b30271 100644 --- a/test/server/routes/api/assets/index.js +++ b/test/server/routes/api/assets/index.js @@ -3,7 +3,7 @@ const passport = require('../../../passport'); const app = require('../../../../../app'); const chai = require('chai'); -chai.should(); +chai.use(require('chai-as-promised')); chai.use(require('chai-http')); const expect = chai.expect; @@ -13,139 +13,125 @@ const SettingsService = require('../../../../../services/settings'); describe('/api/v1/assets', () => { - beforeEach(() => { + beforeEach(async () => { const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['test.com']}}; - return SettingsService.init(settings).then(() => { - return AssetModel.create([ - { - url: 'https://coralproject.net/news/asset1', - title: 'Asset 1', - description: 'term1', - closedAt: Date.now() - }, - { - url: 'https://coralproject.net/news/asset2', - title: 'Asset 2', - description: 'term2', - closedAt: null - } - ]); - }); + await SettingsService.init(settings); + + await AssetModel.create([ + { + url: 'https://coralproject.net/news/asset1', + title: 'Asset 1', + description: 'term1', + closedAt: Date.now() + }, + { + url: 'https://coralproject.net/news/asset2', + title: 'Asset 2', + description: 'term2', + closedAt: null + } + ]); }); describe('#get', () => { - it('should return all assets without a search query', () => { - return chai.request(app) + it('should return all assets without a search query', async () => { + const res = await chai.request(app) .get('/api/v1/assets') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - const body = res.body; + .set(passport.inject({roles: ['ADMIN']})); - expect(body).to.have.property('count', 2); - expect(body).to.have.property('result'); + const body = res.body; - const assets = body.result; + expect(body).to.have.property('count', 2); + expect(body).to.have.property('result'); - expect(assets).to.have.length(2); - }); + const assets = body.result; + + expect(assets).to.have.length(2); }); - it('should return assets that we search for', () => { - return chai.request(app) + it('should return assets that we search for', async () => { + const res = await chai.request(app) .get('/api/v1/assets?search=term2') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - const body = res.body; + .set(passport.inject({roles: ['ADMIN']})); - expect(body).to.have.property('count', 1); - expect(body).to.have.property('result'); + const body = res.body; - const assets = body.result; + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); - expect(assets).to.have.length(1); + const assets = body.result; - const asset = assets[0]; + expect(assets).to.have.length(1); - expect(asset).to.have.property('url', 'https://coralproject.net/news/asset2'); - expect(asset).to.have.property('title', 'Asset 2'); - }); + const asset = assets[0]; + + expect(asset).to.have.property('url', 'https://coralproject.net/news/asset2'); + expect(asset).to.have.property('title', 'Asset 2'); }); - it('should not return assets that we do not search for', () => { - return chai.request(app) + it('should not return assets that we do not search for', async () => { + const res = await chai.request(app) .get('/api/v1/assets?search=term3') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - const body = res.body; + .set(passport.inject({roles: ['ADMIN']})); + const body = res.body; - expect(body).to.have.property('count', 0); - expect(body).to.have.property('result'); + expect(body).to.have.property('count', 0); + expect(body).to.have.property('result'); - expect(body.result).to.be.empty; - }); + expect(body.result).to.be.empty; }); - it('should return only closed assets', () => { - return chai.request(app) + it('should return only closed assets', async () => { + const res = await chai.request(app) .get('/api/v1/assets?filter=closed') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - const body = res.body; + .set(passport.inject({roles: ['ADMIN']})); + const body = res.body; - expect(body).to.have.property('count', 1); - expect(body).to.have.property('result'); + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); - const assets = body.result; + const assets = body.result; - expect(assets[0]).to.have.property('title', 'Asset 1'); - }); + expect(assets[0]).to.have.property('title', 'Asset 1'); }); - it('should return only opened assets', () => { - return chai.request(app) + it('should return only opened assets', async () => { + const res = await chai.request(app) .get('/api/v1/assets?filter=open') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - const body = res.body; + .set(passport.inject({roles: ['ADMIN']})); + const body = res.body; - expect(body).to.have.property('count', 1); - expect(body).to.have.property('result'); + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); - const assets = body.result; + const assets = body.result; - expect(assets[0]).to.have.property('title', 'Asset 2'); - }); + expect(assets[0]).to.have.property('title', 'Asset 2'); }); }); describe('#put', () => { - it('should close the asset', function() { + it('should close the asset', async () => { const today = Date.now(); - return AssetsService.findOrCreateByUrl('http://test.com') - .then((asset) => { - expect(asset).to.have.property('isClosed', false); - expect(asset).to.have.property('closedAt', null); + const asset = await AssetsService.findOrCreateByUrl('http://test.com'); + expect(asset).to.have.property('isClosed', false); + expect(asset).to.have.property('closedAt', null); - return chai.request(app) - .put(`/api/v1/assets/${asset.id}/status`) - .set(passport.inject({roles: ['ADMIN']})) - .send({closedAt: today}); - }) - .then((res) => { + const res = await chai.request(app) + .put(`/api/v1/assets/${asset.id}/status`) + .set(passport.inject({roles: ['ADMIN']})) + .send({closedAt: today}); - expect(res).to.have.status(204); + expect(res).to.have.status(204); - return AssetsService.findByUrl('http://test.com'); - }) - .then((asset) => { - expect(asset).to.have.property('isClosed', true); - expect(asset).to.have.property('closedAt').and.to.not.equal(null); - }); + const closedAsset = await AssetsService.findByUrl('http://test.com'); + expect(closedAsset).to.have.property('isClosed', true); + expect(closedAsset).to.have.property('closedAt').and.to.not.equal(null); }); }); From b3fe3afd4cc4ed1c3336f4d51a65159a719baa5a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 6 Sep 2017 22:20:19 +0700 Subject: [PATCH 15/29] Add todo --- plugins/talk-plugin-toxic-comments/server/hooks.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 991c243a0..8924778a7 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -14,12 +14,18 @@ module.exports = { const apiKey = require('./apiKey'); const scores = await perspective.getScores(apiKey, input.body); - if (input.checkToxicity && scores.SEVERE_TOXICITY.summaryScore > TOXICITY_THRESHOLD) { + const isToxic = scores.SEVERE_TOXICITY.summaryScore > TOXICITY_THRESHOLD; + if (input.checkToxicity && isToxic) { throw ErrToxic; } input.metadata = Object.assign({}, input.metadata, { perspective: scores, }); + + if (isToxic) { + + // TODO: Flag comment as toxic and put in ?premod?. + } }, }, }, From 9b0caa42cde71d7d4556ca32bed4ff4a460c48ff Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 6 Sep 2017 22:58:52 +0700 Subject: [PATCH 16/29] Put toxic comment into premod and add flag --- graph/mutators/comment.js | 4 +-- .../server/constants.js | 2 +- .../server/hooks.js | 32 +++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 71e341547..9142687f6 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -215,7 +215,7 @@ const filterNewComment = (context, {body, asset_id}) => { * @param {Object} [wordlist={}] the results of the wordlist scan * @return {Promise} resolves to the comment's status */ -const resolveNewCommentStatus = async (context, {asset_id, body}, wordlist = {}, settings = {}) => { +const resolveNewCommentStatus = async (context, {asset_id, body, status}, wordlist = {}, settings = {}) => { let {user} = context; // Check to see if the body is too short, if it is, then complain about it! @@ -270,7 +270,7 @@ const resolveNewCommentStatus = async (context, {asset_id, body}, wordlist = {}, } } - return moderation === 'PRE' ? 'PREMOD' : 'NONE'; + return (moderation === 'PRE' || status === 'PREMOD') ? 'PREMOD' : 'NONE'; }; /** diff --git a/plugins/talk-plugin-toxic-comments/server/constants.js b/plugins/talk-plugin-toxic-comments/server/constants.js index a335c519c..1aee300a6 100644 --- a/plugins/talk-plugin-toxic-comments/server/constants.js +++ b/plugins/talk-plugin-toxic-comments/server/constants.js @@ -1,3 +1,3 @@ module.exports = { - TOXICITY_THRESHOLD: 0.8, + TOXICITY_THRESHOLD: 0.85, }; diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 8924778a7..49f7f5d1c 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -1,6 +1,7 @@ const perspective = require('./perspective'); const {ErrToxic} = require('./errors'); const {TOXICITY_THRESHOLD} = require('./constants'); +const ActionsService = require('../../../services/actions'); module.exports = { RootMutation: { @@ -13,7 +14,10 @@ module.exports = { } const apiKey = require('./apiKey'); + + // TODO: handle timeouts. const scores = await perspective.getScores(apiKey, input.body); + const isToxic = scores.SEVERE_TOXICITY.summaryScore > TOXICITY_THRESHOLD; if (input.checkToxicity && isToxic) { throw ErrToxic; @@ -23,10 +27,34 @@ module.exports = { }); if (isToxic) { - - // TODO: Flag comment as toxic and put in ?premod?. + input.status = 'PREMOD'; } }, + async post(_, _input, _context, _info, result) { + + // Perspective is not available when running tests. + if (process.env.NODE_ENV === 'test') { + return result; + } + + const score = result.comment.metadata.perspective.SEVERE_TOXICITY.summaryScore; + const isToxic = score > TOXICITY_THRESHOLD; + if (isToxic) { + + // TODO: this is kind of fragile, we should refactor this to resolve + // all these const's that we're using like 'COMMENTS', 'FLAG' to be + // defined in a checkable schema. + await ActionsService.create({ + item_id: result.comment.id, + item_type: 'COMMENTS', + action_type: 'FLAG', + user_id: null, + group_id: 'Comment contains toxic language', + metadata: {} + }); + } + return result; + }, }, }, }; From 41cd5b320ff096e65d5c900d248dd61f31a633d8 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 03:50:49 +0700 Subject: [PATCH 17/29] Refactor & cleanup toxic-comments plugin --- .../client/components/CheckToxicityHook.js | 11 ++++++ .../client/index.js | 5 --- .../server/apiKey.js | 6 --- .../server/config.js | 5 +++ .../server/constants.js | 3 -- .../server/errors.js | 5 ++- .../server/hooks.js | 39 ++++++++----------- .../server/perspective.js | 35 +++++++++++++++-- .../server/typeDefs.graphql | 3 ++ 9 files changed, 71 insertions(+), 41 deletions(-) delete mode 100644 plugins/talk-plugin-toxic-comments/server/apiKey.js create mode 100644 plugins/talk-plugin-toxic-comments/server/config.js delete mode 100644 plugins/talk-plugin-toxic-comments/server/constants.js diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js index 2e4c66d3e..22c378435 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -1,10 +1,19 @@ import React from 'react'; +/** + * CheckToxicityHook adds hooks to the `commentBox` + * that handles checking a comment for toxicity. + */ export default class CheckToxicityHook extends React.Component { + + // checked signifies if we already sent a request with the `checkToxicity` set to true. checked = false; componentDidMount() { this.toxicityPreHook = this.props.registerHook('preSubmit', (input) => { + + // If we haven't check the toxicity yet, make sure to include `checkToxicity=true` in the mutation. + // Otherwise post comment without checking the toxicity. if (!this.checked) { input.checkToxicity = true; this.checked = true; @@ -12,6 +21,8 @@ export default class CheckToxicityHook extends React.Component { }); this.toxicityPostHook = this.props.registerHook('postSubmit', () => { + + // Reset `checked` after comment was successfully posted. this.checked = false; }); } diff --git a/plugins/talk-plugin-toxic-comments/client/index.js b/plugins/talk-plugin-toxic-comments/client/index.js index 46f8c0fc0..c364a2a00 100644 --- a/plugins/talk-plugin-toxic-comments/client/index.js +++ b/plugins/talk-plugin-toxic-comments/client/index.js @@ -1,11 +1,6 @@ import translations from './translations.yml'; import CheckToxicityHook from './components/CheckToxicityHook'; -/** - * coral-plugin-offtopic depends on coral-plugin-viewing-options - * in other to display filter and use the streamViewingOptions slot - */ - export default { translations, slots: { diff --git a/plugins/talk-plugin-toxic-comments/server/apiKey.js b/plugins/talk-plugin-toxic-comments/server/apiKey.js deleted file mode 100644 index 43e02d154..000000000 --- a/plugins/talk-plugin-toxic-comments/server/apiKey.js +++ /dev/null @@ -1,6 +0,0 @@ -const apiKey = process.env.TALK_PERSPECTIVE_API_KEY; -if(!apiKey) { - throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); -} - -module.exports = apiKey; diff --git a/plugins/talk-plugin-toxic-comments/server/config.js b/plugins/talk-plugin-toxic-comments/server/config.js new file mode 100644 index 000000000..67155cd45 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/config.js @@ -0,0 +1,5 @@ +module.exports = { + API_ENDPOINT: 'https://commentanalyzer.googleapis.com/v1alpha1', + API_KEY: process.env.NODE_ENV !== 'test' ? process.env.TALK_PERSPECTIVE_API_KEY : '', + TOXICITY_THRESHOLD: process.env.TALK_TOXICITY_THRESHOLD || 0.8, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/constants.js b/plugins/talk-plugin-toxic-comments/server/constants.js deleted file mode 100644 index 1aee300a6..000000000 --- a/plugins/talk-plugin-toxic-comments/server/constants.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - TOXICITY_THRESHOLD: 0.85, -}; diff --git a/plugins/talk-plugin-toxic-comments/server/errors.js b/plugins/talk-plugin-toxic-comments/server/errors.js index 17a2d20c6..e5c77ff78 100644 --- a/plugins/talk-plugin-toxic-comments/server/errors.js +++ b/plugins/talk-plugin-toxic-comments/server/errors.js @@ -1,5 +1,8 @@ -const {APIError} = require('../../../errors'); +const {APIError} = require('errors'); +// ErrToxic is sent during a `CreateComment` mutation where +// `input.checkToxicity` is set to true and the comment contains +// toxic language as determined by the perspective service. const ErrToxic = new APIError('Comment is toxic', { status: 400, translation_key: 'COMMENT_IS_TOXIC', diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 49f7f5d1c..32f61b905 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -1,49 +1,44 @@ -const perspective = require('./perspective'); +const {getScores, isToxic} = require('./perspective'); const {ErrToxic} = require('./errors'); -const {TOXICITY_THRESHOLD} = require('./constants'); const ActionsService = require('../../../services/actions'); +// We don't add the hooks during _test_ as the perspective API is not available. +if (process.env.NODE_ENV === 'test') { + return null; +} + module.exports = { RootMutation: { createComment: { async pre(_, {input}, _context, _info) { - // Don't call out to perspective when running tests. - if (process.env.NODE_ENV === 'test') { - return; - } - - const apiKey = require('./apiKey'); - // TODO: handle timeouts. - const scores = await perspective.getScores(apiKey, input.body); + const scores = await getScores(input.body); + const commentIsToxic = isToxic(scores); - const isToxic = scores.SEVERE_TOXICITY.summaryScore > TOXICITY_THRESHOLD; - if (input.checkToxicity && isToxic) { + if (input.checkToxicity && commentIsToxic) { throw ErrToxic; } + + // attach scores to metadata. input.metadata = Object.assign({}, input.metadata, { perspective: scores, }); - if (isToxic) { + if (commentIsToxic) { + + // TODO: this should have a different status than Premod. input.status = 'PREMOD'; } }, async post(_, _input, _context, _info, result) { - - // Perspective is not available when running tests. - if (process.env.NODE_ENV === 'test') { - return result; - } - - const score = result.comment.metadata.perspective.SEVERE_TOXICITY.summaryScore; - const isToxic = score > TOXICITY_THRESHOLD; - if (isToxic) { + if (isToxic(result.comment.metadata.perspective)) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'COMMENTS', 'FLAG' to be // defined in a checkable schema. + + // Add a flag to the comment. await ActionsService.create({ item_id: result.comment.id, item_type: 'COMMENTS', diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js index e863b4777..e66e6ed10 100644 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -1,9 +1,13 @@ const fetch = require('node-fetch'); +const {API_ENDPOINT, API_KEY, TOXICITY_THRESHOLD} = require ('./config'); -const API_ENPOINT = 'https://commentanalyzer.googleapis.com/v1alpha1'; - -async function getScores(apiKey, text) { - const response = await fetch(`${API_ENPOINT}/comments:analyze?key=${apiKey}`, { +/** + * Get scores from the perspective api + * @param {string} text to be anaylized + * @return {object} object containing toxicity scores + */ +async function getScores(text) { + const response = await fetch(`${API_ENDPOINT}/comments:analyze?key=${API_KEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -32,6 +36,29 @@ async function getScores(apiKey, text) { }; } +/** + * Get toxicity probability + * @param {object} scores as returned by `getScores` + * @return {number} toxicity probability from 0 - 1.0 + */ +function getProbability(scores) { + return scores.SEVERE_TOXICITY.summaryScore; +} + +/** + * isToxics determines if given probabilty or scores meets the toxicity threshold. + * @param {object|number} scores or probability + * @return {boolean} + */ +function isToxic(scoresOrProbability) { + const probability = typeof scoresOrProbability === 'object' + ? getProbability(scoresOrProbability) + : scoresOrProbability; + return probability > TOXICITY_THRESHOLD; +} + module.exports = { getScores, + getProbability, + isToxic, }; diff --git a/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql b/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql index 7352db00f..b4dfc8345 100644 --- a/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql +++ b/plugins/talk-plugin-toxic-comments/server/typeDefs.graphql @@ -1,4 +1,7 @@ input CreateCommentInput { + + # If true, the mutation will fail when the + # body contains toxic language. checkToxicity: Boolean } From b1b3db51a6477cfa007ca279ae3f72e7daffc0ab Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 03:59:15 +0700 Subject: [PATCH 18/29] Typo --- plugins/talk-plugin-toxic-comments/server/perspective.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js index e66e6ed10..5db5a5334 100644 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -46,7 +46,7 @@ function getProbability(scores) { } /** - * isToxics determines if given probabilty or scores meets the toxicity threshold. + * isToxic determines if given probabilty or scores meets the toxicity threshold. * @param {object|number} scores or probability * @return {boolean} */ From 43b7326db4e50f59a4511cab539e3d03b25917bb Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 15:38:39 +0700 Subject: [PATCH 19/29] Cleanup and apply pr suggestions --- .gitignore | 1 - graph/errorHandler.js | 28 ++------- graph/hooks.js | 34 +---------- graph/resolvers/root_mutation.js | 60 ++++++++----------- graph/utils.js | 46 ++++++++++++++ .../server/perspective.js | 10 ++-- 6 files changed, 85 insertions(+), 94 deletions(-) create mode 100644 graph/utils.js diff --git a/.gitignore b/.gitignore index c3c17fac3..405a0248a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,3 @@ plugins/* !plugins/talk-plugin-toxic-comments **/node_modules/* -story.html diff --git a/graph/errorHandler.js b/graph/errorHandler.js index 066ae3319..d7176f16a 100644 --- a/graph/errorHandler.js +++ b/graph/errorHandler.js @@ -1,28 +1,8 @@ -const { - GraphQLObjectType, - GraphQLInterfaceType -} = require('graphql'); +const {forEachField} = require('./utils'); const {maskErrors} = require('graphql-errors'); const errors = require('../errors'); const {Error: {ValidationError}} = require('mongoose'); -// This function is pretty much copied verbatim from the graphql-tools repo: -// https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194 -const forEachField = (schema, fn) => { - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach((typeName) => { - const type = typeMap[typeName]; - - if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) { - const fields = type.getFields(); - Object.keys(fields).forEach((fieldName) => { - const field = fields[fieldName]; - fn(field, typeName, fieldName); - }); - } - }); -}; - // If an APIError happens in a mutation, then respond with `{errors: Array}` // according to the schema. const decorateWithMutationErrorHandler = (field) => { @@ -47,7 +27,11 @@ const decorateWithMutationErrorHandler = (field) => { }; }; -// Masks errors during production and handle mutation errors inside the schema. +/** + * Masks errors during production and handle mutation errors inside the schema. + * @param {GraphQLSchema} schema the schema to decorate + * @return {void} + */ const decorateWithErrorHandler = (schema) => { forEachField(schema, (field, typeName) => { diff --git a/graph/hooks.js b/graph/hooks.js index 7f6be7feb..3a34be0bb 100644 --- a/graph/hooks.js +++ b/graph/hooks.js @@ -1,7 +1,4 @@ -const { - GraphQLObjectType, - GraphQLInterfaceType -} = require('graphql'); +const {forEachField} = require('./utils'); const debug = require('debug')('talk:graph:schema'); const Joi = require('joi'); @@ -26,33 +23,6 @@ const defaultResolveFn = (source, args, context, {fieldName}) => { } }; -// This function is pretty much copied verbatim from the graphql-tools repo: -// https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194 -// With the small alteration that we look for the `resolveType` function on the -// schema so we can wrap post hooks around it to provide additional resolve -// points. -const forEachField = (schema, fn) => { - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach((typeName) => { - const type = typeMap[typeName]; - - if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) { - - // Here we capture the change to extract the resolve type. We pass this - // with the `isResolveType = true` to introduce the specific beheviour. - if ('resolveType' in type) { - fn(type, typeName, '__resolveType', true); - } - - const fields = type.getFields(); - Object.keys(fields).forEach((fieldName) => { - const field = fields[fieldName]; - fn(field, typeName, fieldName); - }); - } - }); -}; - /** * Decorates the field with the post resolvers (if available) and attaches a * default type in the form of `Default${typeName}`. @@ -239,6 +209,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa return result; }, result); }; +}, { + includeResolveType: true, }); module.exports = { diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 437d2ab5b..21e6c4787 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -1,43 +1,35 @@ const RootMutation = { - async createComment(_, {input}, {mutators: {Comment}}) { - return { - comment: await Comment.create(input), - }; - }, - async editComment(_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) { - return { - comment: await Comment.edit({id, asset_id, edit: {body}}), - }; - }, - async createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) { - return { - flag: Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}), - }; - }, - async createDontAgree(_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) { - return { - dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}), - }; - }, - async deleteAction(_, {id}, {mutators: {Action}}) { + createComment: async (_, {input}, {mutators: {Comment}}) => ({ + comment: await Comment.create(input), + }), + editComment: async (_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) => ({ + comment: await Comment.edit({id, asset_id, edit: {body}}), + }), + createFlag: async (_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) => ({ + flag: Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}), + }), + createDontAgree: async (_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) => ({ + dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}), + }), + deleteAction: async (_, {id}, {mutators: {Action}}) => { await Action.delete({id}); }, - async setUserStatus(_, {id, status}, {mutators: {User}}) { + setUserStatus: async (_, {id, status}, {mutators: {User}}) => { await User.setUserStatus({id, status}); }, - async suspendUser(_, {input: {id, message, until}}, {mutators: {User}}) { + suspendUser: async (_, {input: {id, message, until}}, {mutators: {User}}) => { await User.suspendUser({id, message, until}); }, - async rejectUsername(_, {input: {id, message}}, {mutators: {User}}) { + rejectUsername: async (_, {input: {id, message}}, {mutators: {User}}) => { await User.rejectUsername({id, message}); }, - async ignoreUser(_, {id}, {mutators: {User}}) { + ignoreUser: async (_, {id}, {mutators: {User}}) => { await User.ignoreUser({id}); }, - async stopIgnoringUser(_, {id}, {mutators: {User}}) { + stopIgnoringUser: async (_, {id}, {mutators: {User}}) => { await User.stopIgnoringUser({id}); }, - async setCommentStatus(_, {id, status}, {mutators: {Comment}, pubsub}) { + setCommentStatus: async (_, {id, status}, {mutators: {Comment}, pubsub}) => { const comment = await Comment.setStatus({id, status}); if (status === 'ACCEPTED') { @@ -49,18 +41,16 @@ const RootMutation = { pubsub.publish('commentRejected', comment); } }, - async addTag(_, {tag}, {mutators: {Tag}}) { + addTag: async (_, {tag}, {mutators: {Tag}}) => { await Tag.add(tag); }, - async removeTag(_, {tag}, {mutators: {Tag}}) { + removeTag: async (_, {tag}, {mutators: {Tag}}) => { await Tag.remove(tag); }, - async createToken(_, {input}, {mutators: {Token}}) { - return { - token: await Token.create(input), - }; - }, - async revokeToken(_, {input}, {mutators: {Token}}) { + createToken: async (_, {input}, {mutators: {Token}}) => ({ + token: await Token.create(input), + }), + revokeToken: async (_, {input}, {mutators: {Token}}) => { await Token.revoke(input); } }; diff --git a/graph/utils.js b/graph/utils.js new file mode 100644 index 000000000..d170a2d83 --- /dev/null +++ b/graph/utils.js @@ -0,0 +1,46 @@ +const { + GraphQLObjectType, + GraphQLInterfaceType +} = require('graphql'); + +/** + * Iterates over each field in a schema. + * This function is pretty much copied verbatim from the graphql-tools repo: + * https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194 + * With the small alteration that we look for the `resolveType` function on the + * schema so we can wrap post hooks around it to provide additional resolve + * points. (Only when `options.includeResolveType` is set to true). + * + * @param {GraphQLSchema} schema the schema to iterate over + * @param {function} fn callback to call on each field + * @param {object} [options] options + * @param {boolean} [options.includeResolveType] include resolveType during iteration + * @return {void} + */ +const forEachField = (schema, fn, options = {}) => { + const {includeResolveType = false} = options; + + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach((typeName) => { + const type = typeMap[typeName]; + + if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) { + + // Here we capture the change to extract the resolve type. We pass this + // with the `isResolveType = true` to introduce the specific beheviour. + if (includeResolveType && 'resolveType' in type) { + fn(type, typeName, '__resolveType', true); + } + + const fields = type.getFields(); + Object.keys(fields).forEach((fieldName) => { + const field = fields[fieldName]; + fn(field, typeName, fieldName); + }); + } + }); +}; + +module.exports = { + forEachField, +}; diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js index 5db5a5334..0b5cd6b05 100644 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -3,8 +3,8 @@ const {API_ENDPOINT, API_KEY, TOXICITY_THRESHOLD} = require ('./config'); /** * Get scores from the perspective api - * @param {string} text to be anaylized - * @return {object} object containing toxicity scores + * @param {string} text text to be anaylized + * @return {object} object containing toxicity scores */ async function getScores(text) { const response = await fetch(`${API_ENDPOINT}/comments:analyze?key=${API_KEY}`, { @@ -38,8 +38,8 @@ async function getScores(text) { /** * Get toxicity probability - * @param {object} scores as returned by `getScores` - * @return {number} toxicity probability from 0 - 1.0 + * @param {object} scores scores as returned by `getScores` + * @return {number} toxicity probability from 0 - 1.0 */ function getProbability(scores) { return scores.SEVERE_TOXICITY.summaryScore; @@ -47,7 +47,7 @@ function getProbability(scores) { /** * isToxic determines if given probabilty or scores meets the toxicity threshold. - * @param {object|number} scores or probability + * @param {object|number} scoresOrProbability scores or probability * @return {boolean} */ function isToxic(scoresOrProbability) { From c95c20c9ad19d7299b0089c6378482d4fa1830c4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 15:41:00 +0700 Subject: [PATCH 20/29] Don't put in default plugins --- plugins.default.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins.default.json b/plugins.default.json index 46762865f..950cec229 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -4,8 +4,7 @@ "talk-plugin-respect", "talk-plugin-offtopic", "talk-plugin-facebook-auth", - "talk-plugin-featured-comments", - "talk-plugin-toxic-comments" + "talk-plugin-featured-comments" ], "client": [ "talk-plugin-respect", @@ -21,7 +20,6 @@ "talk-plugin-sort-most-replied", "talk-plugin-author-menu", "talk-plugin-member-since", - "talk-plugin-ignore-user", - "talk-plugin-toxic-comments" + "talk-plugin-ignore-user" ] } From a78be6874b5e8c9d1e9a9c37ed0be0c9c8e2df25 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 15:49:51 +0700 Subject: [PATCH 21/29] Remove response helpers --- graph/helpers/response.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 graph/helpers/response.js diff --git a/graph/helpers/response.js b/graph/helpers/response.js deleted file mode 100644 index 1db1e9b89..000000000 --- a/graph/helpers/response.js +++ /dev/null @@ -1,34 +0,0 @@ -const errors = require('../../errors'); -const {Error: {ValidationError}} = require('mongoose'); - -/** - * Wraps up a promise or value to return an object with the resolution of the promise - * keyed at `key` or an error caught at `errors`. - */ - -const wrapResponse = (key) => async (promise) => { - try { - let value = await promise; - - let res = {}; - if (key) { - res[key] = value; - } - - return res; - } catch (err) { - if (err instanceof errors.APIError) { - return { - errors: [err] - }; - } else if (err instanceof ValidationError) { - - // TODO: wrap this with one of our internal errors. - throw err; - } - - throw err; - } -}; - -module.exports = wrapResponse; From d8a956d09c8cc5811bda0b396b4da90c2eb6a42d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 16:04:01 +0700 Subject: [PATCH 22/29] Remove dependency on wrapResponse --- client/coral-framework/utils/index.js | 2 +- plugin-api/beta/server/getReactionConfig.js | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 4424e62c5..037c1b99f 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -109,7 +109,7 @@ export function mergeDocuments(documents) { export function getResponseErrors(mutationResult) { const result = []; Object.keys(mutationResult.data).forEach((response) => { - const errors = mutationResult.data[response].errors; + const errors = mutationResult.data[response] && mutationResult.data[response].errors; if (errors && errors.length) { result.push(...errors); } diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index 02878e010..481990c16 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -1,4 +1,3 @@ -const wrapResponse = require('../../../graph/helpers/response'); const {SEARCH_OTHER_USERS} = require('../../../perms/constants'); const errors = require('../../../errors'); const pluralize = require('pluralize'); @@ -90,10 +89,6 @@ function getReactionConfig(reaction) { } type Delete${Reaction}ActionResponse implements Response { - - # The ${reaction} that was created. - ${reaction}: ${Reaction}Action - # An array of errors relating to the mutation that occurred. errors: [UserError!] } @@ -101,7 +96,7 @@ function getReactionConfig(reaction) { type RootMutation { # Creates a ${reaction} on an entity. - create${Reaction}Action(input: Create${Reaction}ActionInput!): Create${Reaction}ActionResponse + create${Reaction}Action(input: Create${Reaction}ActionInput!): Create${Reaction}ActionResponse! delete${Reaction}Action(input: Delete${Reaction}ActionInput!): Delete${Reaction}ActionResponse } @@ -160,7 +155,7 @@ function getReactionConfig(reaction) { } }, RootMutation: { - [`create${Reaction}Action`]: (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => wrapResponse(reaction)(async () => { + [`create${Reaction}Action`]: async (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => { const comment = await Comments.get.load(item_id); let action; @@ -180,9 +175,11 @@ function getReactionConfig(reaction) { pubsub.publish(`${reaction}ActionCreated`, {action, comment}); } - return action; - }), - [`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => wrapResponse(reaction)(async () => { + return { + [reaction]: action, + }; + }, + [`delete${Reaction}Action`]: async (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => { const action = await Action.delete({id}); if (!action) { return null; @@ -194,9 +191,7 @@ function getReactionConfig(reaction) { // The comment is needed to allow better filtering e.g. by asset_id. pubsub.publish(`${reaction}ActionDeleted`, {action, comment}); } - - return action; - }) + }, }, }, hooks: { From f82a0c80bfc04b388be3398bbbbd0647b4e228ee Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 7 Sep 2017 19:37:45 +0700 Subject: [PATCH 23/29] Handle timeout and failure of perspective api --- .../server/config.js | 15 ++++++++--- .../server/hooks.js | 18 ++++++++++--- .../server/perspective.js | 27 ++++++++++++++++--- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/plugins/talk-plugin-toxic-comments/server/config.js b/plugins/talk-plugin-toxic-comments/server/config.js index 67155cd45..bc7ddf138 100644 --- a/plugins/talk-plugin-toxic-comments/server/config.js +++ b/plugins/talk-plugin-toxic-comments/server/config.js @@ -1,5 +1,12 @@ -module.exports = { - API_ENDPOINT: 'https://commentanalyzer.googleapis.com/v1alpha1', - API_KEY: process.env.NODE_ENV !== 'test' ? process.env.TALK_PERSPECTIVE_API_KEY : '', - TOXICITY_THRESHOLD: process.env.TALK_TOXICITY_THRESHOLD || 0.8, +const config = { + API_ENDPOINT: process.env.TALK_PERSPECTIVE_API_ENDPOINT || 'https://commentanalyzer.googleapis.com/v1alpha1', + API_KEY: process.env.TALK_PERSPECTIVE_API_KEY, + THRESHOLD: process.env.TALK_TOXICITY_THRESHOLD || 0.8, + API_TIMEOUT: process.env.TALK_PERSPECTIVE_TIMEOUT || 300, }; + +if (process.env.NODE_ENV !== 'test' && !config.API_KEY) { + throw new Error('Please set the TALK_PERSPECTIVE_API_KEY environment variable to use the toxic-comments plugin. Visit https://www.perspectiveapi.com/ to request API access.'); +} + +module.exports = config; diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 32f61b905..7fd138641 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -12,8 +12,19 @@ module.exports = { createComment: { async pre(_, {input}, _context, _info) { - // TODO: handle timeouts. - const scores = await getScores(input.body); + let scores; + + // Try getting scores. + try { + scores = await getScores(input.body); + } + catch(err) { + + // Warn and let mutation pass. + console.trace(err); + return; + } + const commentIsToxic = isToxic(scores); if (input.checkToxicity && commentIsToxic) { @@ -32,7 +43,8 @@ module.exports = { } }, async post(_, _input, _context, _info, result) { - if (isToxic(result.comment.metadata.perspective)) { + const metadata = result.comment.metadata; + if (metadata.perspective && isToxic(metadata.perspective)) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'COMMENTS', 'FLAG' to be diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js index 0b5cd6b05..fba81b3d2 100644 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -1,5 +1,5 @@ const fetch = require('node-fetch'); -const {API_ENDPOINT, API_KEY, TOXICITY_THRESHOLD} = require ('./config'); +const {API_ENDPOINT, API_KEY, THRESHOLD, API_TIMEOUT} = require('./config'); /** * Get scores from the perspective api @@ -12,6 +12,7 @@ async function getScores(text) { headers: { 'Content-Type': 'application/json', }, + timeout: API_TIMEOUT, body: JSON.stringify({ comment: { text, @@ -54,11 +55,31 @@ function isToxic(scoresOrProbability) { const probability = typeof scoresOrProbability === 'object' ? getProbability(scoresOrProbability) : scoresOrProbability; - return probability > TOXICITY_THRESHOLD; + return probability > THRESHOLD; +} + +/** + * maskKeyInError is a decorator that calls fn and masks the + * API_KEY in errors before throwing. + * @param {function} fn Function that returns a Promise + * @return {function} decorated function + */ +function maskKeyInError(fn) { + return async (...args) => { + try { + return await fn(...args); + } + catch(err) { + if (err.message) { + err.message = err.message.replace(API_KEY, '***'); + } + throw err; + } + }; } module.exports = { - getScores, + getScores: maskKeyInError(getScores), getProbability, isToxic, }; From 96e37ac7254b3d547f2dd9bbd2011d24ab794c51 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 7 Sep 2017 10:58:58 -0600 Subject: [PATCH 24/29] moved config loading to bootstrap --- client/coral-framework/constants/config.js | 3 -- client/coral-framework/services/bootstrap.js | 39 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) delete mode 100644 client/coral-framework/constants/config.js diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js deleted file mode 100644 index 81eab50e2..000000000 --- a/client/coral-framework/constants/config.js +++ /dev/null @@ -1,3 +0,0 @@ -const configElement = document.querySelector('#data'); - -export const CONFIG = JSON.parse(configElement ? configElement.textContent : '{}'); diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index eed8313d4..a3ae5b18e 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -8,7 +8,6 @@ import thunk from 'redux-thunk'; import {loadTranslations} from './i18n'; import bowser from 'bowser'; import {BASE_PATH} from 'coral-framework/constants/url'; -import {CONFIG} from 'coral-framework/constants/config'; import {createPluginsService} from './plugins'; import {createNotificationService} from './notification'; import {createGraphQLRegistry} from './graphqlRegistry'; @@ -16,6 +15,25 @@ import globalFragments from 'coral-framework/graphql/fragments'; import {createStorage} from 'coral-framework/services/storage'; import {createHistory} from 'coral-framework/services/history'; +/** + * getStaticConfiguration will return a singleton of the static configuration + * object provided via a JSON DOM element. + */ +const getStaticConfiguration = (() => { + let staticConfiguration = null; + return () => { + if (staticConfiguration != null) { + return staticConfiguration; + } + + const configElement = document.querySelector('#data'); + + staticConfiguration = JSON.parse(configElement ? configElement.textContent : '{}'); + + return staticConfiguration; + }; +})(); + /** * getAuthToken returns the active auth token or null * Note: this method does not have access to the cookie based token used by @@ -50,7 +68,6 @@ const getAuthToken = (store, storage) => { * @return {Object} context */ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtension = {}, notification} = {}) { - const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; const eventEmitter = new EventEmitter({wildcard: true}); const storage = createStorage(); const history = createHistory(BASE_PATH); @@ -64,13 +81,28 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi // TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT. return getAuthToken(store, storage); }; + const rest = createRestClient({ uri: `${BASE_PATH}api/v1`, token, }); + + // Try to get an overrided liveUri from the static config, if none is found, + // build it. + let {LIVE_URI: liveUri} = getStaticConfiguration(); + if (liveUri == null) { + + // The protocol must match the origin protocol, secure/insecure. + const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; + + // Compose the live url from this protocol, the current host + base path + // with the live path appended to it. + liveUri = `${protocol}://${location.host}${BASE_PATH}api/v1/live`; + } + const client = createClient({ uri: `${BASE_PATH}api/v1/graph/ql`, - liveUri: CONFIG.LIVE_URI || `${protocol}://${location.host}${BASE_PATH}api/v1/live`, + liveUri, token, }); const plugins = createPluginsService(pluginsConfig); @@ -80,6 +112,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi // Use default notification service (pym based) notification = createNotificationService(pym); } + const context = { client, pym, From d57c4b253a57a459590dca307549d3275b15ec70 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 7 Sep 2017 13:25:15 -0600 Subject: [PATCH 25/29] fixes #924 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3d054e0d6..71d4483e6 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.5.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", + "private": true, "scripts": { "postinstall": "./bin/cli plugins reconcile --skip-remote", "start": "./bin/cli serve -j -w", From 2b6a5bfe6e5012de32fc5a79937939c051fca28b Mon Sep 17 00:00:00 2001 From: Peter deHaan Date: Thu, 7 Sep 2017 12:46:52 -0700 Subject: [PATCH 26/29] Tweak ESLint config to check .js files --- .eslintrc.json | 34 ++++++------------- .../Community/components/FlaggedUser.js | 2 +- .../src/components/StreamTabPanel.js | 4 +-- graph/loaders/users.js | 2 +- package.json | 4 +-- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 237650932..8ca153cbc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,9 @@ "es6": true, "node": true }, - "extends": "eslint:recommended", + "extends": [ + "eslint:recommended" + ], "parserOptions": { "ecmaVersion": 2017 }, @@ -12,9 +14,7 @@ "json" ], "rules": { - "indent": ["error", - 2 - ], + "indent": ["error", 2], "no-console": "off", "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], @@ -29,7 +29,7 @@ "no-global-assign": "error", "no-implied-eval": "error", "lines-around-comment": ["warn", {"beforeLineComment": true}], - "spaced-comment": ["warn", "always", { "line": { "exceptions": ["-", "="] } }], + "spaced-comment": ["warn", "always", {"line": {"exceptions": ["-", "="]}}], "no-script-url": "error", "no-throw-literal": "error", "yoda": "warn", @@ -41,32 +41,20 @@ "object-curly-spacing": "warn", "space-infix-ops": ["error"], "space-in-parens": ["error", "never"], - "space-unary-ops": ["error", { - "words": true, - "nonwords": false - }], + "space-unary-ops": ["error", {"words": true, "nonwords": false}], "no-const-assign": "error", "no-duplicate-imports": "error", "prefer-template": "warn", - "comma-spacing": ["error", { - "after": true - }], + "comma-spacing": ["error", {"after": true}], "no-var": "error", "no-lonely-if": "error", "curly": "error", - "no-unused-vars": ["error", { - "argsIgnorePattern": "^_|next", - "varsIgnorePattern": "^_" - }], - "no-multiple-empty-lines": ["error", { - "max": 1 - }], - "newline-per-chained-call": ["error", { - "ignoreChainWithDepth": 2 - }], + "no-unused-vars": ["error", {"argsIgnorePattern": "^_|next", "varsIgnorePattern": "^_"}], + "no-multiple-empty-lines": ["error", {"max": 1}], + "newline-per-chained-call": ["error", {"ignoreChainWithDepth": 2}], "promise/no-return-wrap": "error", "promise/param-names": "error", - "promise/catch-or-return": "error", + "promise/catch-or-return": "warn", "promise/no-native": "off", "promise/no-nesting": "warn", "promise/no-promise-in-callback": "warn", diff --git a/client/coral-admin/src/routes/Community/components/FlaggedUser.js b/client/coral-admin/src/routes/Community/components/FlaggedUser.js index 5b2238592..81d7790fe 100644 --- a/client/coral-admin/src/routes/Community/components/FlaggedUser.js +++ b/client/coral-admin/src/routes/Community/components/FlaggedUser.js @@ -85,7 +85,7 @@ class User extends React.Component { {t('community.flags')}({ user.actions.length }) : - { user.action_summaries.map( + { user.action_summaries.map( (action, i) => { return {shortReasons[action.reason]} ({action.count}) diff --git a/client/coral-embed-stream/src/components/StreamTabPanel.js b/client/coral-embed-stream/src/components/StreamTabPanel.js index a511ac555..50f54c2a0 100644 --- a/client/coral-embed-stream/src/components/StreamTabPanel.js +++ b/client/coral-embed-stream/src/components/StreamTabPanel.js @@ -15,8 +15,8 @@ class StreamTabPanel extends React.Component { {loading ?
    : - {tabPanes} - + {tabPanes} + } ); diff --git a/graph/loaders/users.js b/graph/loaders/users.js index ac3493479..0cceaf33f 100644 --- a/graph/loaders/users.js +++ b/graph/loaders/users.js @@ -27,7 +27,7 @@ const genUserByIDs = async (context, ids) => { * @param {Object} context graph context * @param {Object} query query terms to apply to the users query */ -const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => { +const getUsersByQuery = async ({loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => { let query = UserModel.find(); diff --git a/package.json b/package.json index 3d054e0d6..afb457d35 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "build": "WEBPACK=TRUE NODE_ENV=production webpack -p --config webpack.config.js --bail", "prebuild-watch": "yarn generate-introspection", "build-watch": "WEBPACK=TRUE NODE_ENV=development webpack --progress --config webpack.config.js --watch", - "lint": "eslint --ext .json bin/* .", - "lint-fix": "eslint bin/* . --fix", + "lint": "eslint --ext=.js --ext=.json bin/* .", + "lint-fix": "npm run lint -- --fix", "test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", "test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec", "heroku-postbuild": "./bin/cli plugins reconcile && yarn build", From 2b91bcffda624f1a56c57885f99b368a409ad81c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 7 Sep 2017 13:49:22 -0600 Subject: [PATCH 27/29] fixes #923 --- client/coral-admin/src/actions/install.js | 26 +++++++++---------- .../coral-admin/src/components/UserDetail.js | 24 ++++++++++++----- .../coral-admin/src/containers/UserDetail.js | 11 +++++--- .../Community/components/FlaggedUser.js | 2 +- .../components/RejectUsernameDialog.js | 14 ++++++---- .../src/routes/Stories/components/Stories.js | 17 +++++++----- .../src/components/StreamTabPanel.js | 4 +-- graph/loaders/users.js | 7 +++++ package.json | 2 +- 9 files changed, 69 insertions(+), 38 deletions(-) diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index d6b27115a..02d562d65 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -128,18 +128,18 @@ const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST}); const checkInstallSuccess = (installed) => ({type: actions.CHECK_INSTALL_SUCCESS, installed}); const checkInstallFailure = (error) => ({type: actions.CHECK_INSTALL_FAILURE, error}); -export const checkInstall = (next) => (dispatch, _, {rest}) => { +export const checkInstall = (next) => async (dispatch, _, {rest}) => { dispatch(checkInstallRequest()); - rest('/setup') - .then(({installed}) => { - dispatch(checkInstallSuccess(installed)); - if (installed) { - next(); - } - }) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch(checkInstallFailure(errorMessage)); - }); + + try { + const {installed} = await rest('/setup'); + dispatch(checkInstallSuccess(installed)); + if (installed) { + next(); + } + } catch (error) { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(checkInstallFailure(errorMessage)); + } }; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index a7eadbac3..fd44d5f17 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -28,16 +28,26 @@ export default class UserDetail extends React.Component { bulkReject: PropTypes.func.isRequired, } - rejectThenReload = (info) => { - this.props.rejectComment(info).then(() => { + rejectThenReload = async (info) => { + try { + await this.props.rejectComment(info); this.props.data.refetch(); - }); + } catch (err) { + + // TODO: handle error. + console.error(err); + } } - acceptThenReload = (info) => { - this.props.acceptComment(info).then(() => { + acceptThenReload = async (info) => { + try { + await this.props.acceptComment(info); this.props.data.refetch(); - }); + } catch (err) { + + // TODO: handle error. + console.error(err); + } } showAll = () => { @@ -133,7 +143,7 @@ export default class UserDetail extends React.Component {
    diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 0eb81a9a1..5effb2b84 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -36,14 +36,19 @@ class UserDetailContainer extends React.Component { isLoadingMore = false; // status can be 'ACCEPTED' or 'REJECTED' - bulkSetCommentStatus = (status) => { + bulkSetCommentStatus = async (status) => { const changes = this.props.selectedCommentIds.map((commentId) => { return this.props.setCommentStatus({commentId, status}); }); - Promise.all(changes).then(() => { + try { + await Promise.all(changes); this.props.clearUserDetailSelections(); // un-select everything - }); + } catch (err) { + + // TODO: handle error. + console.error(err); + } } bulkReject = () => { diff --git a/client/coral-admin/src/routes/Community/components/FlaggedUser.js b/client/coral-admin/src/routes/Community/components/FlaggedUser.js index 5b2238592..81d7790fe 100644 --- a/client/coral-admin/src/routes/Community/components/FlaggedUser.js +++ b/client/coral-admin/src/routes/Community/components/FlaggedUser.js @@ -85,7 +85,7 @@ class User extends React.Component { {t('community.flags')}({ user.actions.length }) : - { user.action_summaries.map( + { user.action_summaries.map( (action, i) => { return {shortReasons[action.reason]} ({action.count}) diff --git a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js index 128946b61..4932021bd 100644 --- a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js +++ b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js @@ -48,11 +48,15 @@ class RejectUsernameDialog extends Component { const cancel = this.props.handleClose; const next = () => this.setState({stage: stage + 1}); - const suspend = () => { - rejectUsername({id: user.user.id, message: this.state.email}) - .then(() => { - this.props.handleClose(); - }); + const suspend = async () => { + try { + await rejectUsername({id: user.user.id, message: this.state.email}); + this.props.handleClose(); + } catch (err) { + + // TODO: handle error. + console.error(err); + } }; const suspendModalActions = [ diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 5b931e6fc..d57b8f779 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -50,17 +50,22 @@ export default class Stories extends Component { return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; } - onStatusClick = (closeStream, id, statusMenuOpen) => () => { + onStatusClick = (closeStream, id, statusMenuOpen) => async () => { if (statusMenuOpen) { this.setState((prev) => { prev.statusMenus[id] = false; return prev; }); - this.props.updateAssetState(id, closeStream ? Date.now() : null) - .then(() => { - const {search, sort, filter, page} = this.state; - this.props.fetchAssets(page, limit, search, sort, filter); - }); + + try { + await this.props.updateAssetState(id, closeStream ? Date.now() : null); + const {search, sort, filter, page} = this.state; + this.props.fetchAssets(page, limit, search, sort, filter); + } catch (err) { + + // TODO: handle error. + console.error(err); + } } else { this.setState((prev) => { prev.statusMenus[id] = true; diff --git a/client/coral-embed-stream/src/components/StreamTabPanel.js b/client/coral-embed-stream/src/components/StreamTabPanel.js index a511ac555..50f54c2a0 100644 --- a/client/coral-embed-stream/src/components/StreamTabPanel.js +++ b/client/coral-embed-stream/src/components/StreamTabPanel.js @@ -15,8 +15,8 @@ class StreamTabPanel extends React.Component { {loading ?
    : - {tabPanes} - + {tabPanes} + } ); diff --git a/graph/loaders/users.js b/graph/loaders/users.js index ac3493479..c453542a4 100644 --- a/graph/loaders/users.js +++ b/graph/loaders/users.js @@ -3,6 +3,10 @@ const DataLoader = require('dataloader'); const util = require('./util'); const union = require('lodash/union'); +const { + SEARCH_OTHER_USERS, +} = require('../../perms/constants'); + const UsersService = require('../../services/users'); const UserModel = require('../../models/user'); @@ -28,6 +32,9 @@ const genUserByIDs = async (context, ids) => { * @param {Object} query query terms to apply to the users query */ const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => { + if (!user || !user.can(SEARCH_OTHER_USERS)) { + return null; + } let query = UserModel.find(); diff --git a/package.json b/package.json index 3d054e0d6..bc1125b65 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "WEBPACK=TRUE NODE_ENV=production webpack -p --config webpack.config.js --bail", "prebuild-watch": "yarn generate-introspection", "build-watch": "WEBPACK=TRUE NODE_ENV=development webpack --progress --config webpack.config.js --watch", - "lint": "eslint --ext .json bin/* .", + "lint": "eslint --ext .js,.json bin/* .", "lint-fix": "eslint bin/* . --fix", "test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", "test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec", From 56bd3f23c7bd0f473a800405a43f8c0bd401d792 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 7 Sep 2017 13:53:37 -0600 Subject: [PATCH 28/29] fixed broken query --- graph/loaders/users.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/graph/loaders/users.js b/graph/loaders/users.js index c453542a4..b943c60ed 100644 --- a/graph/loaders/users.js +++ b/graph/loaders/users.js @@ -32,15 +32,23 @@ const genUserByIDs = async (context, ids) => { * @param {Object} query query terms to apply to the users query */ const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => { - if (!user || !user.can(SEARCH_OTHER_USERS)) { - return null; - } - let query = UserModel.find(); - if (action_type) { - const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'}); - ids = ids ? union(ids, userIds) : userIds; + if (action_type || statuses) { + if (!user || !user.can(SEARCH_OTHER_USERS)) { + return null; + } + + if (statuses) { + query = query.where({ + status: { + $in: statuses + } + }); + } else { + const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'}); + ids = ids ? union(ids, userIds) : userIds; + } } if (ids) { @@ -51,14 +59,6 @@ const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, }); } - if (statuses) { - query = query.where({ - status: { - $in: statuses - } - }); - } - if (cursor) { if (sortOrder === 'DESC') { query = query.where({ From f69703a8ba59160c909220bae707afc2d799675f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 8 Sep 2017 00:47:08 -0600 Subject: [PATCH 29/29] fixed missing --- .../coral-admin/src/routes/Install/containers/Install.js | 3 ++- .../src/components/EditableCommentContent.js | 4 ++-- client/coral-embed-stream/src/components/Embed.js | 7 ++++--- client/coral-embed-stream/src/components/Stream.js | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/routes/Install/containers/Install.js b/client/coral-admin/src/routes/Install/containers/Install.js index 442127e47..77486a9c1 100644 --- a/client/coral-admin/src/routes/Install/containers/Install.js +++ b/client/coral-admin/src/routes/Install/containers/Install.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import {compose} from 'react-apollo'; @@ -31,7 +32,7 @@ class InstallContainer extends Component { } InstallContainer.contextTypes = { - router: React.PropTypes.object + router: PropTypes.object }; const mapStateToProps = (state) => ({ diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js index a2433ab1c..c256616f0 100644 --- a/client/coral-embed-stream/src/components/EditableCommentContent.js +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -39,10 +39,10 @@ export class EditableCommentContent extends React.Component { maxCharCount: PropTypes.number, // edit a comment, passed {{ body }} - editComment: React.PropTypes.func, + editComment: PropTypes.func, // called when editing should be stopped - stopEditing: React.PropTypes.func, + stopEditing: PropTypes.func, } constructor(props) { diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index a74028627..86d36d47e 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Stream from '../containers/Stream'; import Slot from 'coral-framework/components/Slot'; import {can} from 'coral-framework/services/perms'; @@ -90,8 +91,8 @@ export default class Embed extends React.Component { } Embed.propTypes = { - data: React.PropTypes.shape({ - loading: React.PropTypes.bool, - error: React.PropTypes.object + data: PropTypes.shape({ + loading: PropTypes.bool, + error: PropTypes.object }).isRequired }; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 89e7b13ae..43176353f 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -308,7 +308,7 @@ Stream.propTypes = { postComment: PropTypes.func.isRequired, // edit a comment, passed (id, asset_id, { body }) - editComment: React.PropTypes.func + editComment: PropTypes.func }; export default Stream;