From 86385e0d867d1a5e063562b4f5f035bb2a373804 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 11 Apr 2018 19:02:38 -0600 Subject: [PATCH 1/9] Jest for server - Introduced the Jest testing framework into our server side code so plugins can now have tests that run --- .eslintrc.json | 3 + client/jest.config.js | 36 + jest.config.js | 42 +- package.json | 11 +- plugin-api/beta/server/getReactionConfig.js | 20 +- .../beta/server/getReactionConfig.spec.js | 51 + plugins/talk-plugin-akismet/index.js | 129 +-- .../{ => server}/config.js | 0 .../{ => server}/errors.js | 0 plugins/talk-plugin-akismet/server/hooks.js | 107 ++ .../talk-plugin-akismet/server/resolvers.js | 7 + .../server/resolvers.spec.js | 14 + .../server/typeDefs.graphql | 10 + .../talk-plugin-akismet/server/typeDefs.js | 7 + .../server/__mocks__/perspective.js | 11 + .../server/hooks.js | 7 +- .../server/hooks.spec.js | 31 + services/logging.js | 7 +- services/mongoose.js | 12 +- test/setupJest.js | 21 + yarn.lock | 1020 +++++++---------- 21 files changed, 774 insertions(+), 772 deletions(-) create mode 100644 client/jest.config.js create mode 100644 plugin-api/beta/server/getReactionConfig.spec.js rename plugins/talk-plugin-akismet/{ => server}/config.js (100%) rename plugins/talk-plugin-akismet/{ => server}/errors.js (100%) create mode 100644 plugins/talk-plugin-akismet/server/hooks.js create mode 100644 plugins/talk-plugin-akismet/server/resolvers.js create mode 100644 plugins/talk-plugin-akismet/server/resolvers.spec.js create mode 100644 plugins/talk-plugin-akismet/server/typeDefs.graphql create mode 100644 plugins/talk-plugin-akismet/server/typeDefs.js create mode 100644 plugins/talk-plugin-toxic-comments/server/__mocks__/perspective.js create mode 100644 plugins/talk-plugin-toxic-comments/server/hooks.spec.js create mode 100644 test/setupJest.js diff --git a/.eslintrc.json b/.eslintrc.json index 78f7c2397..d00d21106 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,6 @@ { + "env": { + "jest": true + }, "extends": "@coralproject/eslint-config-talk" } diff --git a/client/jest.config.js b/client/jest.config.js new file mode 100644 index 000000000..02120d58e --- /dev/null +++ b/client/jest.config.js @@ -0,0 +1,36 @@ +const { pluginsPath } = require('../plugins'); + +const buildTargets = ['coral-admin']; + +const buildEmbeds = ['stream']; + +const specPattern = 'client/**/__tests__/**/*.spec.js?(x)'; + +module.exports = { + rootDir: '../', + testMatch: [ + `/${specPattern}`, + `/plugins/**/${specPattern}`, + ], + setupTestFrameworkScriptFile: '/test/client/setupJest.js', + modulePaths: [ + '/plugins', + '/client', + ...buildTargets.map(target => `/client/${target}/src`), + ...buildEmbeds.map(embed => `/client/coral-embed-${embed}/src`), + ], + moduleFileExtensions: ['js', 'jsx', 'json', 'yaml', 'yml'], + moduleDirectories: ['node_modules'], + transform: { + '^.+\\.jsx?$': 'babel-jest', + '\\.ya?ml$': '/test/client/yamlTransformer.js', + }, + testResultsProcessor: process.env.JEST_REPORTER, + moduleNameMapper: { + '^plugin-api\\/(.*)$': '/plugin-api/$1', + '^plugins\\/(.*)$': '/plugins/$1', + '^pluginsConfig$': pluginsPath, + '\\.(scss|css|less)$': 'identity-obj-proxy', + '\\.(gif|ttf|eot|svg)$': '/test/client/fileMock.js', + }, +}; diff --git a/jest.config.js b/jest.config.js index 7c3755bea..8519ef452 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,40 +1,8 @@ -const path = require('path'); -const { pluginsPath } = require('./plugins'); - -const buildTargets = ['coral-admin']; - -const buildEmbeds = ['stream']; - -// jest.config.js module.exports = { - testMatch: ['**/client/**/__tests__/**/*.js?(x)'], - setupTestFrameworkScriptFile: '/test/client/setupJest.js', - modulePaths: [ - '/plugins', - '/client', - ...buildTargets.map(target => - path.join('', 'client', target, 'src') - ), - ...buildEmbeds.map(embed => - path.join('', 'client', `coral-embed-${embed}`, 'src') - ), - ], - moduleFileExtensions: ['js', 'jsx', 'json', 'yaml', 'yml'], - moduleDirectories: ['node_modules'], - - transform: { - '^.+\\.jsx?$': 'babel-jest', - '\\.ya?ml$': '/test/client/yamlTransformer.js', - }, - + projects: ['', '/client'], + testPathIgnorePatterns: ['client'], + setupTestFrameworkScriptFile: '/test/setupJest.js', testResultsProcessor: process.env.JEST_REPORTER, - - moduleNameMapper: { - '^plugin-api\\/(.*)$': '/plugin-api/$1', - '^plugins\\/(.*)$': '/plugins/$1', - '^pluginsConfig$': pluginsPath, - - '\\.(scss|css|less)$': 'identity-obj-proxy', - '\\.(gif|ttf|eot|svg)$': '/test/client/fileMock.js', - }, + testEnvironment: 'node', + modulePaths: [''], }; diff --git a/package.json b/package.json index c9243a560..68fb0519e 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,11 @@ "lint:js": "eslint bin/cli* .", "lint": "npm-run-all lint:*", "plugins:reconcile": "./bin/cli plugins reconcile", - "test": "npm-run-all test:client test:server", - "test:server": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", - "test:client": "TEST_MODE=unit NODE_ENV=test jest", - "test:client:watch": "TEST_MODE=unit NODE_ENV=test jest --watch", + "test": "npm-run-all test:jest test:mocha", + "test:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand", + "test:client": "TEST_MODE=unit NODE_ENV=test jest --projects client", + "test:mocha": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", + "test:server:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand --projects .", "e2e": "./scripts/e2e.js", "e2e:ci": "./scripts/e2e-ci.sh", "heroku-postbuild": "npm-run-all plugins:reconcile build", @@ -127,6 +128,7 @@ "inquirer-autocomplete-prompt": "^0.12.1", "ioredis": "3.1.4", "ip": "^1.1.5", + "jest": "^22.4.3", "joi": "^13.0.0", "jsonwebtoken": "^8.0.0", "jwt-decode": "^2.2.0", @@ -226,7 +228,6 @@ "eslint-plugin-mocha": "^4.11.0", "husky": "^0.14.3", "identity-obj-proxy": "^3.0.0", - "jest": "^21.2.1", "jest-junit": "^3.6.0", "lint-staged": "^7.0.0", "mocha": "^3.1.2", diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index ffb1a3c07..40832ef74 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -239,26 +239,14 @@ function getReactionConfig(reaction) { hooks: { Action: { __resolveType: { - post({ action_type }) { - switch (action_type) { - case REACTION: - return `${Reaction}Action`; - default: - return undefined; - } - }, + post: ({ action_type }) => + action_type === REACTION ? `${Reaction}Action` : undefined, }, }, ActionSummary: { __resolveType: { - post({ action_type }) { - switch (action_type) { - case REACTION: - return `${Reaction}ActionSummary`; - default: - return undefined; - } - }, + post: ({ action_type = '' } = {}) => + action_type === REACTION ? `${Reaction}ActionSummary` : undefined, }, }, }, diff --git a/plugin-api/beta/server/getReactionConfig.spec.js b/plugin-api/beta/server/getReactionConfig.spec.js new file mode 100644 index 000000000..564f588b2 --- /dev/null +++ b/plugin-api/beta/server/getReactionConfig.spec.js @@ -0,0 +1,51 @@ +const getReactionConfig = require('./getReactionConfig'); + +describe('plugins-api', () => { + describe('getReactionConfig', () => { + let config; + beforeEach(() => { + config = getReactionConfig('heart'); + }); + + describe('context', () => { + it('provides a sort function', () => { + expect(config.context.Sort).toBeInstanceOf(Function); + const sort = config.context.Sort(); + expect(sort.Comments).toHaveProperty('hearts'); + }); + }); + + describe('hooks', () => { + it('handles the __resolveType properly', () => { + expect(config.hooks.ActionSummary.__resolveType).toHaveProperty('post'); + expect(config.hooks.ActionSummary.__resolveType.post).toBeInstanceOf( + Function + ); + expect( + config.hooks.ActionSummary.__resolveType.post({}) + ).toBeUndefined(); + expect( + config.hooks.ActionSummary.__resolveType.post({ action_type: 'LOVE' }) + ).toBeUndefined(); + expect( + config.hooks.ActionSummary.__resolveType.post({ + action_type: 'HEART', + }) + ).toEqual('HeartActionSummary'); + }); + it('handles the __resolveType properly', () => { + expect(config.hooks.Action.__resolveType).toHaveProperty('post'); + expect(config.hooks.Action.__resolveType.post).toBeInstanceOf(Function); + expect(config.hooks.Action.__resolveType.post({})).toBeUndefined(); + expect( + config.hooks.Action.__resolveType.post({ action_type: 'LOVE' }) + ).toBeUndefined(); + expect( + config.hooks.Action.__resolveType.post({ + action_type: 'HEART', + }) + ).toEqual('HeartAction'); + }); + }); + }); +}); diff --git a/plugins/talk-plugin-akismet/index.js b/plugins/talk-plugin-akismet/index.js index c57f5da5d..823faf182 100644 --- a/plugins/talk-plugin-akismet/index.js +++ b/plugins/talk-plugin-akismet/index.js @@ -1,126 +1,5 @@ -const debug = require('debug')('talk:plugin:akismet'); -const { ErrSpam } = require('./errors'); -const akismet = require('akismet-api'); -const { get, merge } = require('lodash'); -const { KEY, SITE } = require('./config'); -const client = akismet.client({ - key: KEY, - blog: SITE, -}); +const typeDefs = require('./server/typeDefs'); +const hooks = require('./server/hooks'); +const resolvers = require('./server/resolvers'); -let enabled = true; - -// TODO: when using a developer key, this is possible, the plus plan does not -// allow us to check the key. -// let enabled = false; -// client.verifyKey((err, valid) => { -// if (err) { -// throw err; -// } - -// if (valid) { -// enabled = true; -// } else { -// throw new Error('Akismet key is invalid'); -// } -// }); - -module.exports = { - typeDefs: ` - input CreateCommentInput { - - # If true, the mutation will fail when the - # body contains detected spam. - checkSpam: Boolean - } - - type Comment { - spam: Boolean - } - `, - hooks: { - RootMutation: { - createComment: { - async pre(_, { input }, { loaders, parent: req }) { - // If the key validation failed, then we can't run with the client. - if (!enabled) { - debug('not enabled, passing'); - return; - } - - let spam = false; - try { - const user_ip = get(req, 'ip', false); - if (!user_ip) { - debug('no ip on request'); - return; - } - - // Get some headers from the request. - const user_agent = req.get('User-Agent'); - if (!user_agent || user_agent.length === 0) { - debug('no user agent on request'); - return; - } - - const referrer = req.get('Referrer'); - if (!referrer || referrer.length === 0) { - debug('no referrer on request'); - return; - } - - // Get the Asset that the comment is being made against. - const asset = await loaders.Assets.getByID.load(input.asset_id); - if (!asset) { - debug('asset not found for new comment'); - return; - } - - // Send off the comment to Akismet to check to see what they say. - spam = await client.checkSpam({ - user_ip, - user_agent, - referrer, - permalink: asset.url, - comment_type: 'comment', - comment_content: input.body, - is_test: true, - }); - - debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`); - } catch (err) { - console.trace(err); - return; - } - - // Attach scores to metadata. - input.metadata = merge({}, input.metadata || {}, { - akismet: spam, - }); - - if (spam) { - if (input.checkSpam) { - throw new ErrSpam(); - } - - // Attach reason information for the flag being added. - input.status = 'SYSTEM_WITHHELD'; - input.actions = - input.actions && input.actions.length >= 0 ? input.actions : []; - input.actions.push({ - action_type: 'FLAG', - user_id: null, - group_id: 'SPAM_COMMENT', - metadata: {}, - }); - } - }, - }, - }, - }, - resolvers: { - Comment: { - spam: comment => get(comment, 'metadata.akismet', null), - }, - }, -}; +module.exports = { typeDefs, hooks, resolvers }; diff --git a/plugins/talk-plugin-akismet/config.js b/plugins/talk-plugin-akismet/server/config.js similarity index 100% rename from plugins/talk-plugin-akismet/config.js rename to plugins/talk-plugin-akismet/server/config.js diff --git a/plugins/talk-plugin-akismet/errors.js b/plugins/talk-plugin-akismet/server/errors.js similarity index 100% rename from plugins/talk-plugin-akismet/errors.js rename to plugins/talk-plugin-akismet/server/errors.js diff --git a/plugins/talk-plugin-akismet/server/hooks.js b/plugins/talk-plugin-akismet/server/hooks.js new file mode 100644 index 000000000..80a233584 --- /dev/null +++ b/plugins/talk-plugin-akismet/server/hooks.js @@ -0,0 +1,107 @@ +const debug = require('debug')('talk:plugin:akismet'); +const { ErrSpam } = require('./errors'); +const akismet = require('akismet-api'); +const { get, merge } = require('lodash'); +const { KEY, SITE } = require('./config'); +const client = akismet.client({ + key: KEY, + blog: SITE, +}); + +let enabled = true; + +// TODO: when using a developer key, this is possible, the plus plan does not +// allow us to check the key. +// let enabled = false; +// client.verifyKey((err, valid) => { +// if (err) { +// throw err; +// } + +// if (valid) { +// enabled = true; +// } else { +// throw new Error('Akismet key is invalid'); +// } +// }); + +module.exports = { + RootMutation: { + createComment: { + async pre(_, { input }, { loaders, parent: req }) { + // If the key validation failed, then we can't run with the client. + if (!enabled) { + debug('not enabled, passing'); + return; + } + + let spam = false; + try { + const user_ip = get(req, 'ip', false); + if (!user_ip) { + debug('no ip on request'); + return; + } + + // Get some headers from the request. + const user_agent = req.get('User-Agent'); + if (!user_agent || user_agent.length === 0) { + debug('no user agent on request'); + return; + } + + const referrer = req.get('Referrer'); + if (!referrer || referrer.length === 0) { + debug('no referrer on request'); + return; + } + + // Get the Asset that the comment is being made against. + const asset = await loaders.Assets.getByID.load(input.asset_id); + if (!asset) { + debug('asset not found for new comment'); + return; + } + + // Send off the comment to Akismet to check to see what they say. + spam = await client.checkSpam({ + user_ip, + user_agent, + referrer, + permalink: asset.url, + comment_type: 'comment', + comment_content: input.body, + is_test: true, + }); + + debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`); + } catch (err) { + console.trace(err); + return; + } + + // Attach scores to metadata. + input.metadata = merge({}, input.metadata || {}, { + akismet: spam, + }); + + if (spam) { + if (input.checkSpam) { + throw new ErrSpam(); + } + + // Attach reason information for the flag being added. + input.status = 'SYSTEM_WITHHELD'; + input.actions = + input.actions && input.actions.length >= 0 ? input.actions : []; + input.actions.push({ + action_type: 'FLAG', + user_id: null, + group_id: 'SPAM_COMMENT', + metadata: {}, + }); + } + }, + }, + }, +}; diff --git a/plugins/talk-plugin-akismet/server/resolvers.js b/plugins/talk-plugin-akismet/server/resolvers.js new file mode 100644 index 000000000..a300f510f --- /dev/null +++ b/plugins/talk-plugin-akismet/server/resolvers.js @@ -0,0 +1,7 @@ +const { get } = require('lodash'); + +module.exports = { + Comment: { + spam: comment => get(comment, 'metadata.akismet', null), + }, +}; diff --git a/plugins/talk-plugin-akismet/server/resolvers.spec.js b/plugins/talk-plugin-akismet/server/resolvers.spec.js new file mode 100644 index 000000000..06ee6168e --- /dev/null +++ b/plugins/talk-plugin-akismet/server/resolvers.spec.js @@ -0,0 +1,14 @@ +const resolvers = require('./resolvers'); + +describe('talk-plugin-akismet', () => { + describe('resolvers', () => { + it('resolves when there is a akismet value', () => { + const spam = resolvers.Comment.spam({ metadata: { akismet: true } }); + expect(spam).toEqual(true); + }); + it('resolves when there not is a akismet value', () => { + const spam = resolvers.Comment.spam({}); + expect(spam).toEqual(null); + }); + }); +}); diff --git a/plugins/talk-plugin-akismet/server/typeDefs.graphql b/plugins/talk-plugin-akismet/server/typeDefs.graphql new file mode 100644 index 000000000..61ded658c --- /dev/null +++ b/plugins/talk-plugin-akismet/server/typeDefs.graphql @@ -0,0 +1,10 @@ +input CreateCommentInput { + + # If true, the mutation will fail when the + # body contains detected spam. + checkSpam: Boolean +} + +type Comment { + spam: Boolean +} diff --git a/plugins/talk-plugin-akismet/server/typeDefs.js b/plugins/talk-plugin-akismet/server/typeDefs.js new file mode 100644 index 000000000..7ab1954e1 --- /dev/null +++ b/plugins/talk-plugin-akismet/server/typeDefs.js @@ -0,0 +1,7 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = fs.readFileSync( + path.join(__dirname, 'typeDefs.graphql'), + 'utf8' +); diff --git a/plugins/talk-plugin-toxic-comments/server/__mocks__/perspective.js b/plugins/talk-plugin-toxic-comments/server/__mocks__/perspective.js new file mode 100644 index 000000000..cda3cf841 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/__mocks__/perspective.js @@ -0,0 +1,11 @@ +let values = {}; + +const getScores = () => values.getScores; + +const isToxic = () => values.isToxic; + +const setValues = newValues => { + values = newValues; +}; + +module.exports = { getScores, isToxic, setValues }; diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js index 0be363ea1..d35b5cc20 100644 --- a/plugins/talk-plugin-toxic-comments/server/hooks.js +++ b/plugins/talk-plugin-toxic-comments/server/hooks.js @@ -1,11 +1,6 @@ const { getScores, isToxic } = require('./perspective'); const { ErrToxic } = require('./errors'); -// 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: { @@ -16,7 +11,7 @@ module.exports = { scores = await getScores(input.body); } catch (err) { // Warn and let mutation pass. - console.trace(err); + console.trace(err); // TODO: log/handle this differently? return; } diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.spec.js b/plugins/talk-plugin-toxic-comments/server/hooks.spec.js new file mode 100644 index 000000000..d9fbe67e6 --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/server/hooks.spec.js @@ -0,0 +1,31 @@ +const hooks = require('./hooks'); +const { ErrToxic } = require('./errors'); + +// Mock out the perspective api call. +jest.mock('./perspective'); + +describe('talk-plugin-toxic-comments', () => { + describe('hooks', () => { + beforeEach(() => { + require('./perspective').setValues({ isToxic: true }); + }); + + it('sets the correct values for a toxic comment', async () => { + let input = { body: 'This is a body.', checkToxicity: false }; + await hooks.RootMutation.createComment.pre(null, { input }, null, null); + expect(input).toHaveProperty('status', 'SYSTEM_WITHHELD'); + }); + + it('throws an error when a toxic comment is sent', async () => { + expect.assertions(1); + await expect( + hooks.RootMutation.createComment.pre( + null, + { input: { checkToxicity: true } }, + null, + null + ) + ).rejects.toBeInstanceOf(ErrToxic); + }); + }); +}); diff --git a/services/logging.js b/services/logging.js index 3e2a6d731..7ae3654b1 100644 --- a/services/logging.js +++ b/services/logging.js @@ -7,11 +7,11 @@ const { LOGGING_LEVEL, REVISION_HASH } = require('../config'); // but will send JSON logs in production that's parsable by a system like ELK. const streams = (() => { // In development, use the debug stream printer. - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV !== 'production') { const debug = require('bunyan-debug-stream'); return [ { - level: 'debug', + level: LOGGING_LEVEL, type: 'raw', stream: debug({ basepath: path.resolve(__dirname, '..'), @@ -22,7 +22,7 @@ const streams = (() => { } // In production, emit JSON. - return [{ stream: process.stdout, level: 'info' }]; + return [{ stream: process.stdout, level: LOGGING_LEVEL }]; })(); // logger is the base logger used by all logging systems in Talk. @@ -31,7 +31,6 @@ const logger = createBunyanLogger({ name: 'talk', version, revision: REVISION_HASH, - level: LOGGING_LEVEL, streams, serializers: stdSerializers, }); diff --git a/services/mongoose.js b/services/mongoose.js index 5e498cee8..66304ee07 100644 --- a/services/mongoose.js +++ b/services/mongoose.js @@ -37,6 +37,15 @@ if (WEBPACK) { // here just ensures that the application can quit correctly. mongoose.disconnect(); } else { + mongoose.connection.on('connected', () => logger.debug('mongodb connected')); + mongoose.connection.on('disconnected', () => + logger.debug('mongodb disconnected') + ); + + setTimeout(() => { + mongoose.disconnect(); + }, 5000); + // Connect to the Mongo instance. mongoose .connect(MONGO_URL, { @@ -45,9 +54,6 @@ if (WEBPACK) { autoIndex: CREATE_MONGO_INDEXES, }, }) - .then(() => { - logger.debug('mongodb connection established'); - }) .catch(err => { console.error(err); process.exit(1); diff --git a/test/setupJest.js b/test/setupJest.js new file mode 100644 index 000000000..9ff2d8af4 --- /dev/null +++ b/test/setupJest.js @@ -0,0 +1,21 @@ +const mongoose = require('../services/mongoose'); + +beforeEach(async () => { + await Promise.all( + Object.keys(mongoose.connection.collections).map(collection => { + return new Promise((resolve, reject) => { + mongoose.connection.collections[collection].remove(function(err) { + if (err) { + return reject(err); + } + + return resolve(); + }); + }); + }) + ); +}); + +afterAll(async function() { + await mongoose.disconnect(); +}); diff --git a/yarn.lock b/yarn.lock index b5d130ab0..598b56829 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,7 +68,7 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@coralproject/eslint-config-talk@^0.1.0", "@coralproject/eslint-config-talk@^0.1.1": +"@coralproject/eslint-config-talk@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@coralproject/eslint-config-talk/-/eslint-config-talk-0.1.1.tgz#71991b4937a3ffe657128d7f1170da4b5fb75c9e" dependencies: @@ -126,12 +126,6 @@ to-title-case "~1.0.0" url-regex "~4.1.1" -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - dependencies: - "@types/node" "*" - "@types/graphql@0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.10.2.tgz#d7c79acbaa17453b6681c80c34b38fcb10c4c08c" @@ -144,25 +138,10 @@ version "0.9.4" resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.4.tgz#cdeb6bcbef9b6c584374b81aa7f48ecf3da404fa" -"@types/lodash@^4.14.50": - version "4.14.106" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd" - "@types/node@*": version "8.0.53" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" -"@types/node@^7.0.0": - version "7.0.59" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.59.tgz#fd7dceba9521c2d62c3e0eda8c5d704bf88b261d" - -"@types/request@^0.0.39": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/request/-/request-0.0.39.tgz#168b96cf4253c5d54d403f746f82ee7aed47ce2c" - dependencies: - "@types/form-data" "*" - "@types/node" "*" - "@types/ws@^3.0.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.2.0.tgz#988ff690e6ed10068a86aa0e9f842d0a03c09e21" @@ -180,7 +159,7 @@ a-sync-waterfall@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.0.tgz#38e8319d79379e24628845b53b96722b29e0e47c" -abab@^1.0.0, abab@^1.0.3, abab@^1.0.4: +abab@^1.0.0, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -195,13 +174,6 @@ accepts@^1.3.4, accepts@~1.3.4: mime-types "~2.1.16" negotiator "0.6.1" -accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" - acorn-dynamic-import@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" @@ -214,7 +186,7 @@ acorn-globals@^1.0.4: dependencies: acorn "^2.1.0" -acorn-globals@^3.0.0, acorn-globals@^3.1.0: +acorn-globals@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" dependencies: @@ -256,10 +228,6 @@ acorn@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" -acorn@^5.5.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" - addressparser@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" @@ -888,6 +856,13 @@ babel-jest@^21.2.0: babel-plugin-istanbul "^4.0.0" babel-preset-jest "^21.2.0" +babel-jest@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a" + dependencies: + babel-plugin-istanbul "^4.1.5" + babel-preset-jest "^22.4.3" + babel-loader@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" @@ -922,10 +897,23 @@ babel-plugin-istanbul@^4.0.0: istanbul-lib-instrument "^1.7.5" test-exclude "^4.1.1" +babel-plugin-istanbul@^4.1.5: + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" + find-up "^2.1.0" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" + babel-plugin-jest-hoist@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz#2cef637259bd4b628a6cace039de5fcd14dbb006" +babel-plugin-jest-hoist@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a" + babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" @@ -1258,6 +1246,13 @@ babel-preset-jest@^21.2.0: babel-plugin-jest-hoist "^21.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" +babel-preset-jest@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156" + dependencies: + babel-plugin-jest-hoist "^22.4.3" + babel-plugin-syntax-object-rest-spread "^6.13.0" + babel-preset-react@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" @@ -1809,13 +1804,6 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" -casual@^1.5.19: - version "1.5.19" - resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2" - dependencies: - mersenne-twister "^1.0.1" - moment "^2.15.2" - center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -2092,6 +2080,14 @@ cliui@^3.0.3, cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +cliui@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc" + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + clone@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" @@ -2239,6 +2235,10 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +compare-versions@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" + component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -2373,10 +2373,6 @@ content-security-policy-builder@1.1.0: dependencies: dashify "^0.2.0" -content-type-parser@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" - content-type-parser@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" @@ -2385,7 +2381,11 @@ content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" -convert-source-map@^1.4.0, convert-source-map@^1.5.0: +convert-source-map@^1.4.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -2477,10 +2477,6 @@ cosmiconfig@^4.0.0, cosmiconfig@~4.0.0: parse-json "^4.0.0" require-from-string "^2.0.1" -crc@3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" - create-ecdh@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" @@ -2775,7 +2771,7 @@ debug@*, debug@3.1.0, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0: dependencies: ms "2.0.0" -debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.8: +debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -2928,6 +2924,10 @@ detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + dialog-polyfill@^0.4.9: version "0.4.9" resolved "https://registry.yarnpkg.com/dialog-polyfill/-/dialog-polyfill-0.4.9.tgz#c690b3727c3d82e0f947bd5b910b32af8a2ef57d" @@ -2970,7 +2970,7 @@ dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" -doctrine@^2.0.0, doctrine@^2.1.0: +doctrine@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" dependencies: @@ -3111,10 +3111,6 @@ ejs@2.5.7, ejs@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -ejs@^2.5.8: - version "2.5.8" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.8.tgz#2ab6954619f225e6193b7ac5f7c39c48fefe4380" - electron-to-chromium@^1.2.7: version "1.3.26" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.26.tgz#996427294861a74d9c7c82b9260ea301e8c02d66" @@ -3253,6 +3249,16 @@ es-abstract@^1.4.3: is-callable "^1.1.3" is-regex "^1.0.4" +es-abstract@^1.5.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.10.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" @@ -3410,49 +3416,6 @@ eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" - dependencies: - ajv "^5.3.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^3.1.0" - doctrine "^2.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.4" - esquery "^1.0.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.0.1" - ignore "^3.3.3" - imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-resolvable "^1.0.0" - js-yaml "^3.9.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" - progress "^2.0.0" - regexpp "^1.0.1" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" - strip-json-comments "~2.0.1" - table "4.0.2" - text-table "~0.2.0" - eslint@^4.5.0: version "4.13.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.1.tgz#0055e0014464c7eb7878caf549ef2941992b444f" @@ -3502,13 +3465,6 @@ espree@^3.5.2: acorn "^5.2.1" acorn-jsx "^3.0.0" -espree@^3.5.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - esprima@3.x.x, esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -3628,6 +3584,10 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -3660,17 +3620,6 @@ expect-ct@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.0.tgz#52735678de18530890d8d7b95f0ac63640958094" -expect@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-21.2.1.tgz#003ac2ac7005c3c29e73b38a272d4afadd6d1d7b" - dependencies: - ansi-styles "^3.2.0" - jest-diff "^21.2.1" - jest-get-type "^21.2.0" - jest-matcher-utils "^21.2.1" - jest-message-util "^21.2.1" - jest-regex-util "^21.2.0" - expect@^22.4.0: version "22.4.0" resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.0.tgz#371edf1ae15b83b5bf5ec34b42f1584660a36c16" @@ -3682,6 +3631,17 @@ expect@^22.4.0: jest-message-util "^22.4.0" jest-regex-util "^22.1.0" +expect@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674" + dependencies: + ansi-styles "^3.2.0" + jest-diff "^22.4.3" + jest-get-type "^22.4.3" + jest-matcher-utils "^22.4.3" + jest-message-util "^22.4.3" + jest-regex-util "^22.4.3" + exports-loader@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886" @@ -3689,20 +3649,6 @@ exports-loader@^0.6.4: loader-utils "^1.0.2" source-map "0.5.x" -express-session@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a" - dependencies: - cookie "0.3.1" - cookie-signature "1.0.6" - crc "3.4.4" - debug "2.6.9" - depd "~1.1.1" - on-headers "~1.0.1" - parseurl "~1.3.2" - uid-safe "~2.1.5" - utils-merge "1.0.1" - express-static-gzip@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-0.3.2.tgz#89ede84547a5717de3146315f62dc996c071a88d" @@ -3744,41 +3690,6 @@ express@4.16.0, express@^4.12.2: utils-merge "1.0.1" vary "~1.1.2" -express@^4.16.3: - version "4.16.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" - dependencies: - accepts "~1.3.5" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.1" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.3" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" - utils-merge "1.0.1" - vary "~1.1.2" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -3976,18 +3887,6 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" -finalhandler@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" - unpipe "~1.0.0" - find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -4174,20 +4073,13 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.0.0: +fsevents@^1.0.0, fsevents@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" dependencies: nan "^2.3.0" node-pre-gyp "^0.6.39" -fsevents@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" - dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.36" - fstream-ignore@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" @@ -4298,16 +4190,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gigya@2.0.33: - version "2.0.33" - resolved "https://registry.yarnpkg.com/gigya/-/gigya-2.0.33.tgz#c5845cd16fac8ebcfb5e727e1ebe9e51352482fb" - dependencies: - "@types/lodash" "^4.14.50" - "@types/node" "^7.0.0" - "@types/request" "^0.0.39" - lodash "^4.17.4" - request "^2.79.0" - git-up@^2.0.0: version "2.0.9" resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.9.tgz#219bfd27c82daeead8495beb386dc18eae63636d" @@ -4974,12 +4856,6 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" -html-encoding-sniffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" - dependencies: - whatwg-encoding "^1.0.1" - html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -5142,6 +5018,13 @@ import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" +import-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + imports-loader@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.7.1.tgz#f204b5f34702a32c1db7d48d89d5e867a0441253" @@ -5304,10 +5187,6 @@ ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" -ipaddr.js@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" - is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -5689,33 +5568,50 @@ isstream@0.1.x, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -istanbul-api@^1.1.1: - version "1.1.14" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680" +istanbul-api@^1.1.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.1.tgz#4c3b05d18c0016d1022e079b98dc82c40f488954" dependencies: async "^2.1.4" + compare-versions "^3.1.0" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.8.0" - istanbul-lib-report "^1.1.1" - istanbul-lib-source-maps "^1.2.1" - istanbul-reports "^1.1.2" + istanbul-lib-coverage "^1.2.0" + istanbul-lib-hook "^1.2.0" + istanbul-lib-instrument "^1.10.1" + istanbul-lib-report "^1.1.4" + istanbul-lib-source-maps "^1.2.4" + istanbul-reports "^1.3.0" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: +istanbul-lib-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" -istanbul-lib-hook@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" +istanbul-lib-coverage@^1.1.2, istanbul-lib-coverage@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" + +istanbul-lib-hook@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz#ae556fd5a41a6e8efa0b1002b1e416dfeaf9816c" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0: +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.2.0" + semver "^5.3.0" + +istanbul-lib-instrument@^1.7.5: version "1.8.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" dependencies: @@ -5727,28 +5623,38 @@ istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-ins istanbul-lib-coverage "^1.1.1" semver "^5.3.0" -istanbul-lib-report@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" +istanbul-lib-report@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5" dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.0" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" +istanbul-lib-source-maps@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6" dependencies: - debug "^2.6.3" - istanbul-lib-coverage "^1.1.1" + debug "^3.1.0" + istanbul-lib-coverage "^1.1.2" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" +istanbul-lib-source-maps@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7" + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.2.0" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.3.0.tgz#2f322e81e1d9520767597dca3c20a0cce89a3554" dependencies: handlebars "^4.0.3" @@ -5760,61 +5666,50 @@ iterall@^1.1.0, iterall@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" -jest-changed-files@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" +jest-changed-files@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2" dependencies: throat "^4.0.0" -jest-cli@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00" +jest-cli@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" + exit "^0.1.2" glob "^7.1.2" graceful-fs "^4.1.11" + import-local "^1.0.0" is-ci "^1.0.10" - istanbul-api "^1.1.1" - istanbul-lib-coverage "^1.0.1" - istanbul-lib-instrument "^1.4.2" - istanbul-lib-source-maps "^1.1.0" - jest-changed-files "^21.2.0" - jest-config "^21.2.1" - jest-environment-jsdom "^21.2.1" - jest-haste-map "^21.2.0" - jest-message-util "^21.2.1" - jest-regex-util "^21.2.0" - jest-resolve-dependencies "^21.2.0" - jest-runner "^21.2.1" - jest-runtime "^21.2.1" - jest-snapshot "^21.2.1" - jest-util "^21.2.1" + istanbul-api "^1.1.14" + istanbul-lib-coverage "^1.1.1" + istanbul-lib-instrument "^1.8.0" + istanbul-lib-source-maps "^1.2.1" + jest-changed-files "^22.4.3" + jest-config "^22.4.3" + jest-environment-jsdom "^22.4.3" + jest-get-type "^22.4.3" + jest-haste-map "^22.4.3" + jest-message-util "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve-dependencies "^22.4.3" + jest-runner "^22.4.3" + jest-runtime "^22.4.3" + jest-snapshot "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" + jest-worker "^22.4.3" micromatch "^2.3.11" - node-notifier "^5.0.2" - pify "^3.0.0" + node-notifier "^5.2.1" + realpath-native "^1.0.0" + rimraf "^2.5.4" slash "^1.0.0" string-length "^2.0.0" strip-ansi "^4.0.0" which "^1.2.12" - worker-farm "^1.3.1" - yargs "^9.0.0" - -jest-config@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-21.2.1.tgz#c7586c79ead0bcc1f38c401e55f964f13bf2a480" - dependencies: - chalk "^2.0.1" - glob "^7.1.1" - jest-environment-jsdom "^21.2.1" - jest-environment-node "^21.2.1" - jest-get-type "^21.2.0" - jest-jasmine2 "^21.2.1" - jest-regex-util "^21.2.0" - jest-resolve "^21.2.0" - jest-util "^21.2.1" - jest-validate "^21.2.1" - pretty-format "^21.2.1" + yargs "^10.0.3" jest-config@^22.4.2: version "22.4.2" @@ -5832,14 +5727,21 @@ jest-config@^22.4.2: jest-validate "^22.4.2" pretty-format "^22.4.0" -jest-diff@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-21.2.1.tgz#46cccb6cab2d02ce98bc314011764bb95b065b4f" +jest-config@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403" dependencies: chalk "^2.0.1" - diff "^3.2.0" - jest-get-type "^21.2.0" - pretty-format "^21.2.1" + glob "^7.1.1" + jest-environment-jsdom "^22.4.3" + jest-environment-node "^22.4.3" + jest-get-type "^22.4.3" + jest-jasmine2 "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" + pretty-format "^22.4.3" jest-diff@^22.4.0: version "22.4.0" @@ -5850,17 +5752,24 @@ jest-diff@^22.4.0: jest-get-type "^22.1.0" pretty-format "^22.4.0" -jest-docblock@^21.0.0, jest-docblock@^21.2.0: +jest-diff@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030" + dependencies: + chalk "^2.0.1" + diff "^3.2.0" + jest-get-type "^22.4.3" + pretty-format "^22.4.3" + +jest-docblock@^21.0.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" -jest-environment-jsdom@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz#38d9980c8259b2a608ec232deee6289a60d9d5b4" +jest-docblock@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19" dependencies: - jest-mock "^21.2.0" - jest-util "^21.2.1" - jsdom "^9.12.0" + detect-newline "^2.1.0" jest-environment-jsdom@^22.4.1: version "22.4.1" @@ -5870,12 +5779,13 @@ jest-environment-jsdom@^22.4.1: jest-util "^22.4.1" jsdom "^11.5.1" -jest-environment-node@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-21.2.1.tgz#98c67df5663c7fbe20f6e792ac2272c740d3b8c8" +jest-environment-jsdom@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e" dependencies: - jest-mock "^21.2.0" - jest-util "^21.2.1" + jest-mock "^22.4.3" + jest-util "^22.4.3" + jsdom "^11.5.1" jest-environment-node@^22.4.1: version "22.4.1" @@ -5884,37 +5794,32 @@ jest-environment-node@^22.4.1: jest-mock "^22.2.0" jest-util "^22.4.1" -jest-get-type@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" +jest-environment-node@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129" + dependencies: + jest-mock "^22.4.3" + jest-util "^22.4.3" jest-get-type@^22.1.0: version "22.1.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.1.0.tgz#4e90af298ed6181edc85d2da500dbd2753e0d5a9" -jest-haste-map@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-21.2.0.tgz#1363f0a8bb4338f24f001806571eff7a4b2ff3d8" +jest-get-type@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" + +jest-haste-map@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b" dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" - jest-docblock "^21.2.0" + jest-docblock "^22.4.3" + jest-serializer "^22.4.3" + jest-worker "^22.4.3" micromatch "^2.3.11" sane "^2.0.0" - worker-farm "^1.3.1" - -jest-jasmine2@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz#9cc6fc108accfa97efebce10c4308548a4ea7592" - dependencies: - chalk "^2.0.1" - expect "^21.2.1" - graceful-fs "^4.1.11" - jest-diff "^21.2.1" - jest-matcher-utils "^21.2.1" - jest-message-util "^21.2.1" - jest-snapshot "^21.2.1" - p-cancelable "^0.3.0" jest-jasmine2@^22.4.2: version "22.4.2" @@ -5932,6 +5837,22 @@ jest-jasmine2@^22.4.2: jest-util "^22.4.1" source-map-support "^0.5.0" +jest-jasmine2@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965" + dependencies: + chalk "^2.0.1" + co "^4.6.0" + expect "^22.4.3" + graceful-fs "^4.1.11" + is-generator-fn "^1.0.0" + jest-diff "^22.4.3" + jest-matcher-utils "^22.4.3" + jest-message-util "^22.4.3" + jest-snapshot "^22.4.3" + jest-util "^22.4.3" + source-map-support "^0.5.0" + jest-junit@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-3.6.0.tgz#f4c4358e5286364a4324dc14abddd526aadfbd38" @@ -5940,13 +5861,11 @@ jest-junit@^3.6.0: strip-ansi "^4.0.0" xml "^1.0.1" -jest-matcher-utils@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64" +jest-leak-detector@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35" dependencies: - chalk "^2.0.1" - jest-get-type "^21.2.0" - pretty-format "^21.2.1" + pretty-format "^22.4.3" jest-matcher-utils@^22.4.0: version "22.4.0" @@ -5956,13 +5875,13 @@ jest-matcher-utils@^22.4.0: jest-get-type "^22.1.0" pretty-format "^22.4.0" -jest-message-util@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-21.2.1.tgz#bfe5d4692c84c827d1dcf41823795558f0a1acbe" +jest-matcher-utils@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff" dependencies: chalk "^2.0.1" - micromatch "^2.3.11" - slash "^1.0.0" + jest-get-type "^22.4.3" + pretty-format "^22.4.3" jest-message-util@^22.4.0: version "22.4.0" @@ -5974,35 +5893,37 @@ jest-message-util@^22.4.0: slash "^1.0.0" stack-utils "^1.0.1" -jest-mock@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-21.2.0.tgz#7eb0770e7317968165f61ea2a7281131534b3c0f" +jest-message-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7" + dependencies: + "@babel/code-frame" "^7.0.0-beta.35" + chalk "^2.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + stack-utils "^1.0.1" jest-mock@^22.2.0: version "22.2.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.2.0.tgz#444b3f9488a7473adae09bc8a77294afded397a7" -jest-regex-util@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-21.2.0.tgz#1b1e33e63143babc3e0f2e6c9b5ba1eb34b2d530" +jest-mock@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.4.3.tgz#f63ba2f07a1511772cdc7979733397df770aabc7" jest-regex-util@^22.1.0: version "22.1.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.1.0.tgz#5daf2fe270074b6da63e5d85f1c9acc866768f53" -jest-resolve-dependencies@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz#9e231e371e1a736a1ad4e4b9a843bc72bfe03d09" - dependencies: - jest-regex-util "^21.2.0" +jest-regex-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af" -jest-resolve@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-21.2.0.tgz#068913ad2ba6a20218e5fd32471f3874005de3a6" +jest-resolve-dependencies@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e" dependencies: - browser-resolve "^1.11.2" - chalk "^2.0.1" - is-builtin-module "^1.0.0" + jest-regex-util "^22.4.3" jest-resolve@^22.4.2: version "22.4.2" @@ -6011,53 +5932,57 @@ jest-resolve@^22.4.2: browser-resolve "^1.11.2" chalk "^2.0.1" -jest-runner@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-21.2.1.tgz#194732e3e518bfb3d7cbfc0fd5871246c7e1a467" +jest-resolve@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea" dependencies: - jest-config "^21.2.1" - jest-docblock "^21.2.0" - jest-haste-map "^21.2.0" - jest-jasmine2 "^21.2.1" - jest-message-util "^21.2.1" - jest-runtime "^21.2.1" - jest-util "^21.2.1" - pify "^3.0.0" - throat "^4.0.0" - worker-farm "^1.3.1" + browser-resolve "^1.11.2" + chalk "^2.0.1" -jest-runtime@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-21.2.1.tgz#99dce15309c670442eee2ebe1ff53a3cbdbbb73e" +jest-runner@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3" + dependencies: + exit "^0.1.2" + jest-config "^22.4.3" + jest-docblock "^22.4.3" + jest-haste-map "^22.4.3" + jest-jasmine2 "^22.4.3" + jest-leak-detector "^22.4.3" + jest-message-util "^22.4.3" + jest-runtime "^22.4.3" + jest-util "^22.4.3" + jest-worker "^22.4.3" + throat "^4.0.0" + +jest-runtime@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0" dependencies: babel-core "^6.0.0" - babel-jest "^21.2.0" - babel-plugin-istanbul "^4.0.0" + babel-jest "^22.4.3" + babel-plugin-istanbul "^4.1.5" chalk "^2.0.1" convert-source-map "^1.4.0" + exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^21.2.1" - jest-haste-map "^21.2.0" - jest-regex-util "^21.2.0" - jest-resolve "^21.2.0" - jest-util "^21.2.1" + jest-config "^22.4.3" + jest-haste-map "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" json-stable-stringify "^1.0.1" micromatch "^2.3.11" + realpath-native "^1.0.0" slash "^1.0.0" strip-bom "3.0.0" write-file-atomic "^2.1.0" - yargs "^9.0.0" + yargs "^10.0.3" -jest-snapshot@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-21.2.1.tgz#29e49f16202416e47343e757e5eff948c07fd7b0" - dependencies: - chalk "^2.0.1" - jest-diff "^21.2.1" - jest-matcher-utils "^21.2.1" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - pretty-format "^21.2.1" +jest-serializer@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436" jest-snapshot@^22.4.0: version "22.4.0" @@ -6070,17 +5995,16 @@ jest-snapshot@^22.4.0: natural-compare "^1.4.0" pretty-format "^22.4.0" -jest-util@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-21.2.1.tgz#a274b2f726b0897494d694a6c3d6a61ab819bb78" +jest-snapshot@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2" dependencies: - callsites "^2.0.0" chalk "^2.0.1" - graceful-fs "^4.1.11" - jest-message-util "^21.2.1" - jest-mock "^21.2.0" - jest-validate "^21.2.1" + jest-diff "^22.4.3" + jest-matcher-utils "^22.4.3" mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^22.4.3" jest-util@^22.4.1: version "22.4.1" @@ -6094,14 +6018,17 @@ jest-util@^22.4.1: mkdirp "^0.5.1" source-map "^0.6.0" -jest-validate@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" +jest-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac" dependencies: + callsites "^2.0.0" chalk "^2.0.1" - jest-get-type "^21.2.0" - leven "^2.1.0" - pretty-format "^21.2.1" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + jest-message-util "^22.4.3" + mkdirp "^0.5.1" + source-map "^0.6.0" jest-validate@^22.4.0, jest-validate@^22.4.2: version "22.4.2" @@ -6113,11 +6040,28 @@ jest-validate@^22.4.0, jest-validate@^22.4.2: leven "^2.1.0" pretty-format "^22.4.0" -jest@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1" +jest-validate@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30" dependencies: - jest-cli "^21.2.1" + chalk "^2.0.1" + jest-config "^22.4.3" + jest-get-type "^22.4.3" + leven "^2.1.0" + pretty-format "^22.4.3" + +jest-worker@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b" + dependencies: + merge-stream "^1.0.1" + +jest@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16" + dependencies: + import-local "^1.0.0" + jest-cli "^22.4.3" joi@^13.0.0: version "13.1.2" @@ -6170,13 +6114,20 @@ js-yaml@0.3.x: version "0.3.7" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-0.3.7.tgz#d739d8ee86461e54b354d6a7d7d1f2ad9a167f62" -js-yaml@^3.4.3, js-yaml@^3.5.2, js-yaml@^3.6.1, js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1: +js-yaml@^3.4.3, js-yaml@^3.5.2, js-yaml@^3.6.1, js-yaml@^3.9.0, js-yaml@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.7.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -6239,30 +6190,6 @@ jsdom@^7.0.2: whatwg-url-compat "~0.6.5" xml-name-validator ">= 2.0.1 < 3.0.0" -jsdom@^9.12.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" - dependencies: - abab "^1.0.3" - acorn "^4.0.4" - acorn-globals "^3.1.0" - array-equal "^1.0.0" - content-type-parser "^1.0.1" - cssom ">= 0.3.2 < 0.4.0" - cssstyle ">= 0.2.37 < 0.3.0" - escodegen "^1.6.1" - html-encoding-sniffer "^1.0.1" - nwmatcher ">= 1.3.9 < 2.0.0" - parse5 "^1.5.1" - request "^2.79.0" - sax "^1.2.1" - symbol-tree "^3.2.1" - tough-cookie "^2.3.2" - webidl-conversions "^4.0.0" - whatwg-encoding "^1.0.1" - whatwg-url "^4.3.0" - xml-name-validator "^2.0.1" - jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -7160,14 +7087,16 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + dependencies: + readable-stream "^2.0.1" + merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -mersenne-twister@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" - metascraper-author@^3.9.2: version "3.9.2" resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19" @@ -7308,7 +7237,7 @@ miller-rabin@^4.0.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -"mime-db@>= 1.33.0 < 2", mime-db@~1.33.0: +"mime-db@>= 1.33.0 < 2": version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -7318,12 +7247,6 @@ mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, dependencies: mime-db "~1.30.0" -mime-types@~2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - dependencies: - mime-db "~1.33.0" - mime@1.4.1, mime@^1.3.4, mime@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -7456,10 +7379,6 @@ moment@^2.10.3: version "2.19.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167" -moment@^2.15.2: - version "2.22.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730" - mongodb-core@2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8" @@ -7763,16 +7682,16 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-notifier@^5.0.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" +node-notifier@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" dependencies: growly "^1.3.0" - semver "^5.3.0" - shellwords "^0.1.0" - which "^1.2.12" + semver "^5.4.1" + shellwords "^0.1.1" + which "^1.3.0" -node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: +node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" dependencies: @@ -8046,7 +7965,7 @@ nunjucks@^3.1.2: optionalDependencies: chokidar "^1.6.0" -"nwmatcher@>= 1.3.7 < 2.0.0", "nwmatcher@>= 1.3.9 < 2.0.0", nwmatcher@^1.4.3: +"nwmatcher@>= 1.3.7 < 2.0.0", nwmatcher@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" @@ -8101,6 +8020,13 @@ object.entries@^1.0.4: function-bind "^1.1.0" has "^1.0.1" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -8224,10 +8150,6 @@ output-file-sync@^1.1.2: mkdirp "^0.5.1" object-assign "^4.1.0" -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -8417,15 +8339,6 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2: uid2 "0.0.x" utils-merge "1.x.x" -passport-openidconnect@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.0.2.tgz#e488f8bdb386c9a9fd39c91d5ab8c880156e8153" - dependencies: - oauth "0.9.x" - passport-strategy "1.x.x" - request "^2.75.0" - webfinger "0.4.x" - passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" @@ -9073,16 +8986,16 @@ prettier@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.10.2.tgz#1af8356d1842276a99a5b5529c82dd9e9ad3cc93" -pretty-format@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36" +pretty-format@^22.4.0: + version "22.4.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.0.tgz#237b1f7e1c50ed03bc65c03ccc29d7c8bb7beb94" dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -pretty-format@^22.4.0: - version "22.4.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.0.tgz#237b1f7e1c50ed03bc65c03ccc29d7c8bb7beb94" +pretty-format@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f" dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" @@ -9150,13 +9063,6 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" -proxy-addr@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.6.0" - proxy-agent@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499" @@ -9407,10 +9313,6 @@ randexp@^0.4.2: discontinuous-range "1.0.0" ret "~0.1.10" -random-bytes@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" - randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -9764,6 +9666,12 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" +realpath-native@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.0.tgz#7885721a83b43bd5327609f0ddecb2482305fdf0" + dependencies: + util.promisify "^1.0.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -9881,10 +9789,6 @@ regexp-clone@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589" -regexpp@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" - regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -9956,7 +9860,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2, request@^2.55.0, request@^2.74.0, request@^2.79.0, request@^2.81.0, request@^2.83.0: +request@2, request@^2.55.0, request@^2.74.0, request@^2.81.0, request@^2.83.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -10035,33 +9939,6 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.75.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -10092,6 +9969,12 @@ require_optional@~1.0.0: resolve-from "^2.0.0" semver "^5.1.0" +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" @@ -10100,6 +9983,10 @@ resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + resolve-from@~4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -10235,13 +10122,13 @@ samsam@1.x, samsam@^1.1.3: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" sane@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + version "2.5.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.0.tgz#6359cd676f5efd9988b264d8ce3b827dd6b27bec" dependencies: - anymatch "^1.3.0" + anymatch "^2.0.0" exec-sh "^0.2.0" fb-watchman "^2.0.0" - minimatch "^3.0.2" + micromatch "^3.1.4" minimist "^1.1.1" walker "~1.0.5" watch "~0.18.0" @@ -10276,7 +10163,7 @@ sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" -sax@>=0.1.1, sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1: +sax@^1.1.4, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -10415,7 +10302,7 @@ serve-static@1.13.0: parseurl "~1.3.2" send "0.16.0" -serve-static@1.13.2, serve-static@^1.10.0: +serve-static@^1.10.0: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" dependencies: @@ -10511,7 +10398,7 @@ shelljs@^0.7.0: interpret "^1.0.0" rechoir "^0.6.2" -shellwords@^0.1.0: +shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -10832,10 +10719,6 @@ stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" -step@0.0.x: - version "0.0.6" - resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2" - stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" @@ -11129,11 +11012,11 @@ symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" -"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.1, symbol-tree@^3.2.2: +"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" -table@4.0.2, table@^4.0.1: +table@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" dependencies: @@ -11213,6 +11096,16 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +test-exclude@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" + dependencies: + arrify "^1.0.1" + micromatch "^3.1.8" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + text-encoding@0.6.4, text-encoding@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" @@ -11380,7 +11273,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@>=2.3.3, tough-cookie@^2.2.0, tough-cookie@^2.3.2, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: +tough-cookie@>=2.3.3, tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: @@ -11392,7 +11285,7 @@ tr46@^1.0.0: dependencies: punycode "^2.1.0" -tr46@~0.0.1, tr46@~0.0.3: +tr46@~0.0.1: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -11453,13 +11346,6 @@ type-is@~1.6.15: media-typer "0.3.0" mime-types "~2.1.15" -type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.18" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -11534,12 +11420,6 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -uid-safe@~2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" - dependencies: - random-bytes "~1.0.0" - uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" @@ -11721,6 +11601,13 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" @@ -11830,22 +11717,11 @@ watchpack@^1.4.0: chokidar "^1.7.0" graceful-fs "^4.1.2" -webfinger@0.4.x: - version "0.4.2" - resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d" - dependencies: - step "0.0.x" - xml2js "0.1.x" - webidl-conversions@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - -webidl-conversions@^4.0.0, webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: +webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -11912,13 +11788,6 @@ whatwg-url-compat@~0.6.5: dependencies: tr46 "~0.0.1" -whatwg-url@^4.3.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - whatwg-url@^6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" @@ -11939,7 +11808,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1, which@^1.2.10, which@^1.2.12, which@^1.2.9: +which@1, which@^1.2.10, which@^1.2.12, which@^1.2.9, which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: @@ -12065,7 +11934,7 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" -"xml-name-validator@>= 2.0.1 < 3.0.0", xml-name-validator@^2.0.1: +"xml-name-validator@>= 2.0.1 < 3.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" @@ -12073,12 +11942,6 @@ xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" -xml2js@0.1.x: - version "0.1.14" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c" - dependencies: - sax ">=0.1.1" - xml@^1.0.0, xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" @@ -12154,6 +12017,29 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" +yargs-parser@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" + dependencies: + camelcase "^4.1.0" + +yargs@^10.0.3: + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^8.1.0" + yargs@^3.19.0, yargs@^3.32.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" @@ -12240,24 +12126,6 @@ yargs@^8.0.2: y18n "^3.2.1" yargs-parser "^7.0.0" -yargs@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" From 322740d1a94aaf7fcdfd8efad29ab84542a497ba Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 11 Apr 2018 19:29:54 -0600 Subject: [PATCH 2/9] small fixes --- package.json | 3 ++- services/mongoose.js | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 68fb0519e..f5c1764c9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "test": "npm-run-all test:jest test:mocha", "test:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand", "test:client": "TEST_MODE=unit NODE_ENV=test jest --projects client", - "test:mocha": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", + "test:server": "npm-run-all test:server:jest test:server:mocha", + "test:server:mocha": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", "test:server:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand --projects .", "e2e": "./scripts/e2e.js", "e2e:ci": "./scripts/e2e-ci.sh", diff --git a/services/mongoose.js b/services/mongoose.js index 66304ee07..4e242cf47 100644 --- a/services/mongoose.js +++ b/services/mongoose.js @@ -42,10 +42,6 @@ if (WEBPACK) { logger.debug('mongodb disconnected') ); - setTimeout(() => { - mongoose.disconnect(); - }, 5000); - // Connect to the Mongo instance. mongoose .connect(MONGO_URL, { From 68473e5a29cd88c4efd097d49bfc3a1f09a4f696 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 12 Apr 2018 08:36:45 -0600 Subject: [PATCH 3/9] added beforeTest to wait for mongoose to be ready --- test/setupJest.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/setupJest.js b/test/setupJest.js index 9ff2d8af4..6ed78a9c5 100644 --- a/test/setupJest.js +++ b/test/setupJest.js @@ -1,5 +1,15 @@ const mongoose = require('../services/mongoose'); +beforeAll(function(done) { + mongoose.connection.on('open', function(err) { + if (err) { + return done(err); + } + + return done(); + }); +}, 30000); + beforeEach(async () => { await Promise.all( Object.keys(mongoose.connection.collections).map(collection => { From 10d11c1615c5ec04969daaebf9781f2c67d7ccad Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 12 Apr 2018 09:37:17 -0600 Subject: [PATCH 4/9] adjusted index creation and managemen --- .circleci/config.yml | 17 + bin/cli | 1 + bin/cli-db | 53 +++ models/action.js | 53 +-- models/asset.js | 99 +---- models/comment.js | 235 +----------- models/migration.js | 10 +- models/schema/action.js | 51 +++ models/schema/asset.js | 97 +++++ models/schema/comment.js | 235 ++++++++++++ models/schema/index.js | 21 ++ models/schema/migration.js | 8 + models/schema/setting.js | 142 ++++++++ models/schema/user.js | 375 +++++++++++++++++++ models/setting.js | 147 +------- models/user.js | 378 +------------------- plugin-api/beta/server/getReactionConfig.js | 31 +- 17 files changed, 1027 insertions(+), 926 deletions(-) create mode 100755 bin/cli-db create mode 100644 models/schema/action.js create mode 100644 models/schema/asset.js create mode 100644 models/schema/comment.js create mode 100644 models/schema/index.js create mode 100644 models/schema/migration.js create mode 100644 models/schema/setting.js create mode 100644 models/schema/user.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 2fe14ad6e..9272a0572 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,11 +1,26 @@ + +# job_environment will setup the environment for any job being executed. +job_environment: &job_environment + DISABLE_CREATE_MONGO_INDEXES: TRUE + # job_defaults applies all the defaults for each job. job_defaults: &job_defaults working_directory: ~/coralproject/talk docker: - image: circleci/node:8 + environment: + <<: *job_environment + +# create_indexes will create the mongo indexes and wait until they have been +# built. +create_indexes: &create_indexes + run: + name: Create the database indexes and wait until they are built + command: ./bin/cli db createIndexes # integration_environment is the environment that configures the tests. integration_environment: &integration_environment + <<: *job_environment NODE_ENV: test CIRCLE_TEST_REPORTS: /tmp/circleci-test-results E2E_MAX_RETRIES: 3 @@ -25,6 +40,7 @@ integration_job: &integration_job - checkout - attach_workspace: at: ~/coralproject/talk + - <<: *create_indexes - run: name: Setup the database with defaults command: ./bin/cli setup --defaults @@ -117,6 +133,7 @@ jobs: environment: JEST_JUNIT_OUTPUT: /tmp/circleci-test-results/jest/test-results.xml JEST_REPORTER: jest-junit + - <<: *create_indexes - run: name: Run the server unit tests command: yarn test:server diff --git a/bin/cli b/bin/cli index 314f33dd4..df2253261 100755 --- a/bin/cli +++ b/bin/cli @@ -11,6 +11,7 @@ const Matcher = require('did-you-mean'); program .command('serve', 'serve the application') + .command('db', 'run database commands') .command('settings', 'interact with the application settings') .command('assets', 'interact with assets') .command('setup', 'setup the application') diff --git a/bin/cli-db b/bin/cli-db new file mode 100755 index 000000000..1057b1057 --- /dev/null +++ b/bin/cli-db @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +const util = require('./util'); +const program = require('commander'); +const config = require('../config'); + +async function createIndexes() { + try { + // Ensure we enable the index creation. + config.CREATE_MONGO_INDEXES = true; + + // TODO: handle the plugin index creation? + + // Let's register the shutdown hooks. + util.onshutdown([() => require('../services/mongoose').disconnect()]); + + // Lets create all the database indexes for the application and wait for all + // them to finish their indexing. + const models = [ + require('../models/action'), + require('../models/asset'), + require('../models/comment'), + require('../models/setting'), + require('../models/user'), + require('../models/migration'), + ]; + + // Call the `.init()` method to setup all the indexes on each model. + // `init()` returns a promise that resolves when the indexes have finished + // building successfully. The `init()` function is idempotent, so we don't + // have to worry about triggering an index rebuild. + await Promise.all(models.map(Model => Model.init())); + + console.log('Indexes created'); + util.shutdown(0); + } catch (err) { + console.error(err); + util.shutdown(1); + } +} + +program + .command('createIndexes') + .description('creates the database indexes and waits until they are created') + .action(createIndexes); + +program.parse(process.argv); + +// If there is no command listed, output help. +if (process.argv.length <= 2) { + program.outputHelp(); + util.shutdown(); +} diff --git a/models/action.js b/models/action.js index f3414f3ea..dd2bc4377 100644 --- a/models/action.js +++ b/models/action.js @@ -1,53 +1,4 @@ const mongoose = require('../services/mongoose'); -const uuid = require('uuid'); -const Schema = mongoose.Schema; -const ACTION_TYPES = require('./enum/action_types'); -const ITEM_TYPES = require('./enum/item_types'); +const { Action } = require('./schema'); -const ActionSchema = new Schema( - { - id: { - type: String, - default: uuid.v4, - unique: true, - }, - action_type: { - type: String, - enum: ACTION_TYPES, - }, - item_type: { - type: String, - enum: ITEM_TYPES, - }, - item_id: String, - user_id: String, - - // The element that summaries will additionally group on in addtion to their action_type, item_type, and - // item_id. - group_id: String, - - // Additional metadata stored on the field. - metadata: Schema.Types.Mixed, - }, - { - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - } -); - -// Create an index on the `item_id` field so that queries looking for -// actions based on the item id can resolve faster. -ActionSchema.index( - { - item_id: 1, - }, - { - background: true, - } -); - -const Action = mongoose.model('Action', ActionSchema); - -module.exports = Action; +module.exports = mongoose.model('Action', Action); diff --git a/models/asset.js b/models/asset.js index 6fdea3b78..6d7f95220 100644 --- a/models/asset.js +++ b/models/asset.js @@ -1,99 +1,4 @@ const mongoose = require('../services/mongoose'); -const Schema = mongoose.Schema; -const uuid = require('uuid'); -const TagLinkSchema = require('./schema/tag_link'); -const get = require('lodash/get'); +const { Asset } = require('./schema'); -const AssetSchema = new Schema( - { - id: { - type: String, - default: uuid.v4, - unique: true, - index: true, - }, - url: { - type: String, - unique: true, - index: true, - }, - type: { - type: String, - default: 'assets', - }, - scraped: { - type: Date, - default: null, - }, - closedAt: { - type: Date, - default: null, - }, - closedMessage: { - type: String, - default: null, - }, - title: String, - description: String, - image: String, - section: String, - subsection: String, - author: String, - publication_date: Date, - modified_date: Date, - - // This object is used exclusively for storing settings that are to override - // the base settings from the base Settings object. This is to be accessed - // always after running `rectifySettings` against it. - settings: { - default: {}, - type: Object, - }, - - // Tags are added by the self or by administrators. - tags: [TagLinkSchema], - - // Additional metadata stored on the field. - metadata: { - default: {}, - type: Object, - }, - }, - { - versionKey: false, - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - } -); - -AssetSchema.index( - { - title: 'text', - url: 'text', - description: 'text', - section: 'text', - subsection: 'text', - author: 'text', - }, - { - background: true, - } -); - -/** - * Returns true if the asset is closed, false else. - */ -AssetSchema.virtual('isClosed').get(function() { - const closedAt = get(this, 'closedAt', null); - if (closedAt === null) { - return false; - } - - return closedAt.getTime() <= new Date().getTime(); -}); - -const Asset = mongoose.model('Asset', AssetSchema); - -module.exports = Asset; +module.exports = mongoose.model('Asset', Asset); diff --git a/models/comment.js b/models/comment.js index 88f5e5829..f61740ffd 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,235 +1,4 @@ const mongoose = require('../services/mongoose'); -const Schema = mongoose.Schema; -const TagLinkSchema = require('./schema/tag_link'); -const uuid = require('uuid'); -const COMMENT_STATUS = require('./enum/comment_status'); +const { Comment } = require('./schema'); -/** - * The Mongo schema for a Comment Status. - * @type {Schema} - */ -const StatusSchema = new Schema( - { - type: { - type: String, - enum: COMMENT_STATUS, - }, - - // The User ID of the user that assigned the status. - assigned_by: { - type: String, - default: null, - }, - - created_at: Date, - }, - { - _id: false, - } -); - -/** - * A record of old body values for a Comment - */ -const BodyHistoryItemSchema = new Schema({ - body: { - required: true, - type: String, - }, - - // datetime until the comment body value was this.body - created_at: { - required: true, - type: Date, - default: Date, - }, -}); - -/** - * The Mongo schema for a Comment. - * @type {Schema} - */ -const CommentSchema = new Schema( - { - id: { - type: String, - default: uuid.v4, - unique: true, - }, - body: { - type: String, - required: [true, 'The body is required.'], - minlength: 2, - }, - body_history: [BodyHistoryItemSchema], - asset_id: String, - author_id: String, - status_history: [StatusSchema], - status: { - type: String, - enum: COMMENT_STATUS, - default: 'NONE', - }, - - // parent_id is the id of the parent comment (null if there is none). - parent_id: String, - - // The number of replies to this comment directly. - reply_count: { - type: Number, - default: 0, - }, - - // Counts to store related to actions taken on the given comment. - action_counts: { - default: {}, - type: Object, - }, - - // Tags are added by the self or by administrators. - tags: [TagLinkSchema], - - // Additional metadata stored on the field. - metadata: { - default: {}, - type: Object, - }, - }, - { - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - toJSON: { - virtuals: true, - }, - } -); - -// Add the indexes for the id of the comment. -CommentSchema.index( - { - id: 1, - }, - { - unique: true, - background: false, - } -); - -CommentSchema.index( - { - status: 1, - created_at: 1, - }, - { - background: true, - } -); - -CommentSchema.index( - { - status: 1, - created_at: 1, - asset_id: 1, - }, - { - background: true, - } -); - -// Create a sparse index to search across. -CommentSchema.index( - { - created_at: 1, - 'action_counts.flag': 1, - status: 1, - }, - { - background: true, - sparse: true, - } -); - -// Create a sparse index to search across. -CommentSchema.index( - { - 'action_counts.flag': 1, - status: 1, - }, - { - background: true, - sparse: true, - } -); - -// Add an index that is optimized for finding flagged comments. -CommentSchema.index( - { - asset_id: 1, - created_at: 1, - 'action_counts.flag': 1, - }, - { - background: true, - } -); - -// Add an index for the reply sort. -CommentSchema.index( - { - asset_id: 1, - created_at: -1, - reply_count: -1, - }, - { - background: true, - } -); - -// Optimize for tag searches/counts. -CommentSchema.index( - { - asset_id: 1, - 'tags.tag.name': 1, - status: 1, - }, - { - background: true, - } -); - -// Optimize for tag searches/counts. -CommentSchema.index( - { - 'tags.tag.name': 1, - status: 1, - }, - { - background: true, - sparse: true, - } -); - -// Add an index that is optimized for sorting based on the created_at timestamp -// but also good at locating comments that have a specific asset id. -CommentSchema.index( - { - asset_id: 1, - created_at: 1, - }, - { - background: true, - } -); - -CommentSchema.virtual('edited').get(function() { - return this.body_history.length > 1; -}); - -// Visable is true when the comment is visible to the public. -CommentSchema.virtual('visible').get(function() { - return ['ACCEPTED', 'NONE'].includes(this.status); -}); - -module.exports = mongoose.model('Comment', CommentSchema); +module.exports = mongoose.model('Comment', Comment); diff --git a/models/migration.js b/models/migration.js index d60a4c0d6..86982108e 100644 --- a/models/migration.js +++ b/models/migration.js @@ -1,10 +1,4 @@ const mongoose = require('../services/mongoose'); -const Schema = mongoose.Schema; +const { Migration } = require('./schema'); -const MigrationSchema = new Schema({ - version: Number, -}); - -const Migration = mongoose.model('Migration', MigrationSchema); - -module.exports = Migration; +module.exports = mongoose.model('Migration', Migration); diff --git a/models/schema/action.js b/models/schema/action.js new file mode 100644 index 000000000..1df7bd793 --- /dev/null +++ b/models/schema/action.js @@ -0,0 +1,51 @@ +const mongoose = require('../../services/mongoose'); +const uuid = require('uuid'); +const Schema = mongoose.Schema; +const ACTION_TYPES = require('../enum/action_types'); +const ITEM_TYPES = require('../enum/item_types'); + +const Action = new Schema( + { + id: { + type: String, + default: uuid.v4, + unique: true, + }, + action_type: { + type: String, + enum: ACTION_TYPES, + }, + item_type: { + type: String, + enum: ITEM_TYPES, + }, + item_id: String, + user_id: String, + + // The element that summaries will additionally group on in addtion to their action_type, item_type, and + // item_id. + group_id: String, + + // Additional metadata stored on the field. + metadata: Schema.Types.Mixed, + }, + { + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at', + }, + } +); + +// Create an index on the `item_id` field so that queries looking for +// actions based on the item id can resolve faster. +Action.index( + { + item_id: 1, + }, + { + background: true, + } +); + +module.exports = Action; diff --git a/models/schema/asset.js b/models/schema/asset.js new file mode 100644 index 000000000..043bc9a3f --- /dev/null +++ b/models/schema/asset.js @@ -0,0 +1,97 @@ +const mongoose = require('../../services/mongoose'); +const Schema = mongoose.Schema; +const uuid = require('uuid'); +const TagLinkSchema = require('./tag_link'); +const { get } = require('lodash'); + +const Asset = new Schema( + { + id: { + type: String, + default: uuid.v4, + unique: true, + index: true, + }, + url: { + type: String, + unique: true, + index: true, + }, + type: { + type: String, + default: 'assets', + }, + scraped: { + type: Date, + default: null, + }, + closedAt: { + type: Date, + default: null, + }, + closedMessage: { + type: String, + default: null, + }, + title: String, + description: String, + image: String, + section: String, + subsection: String, + author: String, + publication_date: Date, + modified_date: Date, + + // This object is used exclusively for storing settings that are to override + // the base settings from the base Settings object. This is to be accessed + // always after running `rectifySettings` against it. + settings: { + default: {}, + type: Object, + }, + + // Tags are added by the self or by administrators. + tags: [TagLinkSchema], + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object, + }, + }, + { + versionKey: false, + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at', + }, + } +); + +Asset.index( + { + title: 'text', + url: 'text', + description: 'text', + section: 'text', + subsection: 'text', + author: 'text', + }, + { + background: true, + } +); + +/** + * Returns true if the asset is closed, false else. + */ +Asset.virtual('isClosed').get(function() { + const closedAt = get(this, 'closedAt', null); + if (closedAt === null) { + return false; + } + + return closedAt.getTime() <= new Date().getTime(); +}); + +module.exports = Asset; diff --git a/models/schema/comment.js b/models/schema/comment.js new file mode 100644 index 000000000..6ef5434d5 --- /dev/null +++ b/models/schema/comment.js @@ -0,0 +1,235 @@ +const mongoose = require('../../services/mongoose'); +const Schema = mongoose.Schema; +const TagLinkSchema = require('./tag_link'); +const uuid = require('uuid'); +const COMMENT_STATUS = require('../enum/comment_status'); + +/** + * The Mongo schema for a Comment Status. + * @type {Schema} + */ +const Status = new Schema( + { + type: { + type: String, + enum: COMMENT_STATUS, + }, + + // The User ID of the user that assigned the status. + assigned_by: { + type: String, + default: null, + }, + + created_at: Date, + }, + { + _id: false, + } +); + +/** + * A record of old body values for a Comment + */ +const BodyHistoryItemSchema = new Schema({ + body: { + required: true, + type: String, + }, + + // datetime until the comment body value was this.body + created_at: { + required: true, + type: Date, + default: Date, + }, +}); + +/** + * The Mongo schema for a Comment. + * @type {Schema} + */ +const Comment = new Schema( + { + id: { + type: String, + default: uuid.v4, + unique: true, + }, + body: { + type: String, + required: [true, 'The body is required.'], + minlength: 2, + }, + body_history: [BodyHistoryItemSchema], + asset_id: String, + author_id: String, + status_history: [Status], + status: { + type: String, + enum: COMMENT_STATUS, + default: 'NONE', + }, + + // parent_id is the id of the parent comment (null if there is none). + parent_id: String, + + // The number of replies to this comment directly. + reply_count: { + type: Number, + default: 0, + }, + + // Counts to store related to actions taken on the given comment. + action_counts: { + default: {}, + type: Object, + }, + + // Tags are added by the self or by administrators. + tags: [TagLinkSchema], + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object, + }, + }, + { + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at', + }, + toJSON: { + virtuals: true, + }, + } +); + +// Add the indexes for the id of the comment. +Comment.index( + { + id: 1, + }, + { + unique: true, + background: false, + } +); + +Comment.index( + { + status: 1, + created_at: 1, + }, + { + background: true, + } +); + +Comment.index( + { + status: 1, + created_at: 1, + asset_id: 1, + }, + { + background: true, + } +); + +// Create a sparse index to search across. +Comment.index( + { + created_at: 1, + 'action_counts.flag': 1, + status: 1, + }, + { + background: true, + sparse: true, + } +); + +// Create a sparse index to search across. +Comment.index( + { + 'action_counts.flag': 1, + status: 1, + }, + { + background: true, + sparse: true, + } +); + +// Add an index that is optimized for finding flagged comments. +Comment.index( + { + asset_id: 1, + created_at: 1, + 'action_counts.flag': 1, + }, + { + background: true, + } +); + +// Add an index for the reply sort. +Comment.index( + { + asset_id: 1, + created_at: -1, + reply_count: -1, + }, + { + background: true, + } +); + +// Optimize for tag searches/counts. +Comment.index( + { + asset_id: 1, + 'tags.tag.name': 1, + status: 1, + }, + { + background: true, + } +); + +// Optimize for tag searches/counts. +Comment.index( + { + 'tags.tag.name': 1, + status: 1, + }, + { + background: true, + sparse: true, + } +); + +// Add an index that is optimized for sorting based on the created_at timestamp +// but also good at locating comments that have a specific asset id. +Comment.index( + { + asset_id: 1, + created_at: 1, + }, + { + background: true, + } +); + +Comment.virtual('edited').get(function() { + return this.body_history.length > 1; +}); + +// Visible is true when the comment is visible to the public. +Comment.virtual('visible').get(function() { + return ['ACCEPTED', 'NONE'].includes(this.status); +}); + +module.exports = Comment; diff --git a/models/schema/index.js b/models/schema/index.js new file mode 100644 index 000000000..976b437c0 --- /dev/null +++ b/models/schema/index.js @@ -0,0 +1,21 @@ +const { CREATE_MONGO_INDEXES } = require('../../config'); + +const Action = require('./action'); +const Asset = require('./asset'); +const Comment = require('./comment'); +const Migration = require('./migration'); +const Setting = require('./setting'); +const User = require('./user'); + +const schema = { Action, Asset, Comment, Migration, Setting, User }; + +// Provide the schema to each of the plugins so that they can add in indexes if +// it is enabled. +if (CREATE_MONGO_INDEXES) { + const plugins = require('../../services/plugins'); + plugins.get('server', 'indexes').map(({ indexes }) => { + indexes(schema); + }); +} + +module.exports = schema; diff --git a/models/schema/migration.js b/models/schema/migration.js new file mode 100644 index 000000000..a8d0e6db5 --- /dev/null +++ b/models/schema/migration.js @@ -0,0 +1,8 @@ +const mongoose = require('../../services/mongoose'); +const Schema = mongoose.Schema; + +const Migration = new Schema({ + version: Number, +}); + +module.exports = Migration; diff --git a/models/schema/setting.js b/models/schema/setting.js new file mode 100644 index 000000000..5e226e6cb --- /dev/null +++ b/models/schema/setting.js @@ -0,0 +1,142 @@ +const mongoose = require('../../services/mongoose'); +const Schema = mongoose.Schema; +const TagSchema = require('./tag'); +const MODERATION_OPTIONS = require('../enum/moderation_options'); + +/** + * Setting manages application settings that get used on front and backend. + * @type {Schema} + */ +const Setting = new Schema( + { + id: { + type: String, + default: '1', + }, + moderation: { + type: String, + enum: MODERATION_OPTIONS, + default: 'POST', + }, + infoBoxEnable: { + type: Boolean, + default: false, + }, + customCssUrl: { + type: String, + default: '', + }, + infoBoxContent: { + type: String, + default: '', + }, + questionBoxEnable: { + type: Boolean, + default: false, + }, + questionBoxIcon: { + type: String, + default: 'default', + }, + questionBoxContent: { + type: String, + default: '', + }, + premodLinksEnable: { + type: Boolean, + default: false, + }, + organizationName: { + type: String, + }, + autoCloseStream: { + type: Boolean, + default: false, + }, + closedTimeout: { + type: Number, + + // Two weeks default expiry. + default: 60 * 60 * 24 * 7 * 2, + }, + closedMessage: { + type: String, + default: 'Expired', + }, + wordlist: { + banned: { + type: Array, + default: [], + }, + suspect: { + type: Array, + default: [], + }, + }, + charCount: { + type: Number, + default: 5000, + }, + charCountEnable: { + type: Boolean, + default: false, + }, + requireEmailConfirmation: { + type: Boolean, + default: false, + }, + domains: { + whitelist: { + type: Array, + default: ['localhost'], + }, + }, + + // Length of time (in milliseconds) after a comment is posted that it can still be edited by the author + editCommentWindowLength: { + type: Number, + min: [0, 'Edit Comment Window length must be greater than zero'], + default: 30 * 1000, + }, + tags: [TagSchema], + + // Additional metadata to let plugins write settings. + metadata: { + default: {}, + type: Object, + }, + }, + { + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at', + }, + toObject: { + transform: (doc, ret) => { + delete ret._id; + delete ret.__v; + + return ret; + }, + }, + } +); + +/** + * Merges two settings objects. + */ +Setting.method('merge', function(src) { + Setting.eachPath(path => { + // Exclude internal fields... + if (['id', '_id', '__v', 'created_at', 'updated_at'].includes(path)) { + return; + } + + // If the source object contains the path, shallow copy it. + if (path in src) { + this[path] = src[path]; + } + }); +}); + +module.exports = Setting; diff --git a/models/schema/user.js b/models/schema/user.js new file mode 100644 index 000000000..ec9c018cc --- /dev/null +++ b/models/schema/user.js @@ -0,0 +1,375 @@ +const mongoose = require('../../services/mongoose'); +const bcrypt = require('bcryptjs'); +const Schema = mongoose.Schema; +const uuid = require('uuid'); +const TagLink = require('./tag_link'); +const Token = require('./token'); +const can = require('../../perms'); +const { get } = require('lodash'); + +// USER_ROLES is the array of roles that is permissible as a user role. +const USER_ROLES = require('../enum/user_roles'); + +// USER_STATUS_USERNAME is the list of statuses that are supported by storing +// the username state. +const USER_STATUS_USERNAME = require('../enum/user_status_username'); + +// Profile is the mongoose schema defined as the representation of a +// User's profile stored in MongoDB. +const Profile = new Schema( + { + // ID provides the identifier for the user profile, in the case of a local + // provider, the id would be an email, in the case of a social provider, + // the id would be the foreign providers identifier. + id: { + type: String, + required: true, + }, + + // Provider is simply the name attached to the authentication mode. In the + // case of a locally provided profile, this will simply be `local`, or a + // social provider which for Facebook would just be `facebook`. + provider: { + type: String, + required: true, + }, + + // Metadata provides a place to put provider specific details. An example of + // something that could be stored here is the `metadata.confirmed_at` could be + // used by the `local` provider to indicate when the email address was + // confirmed. + metadata: { + type: Schema.Types.Mixed, + }, + }, + { + _id: false, + } +); + +// User is the mongoose schema defined as the representation of a User in +// MongoDB. +const User = new Schema( + { + // This ID represents the most unique identifier for a user, it is generated + // when the user is created as a random uuid. + id: { + type: String, + default: uuid.v4, + unique: true, + required: true, + }, + + // This is sourced from the social provider or set manually during user setup + // and simply provides a name to display for the given user. + username: { + type: String, + required: true, + }, + + // TODO: find a way that we can instead utilize MongoDB 3.4's collation + // options to build the index in a case insenstive manner: + // https://docs.mongodb.com/manual/reference/collation/ + lowercaseUsername: { + type: String, + required: true, + unique: true, + }, + + // This provides a source of identity proof for users who login using the + // local provider. A local provider will be assumed for users who do not + // have any social profiles. + password: String, + + // Profiles describes the array of identities for a given user. Any one user + // can have multiple profiles associated with them, including multiple email + // addresses. + profiles: [Profile], + + // Tokens are the individual personal access tokens for a given user. + tokens: [Token], + + // Role is the specific user role that the user holds. + role: { + type: String, + enum: USER_ROLES, + required: true, + default: 'COMMENTER', + }, + + // Status stores the user status information regarding permissions, + // capabilities and moderation state. + status: { + // Username stores the current user status for the username as well as the + // history of changes. + username: { + // Status stores the current username status. + status: { + type: String, + enum: USER_STATUS_USERNAME, + }, + + // History stores the history of username status changes. + history: [ + { + // Status stores the historical username status. + status: { + type: String, + enum: USER_STATUS_USERNAME, + }, + + // assigned_by stores the user id of the user who assigned this status. + assigned_by: { type: String, default: null }, + + // created_at stores the date when this status was assigned. + created_at: { type: Date, default: Date.now }, + }, + ], + }, + + // Banned stores the current user banned status as well as the history of + // changes. + banned: { + // Status stores the current user banned status. + status: { + type: Boolean, + required: true, + default: false, + }, + history: [ + { + // Status stores the historical banned status. + status: Boolean, + + // assigned_by stores the user id of the user who assigned this status. + assigned_by: { type: String, default: null }, + + // message stores the email content sent to the user. + message: { type: String, default: null }, + + // created_at stores the date when this status was assigned. + created_at: { type: Date, default: Date.now }, + }, + ], + }, + + // Suspension stores the current user suspension status as well as the + // history of changes. + suspension: { + // until is the date that the user is suspended until. + until: { + type: Date, + default: null, + }, + history: [ + { + // until is the date that the user is suspended until. + until: Date, + + // assigned_by stores the user id of the user who assigned this status. + assigned_by: { type: String, default: null }, + + // message stores the email content sent to the user. + message: { type: String, default: null }, + + // created_at stores the date when this status was assigned. + created_at: { type: Date, default: Date.now }, + }, + ], + }, + }, + + // IgnoresUsers is an array of user id's that the current user is ignoring. + ignoresUsers: [String], + + // Counts to store related to actions taken on the given user. + action_counts: { + default: {}, + type: Object, + }, + + // Tags are added by the self or by administrators. + tags: [TagLink], + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object, + }, + }, + { + // This will ensure that we have proper timestamps available on this model. + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at', + }, + + toJSON: { + transform: function(doc, ret) { + delete ret.__v; + delete ret._id; + delete ret.password; + }, + }, + } +); + +// Add the index on the user profile data. +User.index( + { + 'profiles.id': 1, + 'profiles.provider': 1, + }, + { + unique: true, + background: false, + } +); + +User.index( + { + lowercaseUsername: 1, + 'profiles.id': 1, + created_at: -1, + }, + { + background: true, + } +); + +// This query is executed often, to count the number of flagged accounts with +// usernames. +User.index( + { + 'action_counts.flag': 1, + 'status.username.status': 1, + }, + { + background: true, + } +); + +// Sorting users by created at is the default people search. +User.index( + { + created_at: -1, + }, + { + background: true, + } +); + +/** + * returns true if a commenter is staff + */ +User.method('isStaff', function() { + return this.role !== 'COMMENTER'; +}); + +/** + * This verifies that a password is valid. + */ +User.method('verifyPassword', function(password) { + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, res) => { + if (err) { + return reject(err); + } + + if (!res) { + return resolve(false); + } + + return resolve(true); + }); + }); +}); + +/** + * Can returns true if the user is allowed to perform a specific graph + * operation. + */ +User.method('can', function(...actions) { + return can(this, ...actions); +}); + +/** + * firstEmail will return the first email on the user. + */ +User.virtual('firstEmail').get(function() { + const emails = this.emails; + if (emails.length === 0) { + return null; + } + + return emails[0]; +}); + +/** + * emails will return all the emails on a user. + */ +User.virtual('emails').get(function() { + return (this.profiles || []) + .filter(({ provider }) => provider === 'local') + .map(({ id }) => id); +}); + +/** + * hasVerifiedEmail will return true if at least one of the local email accounts + * have their email verified. + */ +User.virtual('hasVerifiedEmail').get(function() { + return this.profiles + .filter(({ provider }) => provider === 'local') + .some(profile => { + const confirmedAt = get(profile, 'metadata.confirmed_at') || null; + + // If the profile doesn't have a metadata field, or it does not have a + // confirmed_at field, or that field is null, then send them back. + return confirmedAt !== null; + }); +}); + +User.virtual('system') + .get(function() { + return this._system; + }) + .set(function(system) { + this._system = system; + }); + +/** + * banned returns true when the user is currently banned, and sets the banned + * status locally. + */ +User.virtual('banned') + .get(function() { + return this.status.banned.status; + }) + .set(function(status) { + this.status.banned.status = status; + this.status.banned.history.push({ + status, + created_at: new Date(), + }); + }); + +/** + * suspended returns true when the user is currently suspended, and sets the + * suspension status locally. + */ +User.virtual('suspended') + .get(function() { + return Boolean( + this.status.suspension.until && this.status.suspension.until > new Date() + ); + }) + .set(function(until) { + this.status.suspension.until = until; + this.status.suspension.history.push({ + until, + created_at: new Date(), + }); + }); + +module.exports = User; diff --git a/models/setting.js b/models/setting.js index 1cca9c989..48a495ad2 100644 --- a/models/setting.js +++ b/models/setting.js @@ -1,147 +1,4 @@ const mongoose = require('../services/mongoose'); -const Schema = mongoose.Schema; -const TagSchema = require('./schema/tag'); -const MODERATION_OPTIONS = require('./enum/moderation_options'); +const { Setting } = require('./schema'); -/** - * SettingSchema manages application settings that get used on front and backend. - * @type {Schema} - */ -const SettingSchema = new Schema( - { - id: { - type: String, - default: '1', - }, - moderation: { - type: String, - enum: MODERATION_OPTIONS, - default: 'POST', - }, - infoBoxEnable: { - type: Boolean, - default: false, - }, - customCssUrl: { - type: String, - default: '', - }, - infoBoxContent: { - type: String, - default: '', - }, - questionBoxEnable: { - type: Boolean, - default: false, - }, - questionBoxIcon: { - type: String, - default: 'default', - }, - questionBoxContent: { - type: String, - default: '', - }, - premodLinksEnable: { - type: Boolean, - default: false, - }, - organizationName: { - type: String, - }, - autoCloseStream: { - type: Boolean, - default: false, - }, - closedTimeout: { - type: Number, - - // Two weeks default expiry. - default: 60 * 60 * 24 * 7 * 2, - }, - closedMessage: { - type: String, - default: 'Expired', - }, - wordlist: { - banned: { - type: Array, - default: [], - }, - suspect: { - type: Array, - default: [], - }, - }, - charCount: { - type: Number, - default: 5000, - }, - charCountEnable: { - type: Boolean, - default: false, - }, - requireEmailConfirmation: { - type: Boolean, - default: false, - }, - domains: { - whitelist: { - type: Array, - default: ['localhost'], - }, - }, - - // Length of time (in milliseconds) after a comment is posted that it can still be edited by the author - editCommentWindowLength: { - type: Number, - min: [0, 'Edit Comment Window length must be greater than zero'], - default: 30 * 1000, - }, - tags: [TagSchema], - - // Additional metadata to let plugins write settings. - metadata: { - default: {}, - type: Object, - }, - }, - { - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - toObject: { - transform: (doc, ret) => { - delete ret._id; - delete ret.__v; - - return ret; - }, - }, - } -); - -/** - * Merges two settings objects. - */ -SettingSchema.method('merge', function(src) { - SettingSchema.eachPath(path => { - // Exclude internal fields... - if (['id', '_id', '__v', 'created_at', 'updated_at'].includes(path)) { - return; - } - - // If the source object contains the path, shallow copy it. - if (path in src) { - this[path] = src[path]; - } - }); -}); - -/** - * The Mongo Mongoose object. - */ -const Setting = mongoose.model('Setting', SettingSchema); - -module.exports = Setting; +module.exports = mongoose.model('Setting', Setting); diff --git a/models/user.js b/models/user.js index 717e43a88..842a8de79 100644 --- a/models/user.js +++ b/models/user.js @@ -1,378 +1,4 @@ const mongoose = require('../services/mongoose'); -const bcrypt = require('bcryptjs'); -const Schema = mongoose.Schema; -const uuid = require('uuid'); -const TagLinkSchema = require('./schema/tag_link'); -const TokenSchema = require('./schema/token'); -const can = require('../perms'); -const { get } = require('lodash'); +const { User } = require('./schema'); -// USER_ROLES is the array of roles that is permissible as a user role. -const USER_ROLES = require('./enum/user_roles'); - -// USER_STATUS_USERNAME is the list of statuses that are supported by storing -// the username state. -const USER_STATUS_USERNAME = require('./enum/user_status_username'); - -// ProfileSchema is the mongoose schema defined as the representation of a -// User's profile stored in MongoDB. -const ProfileSchema = new Schema( - { - // ID provides the identifier for the user profile, in the case of a local - // provider, the id would be an email, in the case of a social provider, - // the id would be the foreign providers identifier. - id: { - type: String, - required: true, - }, - - // Provider is simply the name attached to the authentication mode. In the - // case of a locally provided profile, this will simply be `local`, or a - // social provider which for Facebook would just be `facebook`. - provider: { - type: String, - required: true, - }, - - // Metadata provides a place to put provider specific details. An example of - // something that could be stored here is the `metadata.confirmed_at` could be - // used by the `local` provider to indicate when the email address was - // confirmed. - metadata: { - type: Schema.Types.Mixed, - }, - }, - { - _id: false, - } -); - -// UserSchema is the mongoose schema defined as the representation of a User in -// MongoDB. -const UserSchema = new Schema( - { - // This ID represents the most unique identifier for a user, it is generated - // when the user is created as a random uuid. - id: { - type: String, - default: uuid.v4, - unique: true, - required: true, - }, - - // This is sourced from the social provider or set manually during user setup - // and simply provides a name to display for the given user. - username: { - type: String, - required: true, - }, - - // TODO: find a way that we can instead utilize MongoDB 3.4's collation - // options to build the index in a case insenstive manner: - // https://docs.mongodb.com/manual/reference/collation/ - lowercaseUsername: { - type: String, - required: true, - unique: true, - }, - - // This provides a source of identity proof for users who login using the - // local provider. A local provider will be assumed for users who do not - // have any social profiles. - password: String, - - // Profiles describes the array of identities for a given user. Any one user - // can have multiple profiles associated with them, including multiple email - // addresses. - profiles: [ProfileSchema], - - // Tokens are the individual personal access tokens for a given user. - tokens: [TokenSchema], - - // Role is the specific user role that the user holds. - role: { - type: String, - enum: USER_ROLES, - required: true, - default: 'COMMENTER', - }, - - // Status stores the user status information regarding permissions, - // capabilities and moderation state. - status: { - // Username stores the current user status for the username as well as the - // history of changes. - username: { - // Status stores the current username status. - status: { - type: String, - enum: USER_STATUS_USERNAME, - }, - - // History stores the history of username status changes. - history: [ - { - // Status stores the historical username status. - status: { - type: String, - enum: USER_STATUS_USERNAME, - }, - - // assigned_by stores the user id of the user who assigned this status. - assigned_by: { type: String, default: null }, - - // created_at stores the date when this status was assigned. - created_at: { type: Date, default: Date.now }, - }, - ], - }, - - // Banned stores the current user banned status as well as the history of - // changes. - banned: { - // Status stores the current user banned status. - status: { - type: Boolean, - required: true, - default: false, - }, - history: [ - { - // Status stores the historical banned status. - status: Boolean, - - // assigned_by stores the user id of the user who assigned this status. - assigned_by: { type: String, default: null }, - - // message stores the email content sent to the user. - message: { type: String, default: null }, - - // created_at stores the date when this status was assigned. - created_at: { type: Date, default: Date.now }, - }, - ], - }, - - // Suspension stores the current user suspension status as well as the - // history of changes. - suspension: { - // until is the date that the user is suspended until. - until: { - type: Date, - default: null, - }, - history: [ - { - // until is the date that the user is suspended until. - until: Date, - - // assigned_by stores the user id of the user who assigned this status. - assigned_by: { type: String, default: null }, - - // message stores the email content sent to the user. - message: { type: String, default: null }, - - // created_at stores the date when this status was assigned. - created_at: { type: Date, default: Date.now }, - }, - ], - }, - }, - - // IgnoresUsers is an array of user id's that the current user is ignoring. - ignoresUsers: [String], - - // Counts to store related to actions taken on the given user. - action_counts: { - default: {}, - type: Object, - }, - - // Tags are added by the self or by administrators. - tags: [TagLinkSchema], - - // Additional metadata stored on the field. - metadata: { - default: {}, - type: Object, - }, - }, - { - // This will ensure that we have proper timestamps available on this model. - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - - toJSON: { - transform: function(doc, ret) { - delete ret.__v; - delete ret._id; - delete ret.password; - }, - }, - } -); - -// Add the index on the user profile data. -UserSchema.index( - { - 'profiles.id': 1, - 'profiles.provider': 1, - }, - { - unique: true, - background: false, - } -); - -UserSchema.index( - { - lowercaseUsername: 1, - 'profiles.id': 1, - created_at: -1, - }, - { - background: true, - } -); - -// This query is executed often, to count the number of flagged accounts with -// usernames. -UserSchema.index( - { - 'action_counts.flag': 1, - 'status.username.status': 1, - }, - { - background: true, - } -); - -// Sorting users by created at is the default people search. -UserSchema.index( - { - created_at: -1, - }, - { - background: true, - } -); - -/** - * returns true if a commenter is staff - */ -UserSchema.method('isStaff', function() { - return this.role !== 'COMMENTER'; -}); - -/** - * This verifies that a password is valid. - */ -UserSchema.method('verifyPassword', function(password) { - return new Promise((resolve, reject) => { - bcrypt.compare(password, this.password, (err, res) => { - if (err) { - return reject(err); - } - - if (!res) { - return resolve(false); - } - - return resolve(true); - }); - }); -}); - -/** - * Can returns true if the user is allowed to perform a specific graph - * operation. - */ -UserSchema.method('can', function(...actions) { - return can(this, ...actions); -}); - -/** - * firstEmail will return the first email on the user. - */ -UserSchema.virtual('firstEmail').get(function() { - const emails = this.emails; - if (emails.length === 0) { - return null; - } - - return emails[0]; -}); - -/** - * emails will return all the emails on a user. - */ -UserSchema.virtual('emails').get(function() { - return (this.profiles || []) - .filter(({ provider }) => provider === 'local') - .map(({ id }) => id); -}); - -/** - * hasVerifiedEmail will return true if at least one of the local email accounts - * have their email verified. - */ -UserSchema.virtual('hasVerifiedEmail').get(function() { - return this.profiles - .filter(({ provider }) => provider === 'local') - .some(profile => { - const confirmedAt = get(profile, 'metadata.confirmed_at') || null; - - // If the profile doesn't have a metadata field, or it does not have a - // confirmed_at field, or that field is null, then send them back. - return confirmedAt !== null; - }); -}); - -UserSchema.virtual('system') - .get(function() { - return this._system; - }) - .set(function(system) { - this._system = system; - }); - -/** - * banned returns true when the user is currently banned, and sets the banned - * status locally. - */ -UserSchema.virtual('banned') - .get(function() { - return this.status.banned.status; - }) - .set(function(status) { - this.status.banned.status = status; - this.status.banned.history.push({ - status, - created_at: new Date(), - }); - }); - -/** - * suspended returns true when the user is currently suspended, and sets the - * suspension status locally. - */ -UserSchema.virtual('suspended') - .get(function() { - return Boolean( - this.status.suspension.until && this.status.suspension.until > new Date() - ); - }) - .set(function(until) { - this.status.suspension.until = until; - this.status.suspension.history.push({ - until, - created_at: new Date(), - }); - }); - -// Create the User model. -const UserModel = mongoose.model('User', UserSchema); - -module.exports = UserModel; +module.exports = mongoose.model('User', User); diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index 40832ef74..da075d76c 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -2,24 +2,23 @@ const { SEARCH_OTHER_USERS } = require('../../../perms/constants'); const { ErrNotFound, ErrAlreadyExists } = require('../../../errors'); const pluralize = require('pluralize'); const sc = require('snake-case'); -const CommentModel = require('../../../models/comment'); -const { CREATE_MONGO_INDEXES } = require('../../../config'); +// const { CREATE_MONGO_INDEXES } = require('../../../config'); function getReactionConfig(reaction) { reaction = reaction.toLowerCase(); - if (CREATE_MONGO_INDEXES) { - // Create the index on the comment model based on the reaction config. - CommentModel.collection.createIndex( - { - created_at: 1, - [`action_counts.${sc(reaction)}`]: 1, - }, - { - background: true, - } - ); - } + // if (CREATE_MONGO_INDEXES) { + // // Create the index on the comment model based on the reaction config. + // CommentModel.collection.createIndex( + // { + // created_at: 1, + // [`action_counts.${sc(reaction)}`]: 1, + // }, + // { + // background: true, + // } + // ); + // } const reactionPlural = pluralize(reaction); const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1); @@ -128,8 +127,8 @@ function getReactionConfig(reaction) { return { typeDefs, - schemas: ({ CommentSchema }) => { - CommentSchema.index( + indexes: ({ Comment }) => { + Comment.index( { created_at: 1, [`action_counts.${sc(reaction)}`]: 1, From 517852908302472a86d857394a08ff96e8b98fee Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 12 Apr 2018 09:44:17 -0600 Subject: [PATCH 5/9] adjusted env --- .circleci/config.yml | 2 +- package.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9272a0572..848d496e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,7 @@ # job_environment will setup the environment for any job being executed. job_environment: &job_environment + NODE_ENV: test DISABLE_CREATE_MONGO_INDEXES: TRUE # job_defaults applies all the defaults for each job. @@ -21,7 +22,6 @@ create_indexes: &create_indexes # integration_environment is the environment that configures the tests. integration_environment: &integration_environment <<: *job_environment - NODE_ENV: test CIRCLE_TEST_REPORTS: /tmp/circleci-test-results E2E_MAX_RETRIES: 3 diff --git a/package.json b/package.json index f5c1764c9..b0a37528e 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "lint": "npm-run-all lint:*", "plugins:reconcile": "./bin/cli plugins reconcile", "test": "npm-run-all test:jest test:mocha", - "test:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand", - "test:client": "TEST_MODE=unit NODE_ENV=test jest --projects client", + "test:jest": "NODE_ENV=test jest --runInBand", + "test:client": "NODE_ENV=test jest --projects client", "test:server": "npm-run-all test:server:jest test:server:mocha", - "test:server:mocha": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", - "test:server:jest": "TEST_MODE=unit NODE_ENV=test jest --runInBand --projects .", + "test:server:mocha": "NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}", + "test:server:jest": "NODE_ENV=test jest --runInBand --projects .", "e2e": "./scripts/e2e.js", "e2e:ci": "./scripts/e2e-ci.sh", "heroku-postbuild": "npm-run-all plugins:reconcile build", From e5a2152cfdfc1e3b64911fa5b9e3b7833060c75a Mon Sep 17 00:00:00 2001 From: Kit Westneat Date: Thu, 12 Apr 2018 17:51:01 -0400 Subject: [PATCH 6/9] don't show profile tab if user is logged out --- client/coral-embed-stream/src/components/Embed.js | 2 ++ .../src/components/ExtendableTabPanel.js | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 0c4fed129..cb4803a2d 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -16,6 +16,8 @@ import cn from 'classnames'; export default class Embed extends React.Component { getTabs() { + if (!this.props.currentUser) return false; + const tabs = [ - - {tabs} - + {tabs && ( + + {tabs} + + )} {loading ? (
From a6344d8e717f5e83d13dc30bd1fbb7f66bc26e49 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 17 Apr 2018 13:19:03 -0600 Subject: [PATCH 7/9] fixed proptype missing validation --- .../src/components/Embed.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index cb4803a2d..a2476527e 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -16,8 +16,6 @@ import cn from 'classnames'; export default class Embed extends React.Component { getTabs() { - if (!this.props.currentUser) return false; - const tabs = [ {t('embed_comments_tab')} , - - {t('framework.my_profile')} - , ]; + + if (this.props.currentUser) { + tabs.push( + + {t('framework.my_profile')} + + ); + } + if (can(this.props.currentUser, 'UPDATE_ASSET_CONFIG')) { tabs.push( ); } + return tabs; } From a257fdb623f838a869cc9a060feca3de6478e7a3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 17 Apr 2018 13:23:02 -0600 Subject: [PATCH 8/9] removed deprecated not logged in ui for profile tab --- .../tabs/profile/components/NotLoggedIn.css | 14 ---------- .../tabs/profile/components/NotLoggedIn.js | 15 ----------- .../src/tabs/profile/containers/Profile.js | 27 +++---------------- 3 files changed, 4 insertions(+), 52 deletions(-) delete mode 100644 client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.css delete mode 100644 client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.js diff --git a/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.css b/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.css deleted file mode 100644 index c15c44c98..000000000 --- a/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.css +++ /dev/null @@ -1,14 +0,0 @@ -.message { - padding: 10px 0 20px; - letter-spacing: 0.1px; - font-size: 13px; - line-height: 33px; -} - -.message a { - color: black; - font-weight: bold; - cursor: pointer; - margin: 0px; - padding-bottom: 2px; -} diff --git a/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.js b/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.js deleted file mode 100644 index 4987d57be..000000000 --- a/client/coral-embed-stream/src/tabs/profile/components/NotLoggedIn.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import styles from './NotLoggedIn.css'; -import cn from 'classnames'; - -import t from 'coral-framework/services/i18n'; - -export default ({ showSignInDialog }) => ( -
-
- {t('settings.sign_in')}{' '} - {t('settings.to_access')} -
-
{t('from_settings_page')}
-
-); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js index 30d634d94..84584ae5d 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js @@ -2,27 +2,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { compose, gql } from 'react-apollo'; -import { bindActionCreators } from 'redux'; import { withQuery } from 'coral-framework/hocs'; -import NotLoggedIn from '../components/NotLoggedIn'; import { Spinner } from 'coral-ui'; import Profile from '../components/Profile'; import TabPanel from './TabPanel'; import { getDefinitionName } from 'coral-framework/utils'; -import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; import { getSlotFragmentSpreads } from 'coral-framework/utils'; class ProfileContainer extends Component { - componentWillReceiveProps(nextProps) { - if (!this.props.currentUser && nextProps.currentUser) { - // Refetch after login. - this.props.data.refetch(); - } - } - render() { - const { currentUser, showSignInDialog, root } = this.props; + const { currentUser, root } = this.props; const { me } = this.props.root; const loading = this.props.data.loading; @@ -30,10 +20,6 @@ class ProfileContainer extends Component { return
{this.props.data.error.message}
; } - if (!currentUser) { - return ; - } - if (loading || !me) { return ; } @@ -57,7 +43,6 @@ ProfileContainer.propTypes = { data: PropTypes.object, root: PropTypes.object, currentUser: PropTypes.object, - showSignInDialog: PropTypes.func, }; const slots = ['profileSections']; @@ -85,10 +70,6 @@ const mapStateToProps = state => ({ currentUser: state.auth.user, }); -const mapDispatchToProps = dispatch => - bindActionCreators({ showSignInDialog }, dispatch); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - withProfileQuery -)(ProfileContainer); +export default compose(connect(mapStateToProps), withProfileQuery)( + ProfileContainer +); From b6299c2d406c75c1bb208bc6014387d59f4d31e3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 17 Apr 2018 13:35:30 -0600 Subject: [PATCH 9/9] fix e2e test --- test/e2e/specs/03_embedStream.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index f45c467e8..4b6831271 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -96,12 +96,6 @@ module.exports = { comments.logout(); }, - 'not logged in user clicks my profile tab': client => { - const embedStream = client.page.embedStream(); - const profile = embedStream.goToProfileSection(); - - profile.assert.visible('@notLoggedIn'); - }, 'admin logs in': client => { const { testData: { admin } } = client.globals; const embedStream = client.page.embedStream();