From 48875294c8852c6e75e2462cc7649dc399d24511 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 5 Sep 2017 13:57:35 -0600 Subject: [PATCH 1/7] 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 2/7] 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 96e37ac7254b3d547f2dd9bbd2011d24ab794c51 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 7 Sep 2017 10:58:58 -0600 Subject: [PATCH 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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({