From 08b16094404a4ca6ee5286f820363ca01c4bb98f Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Wed, 26 Apr 2017 21:47:03 -0700 Subject: [PATCH] [chore] added prettier --- .eslintrc | 6 +- frontend/components/Switch.js | 35 +- frontend/scenes/Application.js | 25 +- frontend/stores/CacheStore.js | 12 +- frontend/stores/UiStore.js | 4 +- frontend/stores/UserStore.js | 8 +- frontend/utils/ApiClient.js | 77 +- frontend/utils/actions.js | 12 +- frontend/utils/emojify.js | 2 +- frontend/utils/markdown.js | 12 +- frontend/utils/random.js | 8 +- frontend/utils/testUtils.js | 6 +- package.json | 14 +- server/api/apiKeys.js | 18 +- server/api/auth.js | 74 +- server/api/auth.test.js | 1 - server/api/collections.js | 27 +- server/api/documents.js | 56 +- server/api/hooks.js | 15 +- server/api/user.js | 47 +- server/middlewares/cache.js | 2 +- server/middlewares/methodOverride.js | 2 +- server/middlewares/subdomainRedirect.js | 7 +- server/migrations/20160619080644-initial.js | 89 +- .../20160622043741-add-parent-document.js | 18 +- .../migrations/20160626063409-add-indexes.js | 6 +- .../20160626175224-add-revisions.js | 78 +- .../migrations/20160711071958-search-index.js | 6 +- .../20160726061511-atlas-creator.js | 18 +- ...160812145029-document-atlas-soft-delete.js | 30 +- .../20160814083127-paranoia-indeces.js | 18 +- ...20160814095336-add-document-createdById.js | 22 +- ...0814111419-add-document-collaboratorIds.js | 12 +- .../20160815142720-app-collection-urlId.js | 18 +- .../20160816082738-add-revision-index.js | 4 +- .../migrations/20160824061730-add-apikeys.js | 4 +- .../20160824062457-add-apikey-indeces.js | 4 +- .../20160911230444-user-optional-slack-id.js | 60 +- .../20160911232911-user-unique-fields.js | 60 +- server/models/ApiKey.js | 47 +- server/models/Atlas.js | 344 +++---- server/models/Document.js | 121 ++- server/models/Revision.js | 15 +- server/models/Team.js | 57 +- server/models/User.js | 96 +- server/models/index.js | 9 +- server/presenters/user.test.js | 32 +- server/static/service-worker.js | 890 +++++++++++++++++- server/test/helper.js | 3 +- server/test/support.js | 10 +- server/utils/s3.js | 25 +- server/utils/truncate.js | 6 +- yarn.lock | 339 ++++++- 53 files changed, 1983 insertions(+), 928 deletions(-) diff --git a/.eslintrc b/.eslintrc index a4d6dcb0e..49166483b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,9 @@ { - "extends": "airbnb", + "extends": [ + "airbnb", + "prettier", + "prettier/react" + ], "parser": "babel-eslint", "rules": { "arrow-body-style":[0, "as-needed"], // fix `this` shortcut on ES6 classes diff --git a/frontend/components/Switch.js b/frontend/components/Switch.js index c897940dd..1c983e400 100644 --- a/frontend/components/Switch.js +++ b/frontend/components/Switch.js @@ -8,10 +8,7 @@ import { actionColor } from 'styles/constants.scss'; * Binary toggle switch component */ -const Switch = observer(({ - checked, - ...props -}) => { +const Switch = observer(({ checked, ...props }) => { const scale = '18'; const colors = { success: actionColor, @@ -19,9 +16,8 @@ const Switch = observer(({ }; const borderColor = actionColor; - - const color = checked ? colors.success : borderColor - const transform = checked ? `translateX(${scale * 0.5}px)` : 'translateX(0)' + const color = checked ? colors.success : borderColor; + const transform = checked ? `translateX(${scale * 0.5}px)` : 'translateX(0)'; const sx = { root: { @@ -32,7 +28,7 @@ const Switch = observer(({ backgroundColor: checked ? 'currentcolor' : null, borderRadius: 99999, boxShadow: 'inset 0 0 0 2px', - cursor: 'pointer' + cursor: 'pointer', }, dot: { width: scale, @@ -44,29 +40,30 @@ const Switch = observer(({ boxShadow: 'inset 0 0 0 2px', borderRadius: 99999, color, - backgroundColor: colors.white - } - } + backgroundColor: colors.white, + }, + }; return ( + baseStyle={sx.root} + >
- ) + ); }); Switch.propTypes = { /** Sets the Switch to an active style */ - checked: React.PropTypes.bool -} + checked: React.PropTypes.bool, +}; Switch.contextTypes = { - rebass: React.PropTypes.object -} + rebass: React.PropTypes.object, +}; export default Switch; diff --git a/frontend/scenes/Application.js b/frontend/scenes/Application.js index bc2eee0c9..d9043bbf3 100644 --- a/frontend/scenes/Application.js +++ b/frontend/scenes/Application.js @@ -2,15 +2,14 @@ import React from 'react'; import { observer } from 'mobx-react'; import Helmet from 'react-helmet'; -@observer -class Application extends React.Component { +@observer class Application extends React.Component { static childContextTypes = { rebass: React.PropTypes.object, - } + }; static propTypes = { children: React.PropTypes.node.isRequired, - } + }; getChildContext() { return { @@ -25,13 +24,7 @@ class Application extends React.Component { // fontSizes: [64, 48, 28, 20, 18, 16, 14], bold: 500, - scale: [ - 0, - 8, - 18, - 36, - 72, - ], + scale: [0, 8, 18, 36, 72], Input: { // borderBottom: '1px solid #eee', }, @@ -43,9 +36,7 @@ class Application extends React.Component { ButtonOutline: { color: '#000', }, - InlineForm: { - - }, + InlineForm: {}, }, }; } @@ -55,14 +46,14 @@ class Application extends React.Component {
- { this.props.children } + {this.props.children}
); } diff --git a/frontend/stores/CacheStore.js b/frontend/stores/CacheStore.js index 75e885c88..077022bf2 100644 --- a/frontend/stores/CacheStore.js +++ b/frontend/stores/CacheStore.js @@ -21,13 +21,13 @@ class CacheStore { _.defer(() => localStorage.setItem(CACHE_STORE, this.asJson)); }; - @action cacheList = (data) => { - data.forEach((item) => this.cacheWithId(item.id, item)); + @action cacheList = data => { + data.forEach(item => this.cacheWithId(item.id, item)); }; - @action fetchFromCache = (id) => { + @action fetchFromCache = id => { return this.cache[id]; - } + }; constructor() { // Rehydrate @@ -37,6 +37,4 @@ class CacheStore { } export default CacheStore; -export { - CACHE_STORE, -}; +export { CACHE_STORE }; diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index 4b837da58..f3b1829c7 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -27,6 +27,4 @@ class UiStore { } export default UiStore; -export { - UI_STORE, -}; +export { UI_STORE }; diff --git a/frontend/stores/UserStore.js b/frontend/stores/UserStore.js index 222572d17..dd369412e 100644 --- a/frontend/stores/UserStore.js +++ b/frontend/stores/UserStore.js @@ -40,7 +40,7 @@ class UserStore { const state = Math.random().toString(36).substring(7); this.oauthState = state; return this.oauthState; - } + }; @action authWithSlack = async (code, state, redirectTo) => { if (state !== this.oauthState) { @@ -60,7 +60,7 @@ class UserStore { this.team = res.data.team; this.token = res.data.accessToken; browserHistory.replace(redirectTo || '/'); - } + }; constructor() { // Rehydrate @@ -73,6 +73,4 @@ class UserStore { } export default UserStore; -export { - USER_STORE, -}; +export { USER_STORE }; diff --git a/frontend/utils/ApiClient.js b/frontend/utils/ApiClient.js index c4d4c35ee..7fe48878e 100644 --- a/frontend/utils/ApiClient.js +++ b/frontend/utils/ApiClient.js @@ -7,7 +7,7 @@ import constants from '../constants'; const isIterable = object => object != null && typeof object[Symbol.iterator] === 'function'; -const cacheResponse = (data) => { +const cacheResponse = data => { if (isIterable(data)) { stores.cache.cacheList(data); } else { @@ -51,59 +51,58 @@ class ApiClient { // Handle request promises and return a new promise return new Promise((resolve, reject) => { request - .then((response) => { - // Handle successful responses - if (response.status >= 200 && response.status < 300) { - return response; - } + .then(response => { + // Handle successful responses + if (response.status >= 200 && response.status < 300) { + return response; + } - // Handle 404 - if (response.status === 404) { - return browserHistory.push('/404'); - } + // Handle 404 + if (response.status === 404) { + return browserHistory.push('/404'); + } - // Handle 401, log out user - if (response.status === 401) { - return stores.user.logout(); - } + // Handle 401, log out user + if (response.status === 401) { + return stores.user.logout(); + } - // Handle failed responses - const error = {}; - error.statusCode = response.status; - error.response = response; - throw error; - }) - .then((response) => { - return response.json(); - }) - .then((json) => { - // Cache responses - if (options.cache) { - cacheResponse(json.data); - } - resolve(json); - }) - .catch(error => { - error.response.json() + // Handle failed responses + const error = {}; + error.statusCode = response.status; + error.response = response; + throw error; + }) + .then(response => { + return response.json(); + }) .then(json => { - error.data = json; - reject(error); + // Cache responses + if (options.cache) { + cacheResponse(json.data); + } + resolve(json); + }) + .catch(error => { + error.response.json().then(json => { + error.data = json; + reject(error); + }); }); - }); }); - } + }; get = (path, data, options) => { return this.fetch(path, 'GET', data, options); - } + }; post = (path, data, options) => { return this.fetch(path, 'POST', data, options); - } + }; // Helpers - constructQueryString = (data) => { + constructQueryString = data => { return _.map(data, (v, k) => { return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; }).join('&'); diff --git a/frontend/utils/actions.js b/frontend/utils/actions.js index dea6f1bfc..17bd12ea1 100644 --- a/frontend/utils/actions.js +++ b/frontend/utils/actions.js @@ -1,9 +1,9 @@ export default (type, ...argNames) => { return function(...args) { - let action = { type } + let action = { type }; argNames.forEach((arg, index) => { - action[argNames[index]] = args[index] - }) - return action - } -} + action[argNames[index]] = args[index]; + }); + return action; + }; +}; diff --git a/frontend/utils/emojify.js b/frontend/utils/emojify.js index 7d2442f5d..4ed97796f 100644 --- a/frontend/utils/emojify.js +++ b/frontend/utils/emojify.js @@ -2,7 +2,7 @@ import emojiMapping from './emoji-mapping.json'; const EMOJI_REGEX = /:([A-Za-z0-9_\-\+]+?):/gm; -const emojify = (text='') => { +const emojify = (text = '') => { const emojis = text.match(EMOJI_REGEX) || []; let emojifiedText = text; diff --git a/frontend/utils/markdown.js b/frontend/utils/markdown.js index 281c7c756..f4b49373d 100644 --- a/frontend/utils/markdown.js +++ b/frontend/utils/markdown.js @@ -12,7 +12,9 @@ const Renderer = sanitizedRenderer(marked.Renderer); const renderer = new Renderer(); renderer.code = (code, language) => { const validLang = !!(language && highlight.getLanguage(language)); - const highlighted = validLang ? highlight.highlight(language, code).value : _.escape(code); + const highlighted = validLang + ? highlight.highlight(language, code).value + : _.escape(code); return `
${highlighted}
`; }; renderer.heading = (text, level) => { @@ -25,10 +27,10 @@ renderer.heading = (text, level) => { `; }; -const convertToMarkdown = (text) => { +const convertToMarkdown = text => { // Add TOC text = toc.insert(text || '', { - slugify: (heading) => { + slugify: heading => { // FIXME: E.g. `&` gets messed up const headingSlug = _.escape(slug(heading)); return headingSlug; @@ -46,6 +48,4 @@ const convertToMarkdown = (text) => { }); }; -export { - convertToMarkdown, -}; +export { convertToMarkdown }; diff --git a/frontend/utils/random.js b/frontend/utils/random.js index 50c05eaf4..5604e9071 100644 --- a/frontend/utils/random.js +++ b/frontend/utils/random.js @@ -1,7 +1,5 @@ const randomInteger = (min, max) => { - return Math.floor(Math.random()*(max-min+1)+min); -} + return Math.floor(Math.random() * (max - min + 1) + min); +}; -export { - randomInteger -}; \ No newline at end of file +export { randomInteger }; diff --git a/frontend/utils/testUtils.js b/frontend/utils/testUtils.js index 9eae17b43..8570c9d4a 100644 --- a/frontend/utils/testUtils.js +++ b/frontend/utils/testUtils.js @@ -1,10 +1,8 @@ import renderer from 'react-test-renderer'; -const snap = (children) => { +const snap = children => { const component = renderer.create(children); expect(component).toMatchSnapshot(); }; -export { - snap, -}; +export { snap }; diff --git a/package.json b/package.json index 6584ea403..1514255eb 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,20 @@ "start": "node index.js", "dev": "cross-env NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js", "lint": "eslint frontend", + "prettier": "prettier --single-quote --trailing-comma es5 --write frontend/**/*.js server/**/*.js", "deploy": "git push heroku master", "heroku-postbuild": "npm run build && npm run sequelize db:migrate", "sequelize": "./node_modules/.bin/sequelize", "test": "npm run test:frontend && npm run test:server", "test:frontend": "jest", - "test:server": "jest --config=server/.jest-config --runInBand" + "test:server": "jest --config=server/.jest-config --runInBand", + "precommit": "lint-staged" + }, + "lint-staged": { + "*.js": [ + "yarn prettier", + "git add" + ] }, "jest": { "verbose": false, @@ -150,14 +158,18 @@ "devDependencies": { "babel-jest": "^15.0.0", "enzyme": "^2.4.1", + "eslint-config-prettier": "^1.7.0", + "eslint-plugin-prettier": "^2.0.1", "fetch-test-server": "^1.1.0", "identity-obj-proxy": "^3.0.0", "ignore-loader": "0.1.1", "jest-cli": "^15.1.1", "koa-webpack-dev-middleware": "1.2.0", "koa-webpack-hot-middleware": "1.0.3", + "lint-staged": "^3.4.0", "node-dev": "3.1.0", "nodemon": "1.9.1", + "prettier": "^1.2.2", "react-addons-test-utils": "^15.3.1", "react-test-renderer": "^15.3.1" } diff --git a/server/api/apiKeys.js b/server/api/apiKeys.js index 3570334e3..0a60efa89 100644 --- a/server/api/apiKeys.js +++ b/server/api/apiKeys.js @@ -8,10 +8,8 @@ import { ApiKey } from '../models'; const router = new Router(); -router.post('apiKeys.create', auth(), async (ctx) => { - const { - name, - } = ctx.body; +router.post('apiKeys.create', auth(), async ctx => { + const { name } = ctx.body; ctx.assertPresent(name, 'name is required'); const user = ctx.state.user; @@ -26,15 +24,13 @@ router.post('apiKeys.create', auth(), async (ctx) => { }; }); -router.post('apiKeys.list', auth(), pagination(), async (ctx) => { +router.post('apiKeys.list', auth(), pagination(), async ctx => { const user = ctx.state.user; const keys = await ApiKey.findAll({ where: { userId: user.id, }, - order: [ - ['createdAt', 'DESC'], - ], + order: [['createdAt', 'DESC']], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, }); @@ -49,10 +45,8 @@ router.post('apiKeys.list', auth(), pagination(), async (ctx) => { }; }); -router.post('apiKeys.delete', auth(), async (ctx) => { - const { - id, - } = ctx.body; +router.post('apiKeys.delete', auth(), async ctx => { + const { id } = ctx.body; ctx.assertPresent(id, 'id is required'); const user = ctx.state.user; diff --git a/server/api/auth.js b/server/api/auth.js index ec666231d..e2069627c 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -9,7 +9,7 @@ import { User, Team } from '../models'; const router = new Router(); -router.post('auth.signup', async (ctx) => { +router.post('auth.signup', async ctx => { const { username, name, email, password } = ctx.request.body; ctx.assertPresent(username, 'name is required'); @@ -19,11 +19,19 @@ router.post('auth.signup', async (ctx) => { ctx.assertPresent(password, 'password is required'); if (await User.findOne({ where: { email } })) { - throw apiError(400, 'user_exists_with_email', 'User already exists with this email'); + throw apiError( + 400, + 'user_exists_with_email', + 'User already exists with this email' + ); } if (await User.findOne({ where: { username } })) { - throw apiError(400, 'user_exists_with_username', 'User already exists with this username'); + throw apiError( + 400, + 'user_exists_with_username', + 'User already exists with this username' + ); } const user = await User.create({ @@ -33,13 +41,15 @@ router.post('auth.signup', async (ctx) => { password, }); - ctx.body = { data: { - user: await presentUser(ctx, user), - accessToken: user.getJwtToken(), - } }; + ctx.body = { + data: { + user: await presentUser(ctx, user), + accessToken: user.getJwtToken(), + }, + }; }); -router.post('auth.login', async (ctx) => { +router.post('auth.login', async ctx => { const { username, password } = ctx.request.body; ctx.assertPresent(username, 'username/email is required'); @@ -47,10 +57,9 @@ router.post('auth.login', async (ctx) => { let user; if (username) { - user = await User.findOne({ where: Sequelize.or( - { email: username }, - { username }, - ) }); + user = await User.findOne({ + where: Sequelize.or({ email: username }, { username }), + }); } else { throw apiError(400, 'invalid_credentials', 'username or email is invalid'); } @@ -67,13 +76,15 @@ router.post('auth.login', async (ctx) => { throw apiError(400, 'invalid_password', 'Invalid password'); } - ctx.body = { data: { - user: await presentUser(ctx, user), - accessToken: user.getJwtToken(), - } }; + ctx.body = { + data: { + user: await presentUser(ctx, user), + accessToken: user.getJwtToken(), + }, + }; }); -router.post('auth.slack', async (ctx) => { +router.post('auth.slack', async ctx => { const { code } = ctx.body; ctx.assertPresent(code, 'code is required'); @@ -86,7 +97,9 @@ router.post('auth.slack', async (ctx) => { let data; try { - const response = await fetch(`https://slack.com/api/oauth.access?${querystring.stringify(body)}`); + const response = await fetch( + `https://slack.com/api/oauth.access?${querystring.stringify(body)}` + ); data = await response.json(); } catch (e) { throw httpErrors.BadRequest(); @@ -97,7 +110,11 @@ router.post('auth.slack', async (ctx) => { // Temp to block const allowedSlackDomains = process.env.ALLOWED_SLACK_DOMAINS.split(','); if (!allowedSlackDomains.includes(data.team.domain)) { - throw apiError(400, 'invalid_slack_team', 'Atlas is currently in private beta'); + throw apiError( + 400, + 'invalid_slack_team', + 'Atlas is currently in private beta' + ); } // User @@ -138,14 +155,16 @@ router.post('auth.slack', async (ctx) => { await team.createFirstAtlas(user.id); } - ctx.body = { data: { - user: await presentUser(ctx, user), - team: await presentTeam(ctx, team), - accessToken: user.getJwtToken(), - } }; + ctx.body = { + data: { + user: await presentUser(ctx, user), + team: await presentTeam(ctx, team), + accessToken: user.getJwtToken(), + }, + }; }); -router.post('auth.slackCommands', async (ctx) => { +router.post('auth.slackCommands', async ctx => { const { code } = ctx.body; ctx.assertPresent(code, 'code is required'); @@ -158,7 +177,9 @@ router.post('auth.slackCommands', async (ctx) => { let data; try { - const response = await fetch(`https://slack.com/api/oauth.access?${querystring.stringify(body)}`); + const response = await fetch( + `https://slack.com/api/oauth.access?${querystring.stringify(body)}` + ); data = await response.json(); } catch (e) { throw httpErrors.BadRequest(); @@ -167,5 +188,4 @@ router.post('auth.slackCommands', async (ctx) => { if (!data.ok) throw httpErrors.BadRequest(data.error); }); - export default router; diff --git a/server/api/auth.test.js b/server/api/auth.test.js index 68c43f00f..4ab3ddf47 100644 --- a/server/api/auth.test.js +++ b/server/api/auth.test.js @@ -37,7 +37,6 @@ describe('#auth.signup', async () => { expect(body).toMatchSnapshot(); }); - it('should require valid email', async () => { const res = await server.post('/api/auth.signup', { body: { diff --git a/server/api/collections.js b/server/api/collections.js index 46b5824b7..86650a9e4 100644 --- a/server/api/collections.js +++ b/server/api/collections.js @@ -9,12 +9,8 @@ import { Atlas } from '../models'; const router = new Router(); -router.post('collections.create', auth(), async (ctx) => { - const { - name, - description, - type, - } = ctx.body; +router.post('collections.create', auth(), async ctx => { + const { name, description, type } = ctx.body; ctx.assertPresent(name, 'name is required'); const user = ctx.state.user; @@ -32,7 +28,7 @@ router.post('collections.create', auth(), async (ctx) => { }; }); -router.post('collections.info', auth(), async (ctx) => { +router.post('collections.info', auth(), async ctx => { const { id } = ctx.body; ctx.assertPresent(id, 'id is required'); @@ -51,25 +47,24 @@ router.post('collections.info', auth(), async (ctx) => { }; }); - -router.post('collections.list', auth(), pagination(), async (ctx) => { +router.post('collections.list', auth(), pagination(), async ctx => { const user = ctx.state.user; const collections = await Atlas.findAll({ where: { teamId: user.teamId, }, - order: [ - ['updatedAt', 'DESC'], - ], + order: [['updatedAt', 'DESC']], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, }); // Atlases let data = []; - await Promise.all(collections.map(async (atlas) => { - return data.push(await presentCollection(ctx, atlas, true)); - })); + await Promise.all( + collections.map(async atlas => { + return data.push(await presentCollection(ctx, atlas, true)); + }) + ); data = _.orderBy(data, ['updatedAt'], ['desc']); @@ -79,7 +74,7 @@ router.post('collections.list', auth(), pagination(), async (ctx) => { }; }); -router.post('collections.updateNavigationTree', auth(), async (ctx) => { +router.post('collections.updateNavigationTree', auth(), async ctx => { const { id, tree } = ctx.body; ctx.assertPresent(id, 'id is required'); diff --git a/server/api/documents.js b/server/api/documents.js index 4ffc018a6..537997cb9 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -12,7 +12,7 @@ import { Document, Atlas } from '../models'; const router = new Router(); -const getDocumentForId = async (id) => { +const getDocumentForId = async id => { try { let document; if (isUUID(id)) { @@ -38,7 +38,7 @@ const getDocumentForId = async (id) => { }; // FIXME: This really needs specs :/ -router.post('documents.info', auth(), async (ctx) => { +router.post('documents.info', auth(), async ctx => { const { id } = ctx.body; ctx.assertPresent(id, 'id is required'); const document = await getDocumentForId(id); @@ -69,7 +69,7 @@ router.post('documents.info', auth(), async (ctx) => { } }); -router.post('documents.search', auth(), async (ctx) => { +router.post('documents.search', auth(), async ctx => { const { query } = ctx.body; ctx.assertPresent(query, 'query is required'); @@ -78,12 +78,16 @@ router.post('documents.search', auth(), async (ctx) => { const documents = await Document.searchForUser(user, query); const data = []; - await Promise.all(documents.map(async (document) => { - data.push(await presentDocument(ctx, document, { - includeCollection: true, - includeCollaborators: true, - })); - })); + await Promise.all( + documents.map(async document => { + data.push( + await presentDocument(ctx, document, { + includeCollection: true, + includeCollaborators: true, + }) + ); + }) + ); ctx.body = { pagination: ctx.state.pagination, @@ -91,14 +95,8 @@ router.post('documents.search', auth(), async (ctx) => { }; }); - -router.post('documents.create', auth(), async (ctx) => { - const { - collection, - title, - text, - parentDocument, - } = ctx.body; +router.post('documents.create', auth(), async ctx => { + const { collection, title, text, parentDocument } = ctx.body; ctx.assertPresent(collection, 'collection is required'); ctx.assertPresent(title, 'title is required'); ctx.assertPresent(text, 'text is required'); @@ -115,7 +113,7 @@ router.post('documents.create', auth(), async (ctx) => { const document = await (() => { return new Promise(resolve => { - lock(ownerCollection.id, 10000, async (done) => { + lock(ownerCollection.id, 10000, async done => { // FIXME: should we validate the existance of parentDocument? let parentDocumentObj = {}; if (parentDocument && ownerCollection.type === 'atlas') { @@ -158,12 +156,8 @@ router.post('documents.create', auth(), async (ctx) => { }; }); -router.post('documents.update', auth(), async (ctx) => { - const { - id, - title, - text, - } = ctx.body; +router.post('documents.update', auth(), async ctx => { + const { id, title, text } = ctx.body; ctx.assertPresent(id, 'id is required'); ctx.assertPresent(title, 'title is required'); ctx.assertPresent(text, 'text is required'); @@ -171,7 +165,8 @@ router.post('documents.update', auth(), async (ctx) => { const user = ctx.state.user; const document = await getDocumentForId(id); - if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest(); + if (!document || document.teamId !== user.teamId) + throw httpErrors.BadRequest(); // Update document document.title = title; @@ -194,23 +189,22 @@ router.post('documents.update', auth(), async (ctx) => { }; }); -router.post('documents.delete', auth(), async (ctx) => { - const { - id, - } = ctx.body; +router.post('documents.delete', auth(), async ctx => { + const { id } = ctx.body; ctx.assertPresent(id, 'id is required'); const user = ctx.state.user; const document = await getDocumentForId(id); const collection = await Atlas.findById(document.atlasId); - if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest(); + if (!document || document.teamId !== user.teamId) + throw httpErrors.BadRequest(); // TODO: Add locking if (collection.type === 'atlas') { // Don't allow deletion of root docs if (!document.parentDocumentId) { - throw httpErrors.BadRequest('Unable to delete atlas\'s root document'); + throw httpErrors.BadRequest("Unable to delete atlas's root document"); } // Delete all chilren diff --git a/server/api/hooks.js b/server/api/hooks.js index c49503829..65b29b820 100644 --- a/server/api/hooks.js +++ b/server/api/hooks.js @@ -4,17 +4,14 @@ import { Document, User } from '../models'; const router = new Router(); -router.post('hooks.slack', async (ctx) => { - const { - token, - user_id, - text, - } = ctx.body; +router.post('hooks.slack', async ctx => { + const { token, user_id, text } = ctx.body; ctx.assertPresent(token, 'token is required'); ctx.assertPresent(user_id, 'user_id is required'); ctx.assertPresent(text, 'text is required'); - if (token !== process.env.SLACK_VERIFICATION_TOKEN) throw httpErrors.BadRequest('Invalid token'); + if (token !== process.env.SLACK_VERIFICATION_TOKEN) + throw httpErrors.BadRequest('Invalid token'); const user = await User.find({ where: { @@ -31,7 +28,9 @@ router.post('hooks.slack', async (ctx) => { const results = []; let number = 1; for (const document of documents) { - results.push(`${number}. <${process.env.URL}${document.getUrl()}|${document.title}>`); + results.push( + `${number}. <${process.env.URL}${document.getUrl()}|${document.title}>` + ); number += 1; } diff --git a/server/api/user.js b/server/api/user.js index 0a9fc1192..a68dcedcf 100644 --- a/server/api/user.js +++ b/server/api/user.js @@ -1,20 +1,17 @@ import uuid from 'uuid'; import Router from 'koa-router'; -import { - makePolicy, - signPolicy, -} from '../utils/s3'; +import { makePolicy, signPolicy } from '../utils/s3'; import auth from './middlewares/authentication'; import { presentUser } from '../presenters'; const router = new Router(); -router.post('user.info', auth(), async (ctx) => { +router.post('user.info', auth(), async ctx => { ctx.body = { data: await presentUser(ctx, ctx.state.user) }; }); -router.post('user.s3Upload', auth(), async (ctx) => { +router.post('user.s3Upload', auth(), async ctx => { const { filename, kind, size } = ctx.body; ctx.assertPresent(filename, 'filename is required'); ctx.assertPresent(kind, 'kind is required'); @@ -24,25 +21,27 @@ router.post('user.s3Upload', auth(), async (ctx) => { const key = `${s3Key}/${filename}`; const policy = makePolicy(); - ctx.body = { data: { - maxUploadSize: process.env.AWS_S3_UPLOAD_MAX_SIZE, - uploadUrl: process.env.AWS_S3_UPLOAD_BUCKET_URL, - form: { - AWSAccessKeyId: process.env.AWS_ACCESS_KEY_ID, - 'Cache-Control': 'max-age=31557600', - 'Content-Type': kind, - key, - acl: 'public-read', - signature: signPolicy(policy), - policy, + ctx.body = { + data: { + maxUploadSize: process.env.AWS_S3_UPLOAD_MAX_SIZE, + uploadUrl: process.env.AWS_S3_UPLOAD_BUCKET_URL, + form: { + AWSAccessKeyId: process.env.AWS_ACCESS_KEY_ID, + 'Cache-Control': 'max-age=31557600', + 'Content-Type': kind, + key, + acl: 'public-read', + signature: signPolicy(policy), + policy, + }, + asset: { + contentType: kind, + url: `${process.env.AWS_S3_UPLOAD_BUCKET_URL}${s3Key}/${filename}`, + name: filename, + size, + }, }, - asset: { - contentType: kind, - url: `${process.env.AWS_S3_UPLOAD_BUCKET_URL}${s3Key}/${filename}`, - name: filename, - size, - }, - } }; + }; }); export default router; diff --git a/server/middlewares/cache.js b/server/middlewares/cache.js index 20e13cd9e..7a10ad642 100644 --- a/server/middlewares/cache.js +++ b/server/middlewares/cache.js @@ -8,7 +8,7 @@ export default function cache() { ctx.cache.set = async (id, value) => { ctx.cache[id] = value; - } + }; ctx.cache.get = async (id, def) => { if (ctx.cache[id]) { diff --git a/server/middlewares/methodOverride.js b/server/middlewares/methodOverride.js index ba5a39179..0c49e2a95 100644 --- a/server/middlewares/methodOverride.js +++ b/server/middlewares/methodOverride.js @@ -5,7 +5,7 @@ export default function methodOverride(_options) { if (ctx.method === 'POST') { ctx.body = ctx.request.body; } else if (ctx.method === 'GET') { - ctx.method= 'POST'; // eslint-disable-line + ctx.method = 'POST'; // eslint-disable-line ctx.body = queryString.parse(ctx.querystring); } return next(); diff --git a/server/middlewares/subdomainRedirect.js b/server/middlewares/subdomainRedirect.js index b297ccd08..8989bbbeb 100644 --- a/server/middlewares/subdomainRedirect.js +++ b/server/middlewares/subdomainRedirect.js @@ -2,9 +2,8 @@ export default function subdomainRedirect(options) { return async function subdomainRedirectMiddleware(ctx, next) { if (ctx.headers.host === 'beautifulatlas.com') { ctx.redirect('https://www.' + ctx.headers.host + ctx.path); - } - else { + } else { return next(); } - } -}; + }; +} diff --git a/server/migrations/20160619080644-initial.js b/server/migrations/20160619080644-initial.js index a7f104a18..4a113bc12 100644 --- a/server/migrations/20160619080644-initial.js +++ b/server/migrations/20160619080644-initial.js @@ -1,12 +1,12 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.createTable('teams', { id: { type: 'UUID', allowNull: false, - primaryKey: true + primaryKey: true, }, name: { type: 'CHARACTER VARYING', @@ -15,7 +15,7 @@ module.exports = { slackId: { type: 'CHARACTER VARYING', allowNull: true, - unique: true + unique: true, }, slackData: { type: 'JSONB', @@ -28,14 +28,14 @@ module.exports = { updatedAt: { type: 'TIMESTAMP WITH TIME ZONE', allowNull: false, - } + }, }); queryInterface.createTable('atlases', { id: { type: 'UUID', allowNull: false, - primaryKey: true + primaryKey: true, }, name: { type: 'CHARACTER VARYING', @@ -68,14 +68,14 @@ module.exports = { // model: "teams", // key: "id", // } - } + }, }); queryInterface.createTable('users', { id: { type: 'UUID', allowNull: false, - primaryKey: true + primaryKey: true, }, email: { type: 'CHARACTER VARYING', @@ -96,7 +96,8 @@ module.exports = { }, slackAccessToken: { type: 'bytea', - allowNull: true, }, + allowNull: true, + }, slackId: { type: 'CHARACTER VARYING', unique: true, @@ -125,46 +126,48 @@ module.exports = { // model: "teams", // key: "id", // } - } + }, }); queryInterface.createTable('documents', { - id: - { type: 'UUID', - allowNull: false, - primaryKey: true }, - urlId: - { type: 'CHARACTER VARYING', - allowNull: false, - unique: true, }, - private: - { type: 'BOOLEAN', - allowNull: false, - defaultValue: true, - }, - title: - { type: 'CHARACTER VARYING', - allowNull: false, + id: { + type: 'UUID', + allowNull: false, + primaryKey: true, }, - text: - { type: 'TEXT', - allowNull: true, + urlId: { + type: 'CHARACTER VARYING', + allowNull: false, + unique: true, }, - html: - { type: 'TEXT', - allowNull: true, + private: { + type: 'BOOLEAN', + allowNull: false, + defaultValue: true, }, - preview: - { type: 'TEXT', - allowNull: true, + title: { + type: 'CHARACTER VARYING', + allowNull: false, }, - createdAt: - { type: 'TIMESTAMP WITH TIME ZONE', - allowNull: false, + text: { + type: 'TEXT', + allowNull: true, }, - updatedAt: - { type: 'TIMESTAMP WITH TIME ZONE', - allowNull: false, + html: { + type: 'TEXT', + allowNull: true, + }, + preview: { + type: 'TEXT', + allowNull: true, + }, + createdAt: { + type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + }, + updatedAt: { + type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, }, userId: { type: 'UUID', @@ -189,11 +192,11 @@ module.exports = { // model: "teams", // key: "id", // } - } + }, }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.dropAllTables(); - } + }, }; diff --git a/server/migrations/20160622043741-add-parent-document.js b/server/migrations/20160622043741-add-parent-document.js index f8ecdbb7c..415e826ba 100644 --- a/server/migrations/20160622043741-add-parent-document.js +++ b/server/migrations/20160622043741-add-parent-document.js @@ -1,18 +1,14 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'documents', - 'parentDocumentId', - { - type: Sequelize.UUID, - allowNull: true, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('documents', 'parentDocumentId', { + type: Sequelize.UUID, + allowNull: true, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('documents', 'parentDocumentId'); - } + }, }; diff --git a/server/migrations/20160626063409-add-indexes.js b/server/migrations/20160626063409-add-indexes.js index 6d886bd5c..a3878ff21 100644 --- a/server/migrations/20160626063409-add-indexes.js +++ b/server/migrations/20160626063409-add-indexes.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.addIndex('documents', ['urlId']); queryInterface.addIndex('documents', ['id', 'atlasId']); queryInterface.addIndex('documents', ['id', 'teamId']); @@ -14,7 +14,7 @@ module.exports = { queryInterface.addIndex('users', ['slackId']); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeIndex('documents', ['urlId']); queryInterface.removeIndex('documents', ['id', 'atlasId']); queryInterface.removeIndex('documents', ['id', 'teamId']); @@ -25,5 +25,5 @@ module.exports = { queryInterface.removeIndex('teams', ['slackId']); queryInterface.removeIndex('users', ['slackId']); - } + }, }; diff --git a/server/migrations/20160626175224-add-revisions.js b/server/migrations/20160626175224-add-revisions.js index 2e99cecd1..4a9f46c01 100644 --- a/server/migrations/20160626175224-add-revisions.js +++ b/server/migrations/20160626175224-add-revisions.js @@ -1,43 +1,43 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.createTable('revisions', { id: { type: 'UUID', allowNull: false, - primaryKey: true + primaryKey: true, }, - title: - { type: 'CHARACTER VARYING', - allowNull: false, + title: { + type: 'CHARACTER VARYING', + allowNull: false, }, - text: - { type: 'TEXT', - allowNull: true, + text: { + type: 'TEXT', + allowNull: true, }, - html: - { type: 'TEXT', - allowNull: true, + html: { + type: 'TEXT', + allowNull: true, }, - preview: - { type: 'TEXT', - allowNull: true, + preview: { + type: 'TEXT', + allowNull: true, }, - createdAt: - { type: 'TIMESTAMP WITH TIME ZONE', - allowNull: false, + createdAt: { + type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, }, - updatedAt: - { type: 'TIMESTAMP WITH TIME ZONE', - allowNull: false, + updatedAt: { + type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, }, userId: { type: 'UUID', allowNull: false, references: { model: 'users', - } + }, }, documentId: { type: 'UUID', @@ -45,36 +45,28 @@ module.exports = { references: { model: 'documents', onDelete: 'CASCADE', - } + }, }, }); - queryInterface.addColumn( - 'documents', - 'lastModifiedById', - { - type: 'UUID', - allowNull: false, - references: { - model: 'users', - } - } - ); + queryInterface.addColumn('documents', 'lastModifiedById', { + type: 'UUID', + allowNull: false, + references: { + model: 'users', + }, + }); - queryInterface.addColumn( - 'documents', - 'revisionCount', - { - type: 'INTEGER', - defaultValue: 0 - } - ); + queryInterface.addColumn('documents', 'revisionCount', { + type: 'INTEGER', + defaultValue: 0, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.dropTable('revisions'); queryInterface.removeColumn('documents', 'lastModifiedById'); queryInterface.removeColumn('documents', 'revisionCount'); - } + }, }; diff --git a/server/migrations/20160711071958-search-index.js b/server/migrations/20160711071958-search-index.js index 5a73c0ec5..bb9c7ec06 100644 --- a/server/migrations/20160711071958-search-index.js +++ b/server/migrations/20160711071958-search-index.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { const searchDocument = ` ALTER TABLE documents ADD COLUMN "searchVector" tsvector; CREATE INDEX documents_tsv_idx ON documents USING gin("searchVector"); @@ -40,7 +40,7 @@ ON atlases FOR EACH ROW EXECUTE PROCEDURE atlases_search_trigger(); queryInterface.sequelize.query(searchAtlas); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { // TODO? - } + }, }; diff --git a/server/migrations/20160726061511-atlas-creator.js b/server/migrations/20160726061511-atlas-creator.js index 1d6285863..41ed9425c 100644 --- a/server/migrations/20160726061511-atlas-creator.js +++ b/server/migrations/20160726061511-atlas-creator.js @@ -1,18 +1,14 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'atlases', - 'creatorId', - { - type: Sequelize.UUID, - allowNull: true, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('atlases', 'creatorId', { + type: Sequelize.UUID, + allowNull: true, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('atlases', 'creatorId'); - } + }, }; diff --git a/server/migrations/20160812145029-document-atlas-soft-delete.js b/server/migrations/20160812145029-document-atlas-soft-delete.js index a2081fc46..6f2864e91 100644 --- a/server/migrations/20160812145029-document-atlas-soft-delete.js +++ b/server/migrations/20160812145029-document-atlas-soft-delete.js @@ -1,28 +1,20 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'atlases', - 'deletedAt', - { - type: Sequelize.DATE, - allowNull: true, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('atlases', 'deletedAt', { + type: Sequelize.DATE, + allowNull: true, + }); - queryInterface.addColumn( - 'documents', - 'deletedAt', - { - type: Sequelize.DATE, - allowNull: true, - } - ); + queryInterface.addColumn('documents', 'deletedAt', { + type: Sequelize.DATE, + allowNull: true, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('atlases', 'deletedAt'); queryInterface.removeColumn('documents', 'deletedAt'); - } + }, }; diff --git a/server/migrations/20160814083127-paranoia-indeces.js b/server/migrations/20160814083127-paranoia-indeces.js index b488d56f3..48853314d 100644 --- a/server/migrations/20160814083127-paranoia-indeces.js +++ b/server/migrations/20160814083127-paranoia-indeces.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { // Remove old indeces queryInterface.removeIndex('documents', ['urlId']); queryInterface.removeIndex('documents', ['id', 'atlasId']); @@ -15,13 +15,17 @@ module.exports = { queryInterface.addIndex('documents', ['urlId', 'deletedAt']); queryInterface.addIndex('documents', ['id', 'atlasId', 'deletedAt']); queryInterface.addIndex('documents', ['id', 'teamId', 'deletedAt']); - queryInterface.addIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']); + queryInterface.addIndex('documents', [ + 'parentDocumentId', + 'atlasId', + 'deletedAt', + ]); queryInterface.addIndex('atlases', ['id', 'deletedAt']); queryInterface.addIndex('atlases', ['id', 'teamId', 'deletedAt']); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.addIndex('documents', ['urlId']); queryInterface.addIndex('documents', ['id', 'atlasId']); queryInterface.addIndex('documents', ['id', 'teamId']); @@ -33,9 +37,13 @@ module.exports = { queryInterface.removeIndex('documents', ['urlId', 'deletedAt']); queryInterface.removeIndex('documents', ['id', 'atlasId', 'deletedAt']); queryInterface.removeIndex('documents', ['id', 'teamId', 'deletedAt']); - queryInterface.removeIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']); + queryInterface.removeIndex('documents', [ + 'parentDocumentId', + 'atlasId', + 'deletedAt', + ]); queryInterface.removeIndex('atlases', ['id', 'deletedAt']); queryInterface.removeIndex('atlases', ['id', 'teamId', 'deletedAt']); - } + }, }; diff --git a/server/migrations/20160814095336-add-document-createdById.js b/server/migrations/20160814095336-add-document-createdById.js index 9228addb5..1c53a0699 100644 --- a/server/migrations/20160814095336-add-document-createdById.js +++ b/server/migrations/20160814095336-add-document-createdById.js @@ -1,21 +1,17 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'documents', - 'createdById', - { - type: 'UUID', - allowNull: true, - references: { - model: 'users', - }, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('documents', 'createdById', { + type: 'UUID', + allowNull: true, + references: { + model: 'users', + }, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('documents', 'createdById'); }, }; diff --git a/server/migrations/20160814111419-add-document-collaboratorIds.js b/server/migrations/20160814111419-add-document-collaboratorIds.js index b15315fd2..da7332089 100644 --- a/server/migrations/20160814111419-add-document-collaboratorIds.js +++ b/server/migrations/20160814111419-add-document-collaboratorIds.js @@ -1,14 +1,12 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'documents', - 'collaboratorIds', - { type: Sequelize.ARRAY(Sequelize.UUID) } - ) + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('documents', 'collaboratorIds', { + type: Sequelize.ARRAY(Sequelize.UUID), + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('documents', 'collaboratorIds'); }, }; diff --git a/server/migrations/20160815142720-app-collection-urlId.js b/server/migrations/20160815142720-app-collection-urlId.js index bd9c1aeff..bb8b0c708 100644 --- a/server/migrations/20160815142720-app-collection-urlId.js +++ b/server/migrations/20160815142720-app-collection-urlId.js @@ -1,18 +1,14 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.addColumn( - 'atlases', - 'urlId', - { - type: Sequelize.STRING, - unique: true, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('atlases', 'urlId', { + type: Sequelize.STRING, + unique: true, + }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeColumn('atlases', 'urlId'); - } + }, }; diff --git a/server/migrations/20160816082738-add-revision-index.js b/server/migrations/20160816082738-add-revision-index.js index 4206bb594..2ac17f658 100644 --- a/server/migrations/20160816082738-add-revision-index.js +++ b/server/migrations/20160816082738-add-revision-index.js @@ -1,11 +1,11 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.addIndex('revisions', ['documentId']); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeIndex('revisions', ['documentId']); }, }; diff --git a/server/migrations/20160824061730-add-apikeys.js b/server/migrations/20160824061730-add-apikeys.js index 7cff90f67..f12f14050 100644 --- a/server/migrations/20160824061730-add-apikeys.js +++ b/server/migrations/20160824061730-add-apikeys.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.createTable('apiKeys', { id: { type: 'UUID', @@ -40,7 +40,7 @@ module.exports = { }); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.createTable('apiKeys'); }, }; diff --git a/server/migrations/20160824062457-add-apikey-indeces.js b/server/migrations/20160824062457-add-apikey-indeces.js index 1ed8ae22b..c30226a59 100644 --- a/server/migrations/20160824062457-add-apikey-indeces.js +++ b/server/migrations/20160824062457-add-apikey-indeces.js @@ -1,12 +1,12 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { + up: function(queryInterface, Sequelize) { queryInterface.addIndex('apiKeys', ['secret', 'deletedAt']); queryInterface.addIndex('apiKeys', ['userId', 'deletedAt']); }, - down: function (queryInterface, Sequelize) { + down: function(queryInterface, Sequelize) { queryInterface.removeIndex('apiKeys', ['secret', 'deletedAt']); queryInterface.removeIndex('apiKeys', ['userId', 'deletedAt']); }, diff --git a/server/migrations/20160911230444-user-optional-slack-id.js b/server/migrations/20160911230444-user-optional-slack-id.js index b1bf1f188..f3531738e 100644 --- a/server/migrations/20160911230444-user-optional-slack-id.js +++ b/server/migrations/20160911230444-user-optional-slack-id.js @@ -2,45 +2,29 @@ 'use strict'; module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.changeColumn( - 'users', - 'slackId', - { - type: Sequelize.STRING, - unique: false, - allowNull: true, - } - ); - queryInterface.changeColumn( - 'teams', - 'slackId', - { - type: Sequelize.STRING, - unique: false, - allowNull: true, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.changeColumn('users', 'slackId', { + type: Sequelize.STRING, + unique: false, + allowNull: true, + }); + queryInterface.changeColumn('teams', 'slackId', { + type: Sequelize.STRING, + unique: false, + allowNull: true, + }); }, - down: function (queryInterface, Sequelize) { - queryInterface.changeColumn( - 'users', - 'slackId', - { - type: Sequelize.STRING, - unique: true, - allowNull: false, - } - ); - queryInterface.changeColumn( - 'teams', - 'slackId', - { - type: Sequelize.STRING, - unique: true, - allowNull: false, - } - ); + down: function(queryInterface, Sequelize) { + queryInterface.changeColumn('users', 'slackId', { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }); + queryInterface.changeColumn('teams', 'slackId', { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }); }, }; diff --git a/server/migrations/20160911232911-user-unique-fields.js b/server/migrations/20160911232911-user-unique-fields.js index 5786ff4d0..4dfaffe24 100644 --- a/server/migrations/20160911232911-user-unique-fields.js +++ b/server/migrations/20160911232911-user-unique-fields.js @@ -1,44 +1,28 @@ module.exports = { - up: function (queryInterface, Sequelize) { - queryInterface.changeColumn( - 'users', - 'email', - { - type: Sequelize.STRING, - unique: true, - allowNull: false, - } - ); - queryInterface.changeColumn( - 'users', - 'username', - { - type: Sequelize.STRING, - unique: true, - allowNull: false, - } - ); + up: function(queryInterface, Sequelize) { + queryInterface.changeColumn('users', 'email', { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }); + queryInterface.changeColumn('users', 'username', { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }); }, - down: function (queryInterface, Sequelize) { - queryInterface.changeColumn( - 'users', - 'email', - { - type: Sequelize.STRING, - unique: false, - allowNull: true, - } - ); + down: function(queryInterface, Sequelize) { + queryInterface.changeColumn('users', 'email', { + type: Sequelize.STRING, + unique: false, + allowNull: true, + }); - queryInterface.changeColumn( - 'users', - 'username', - { - type: Sequelize.STRING, - unique: false, - allowNull: true, - } - ); + queryInterface.changeColumn('users', 'username', { + type: Sequelize.STRING, + unique: false, + allowNull: true, + }); }, }; diff --git a/server/models/ApiKey.js b/server/models/ApiKey.js index f54636a92..69ade74d4 100644 --- a/server/models/ApiKey.js +++ b/server/models/ApiKey.js @@ -1,28 +1,33 @@ -import { - DataTypes, - sequelize, -} from '../sequelize'; +import { DataTypes, sequelize } from '../sequelize'; import randomstring from 'randomstring'; -const Team = sequelize.define('team', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, - name: DataTypes.STRING, - secret: { type: DataTypes.STRING, unique: true }, - userId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', +const Team = sequelize.define( + 'team', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: DataTypes.STRING, + secret: { type: DataTypes.STRING, unique: true }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + }, }, }, -}, { - tableName: 'apiKeys', - paranoid: true, - hooks: { - beforeValidate: (key) => { - key.secret = randomstring.generate(38); + { + tableName: 'apiKeys', + paranoid: true, + hooks: { + beforeValidate: key => { + key.secret = randomstring.generate(38); + }, }, - }, -}); + } +); export default Team; diff --git a/server/models/Atlas.js b/server/models/Atlas.js index 57a537409..c78804585 100644 --- a/server/models/Atlas.js +++ b/server/models/Atlas.js @@ -1,9 +1,6 @@ import slug from 'slug'; import randomstring from 'randomstring'; -import { - DataTypes, - sequelize, -} from '../sequelize'; +import { DataTypes, sequelize } from '../sequelize'; import _ from 'lodash'; import Document from './Document'; @@ -11,184 +8,207 @@ slug.defaults.mode = 'rfc3986'; const allowedAtlasTypes = [['atlas', 'journal']]; -const Atlas = sequelize.define('atlas', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, - urlId: { type: DataTypes.STRING, unique: true }, - name: DataTypes.STRING, - description: DataTypes.STRING, - type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes } }, - creatorId: DataTypes.UUID, - - /* type: atlas */ - navigationTree: DataTypes.JSONB, -}, { - tableName: 'atlases', - paranoid: true, - hooks: { - beforeValidate: (collection) => { - collection.urlId = collection.urlId || randomstring.generate(10); +const Atlas = sequelize.define( + 'atlas', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, }, - afterCreate: async (collection) => { - if (collection.type !== 'atlas') return; + urlId: { type: DataTypes.STRING, unique: true }, + name: DataTypes.STRING, + description: DataTypes.STRING, + type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes } }, + creatorId: DataTypes.UUID, - await Document.create({ - parentDocumentId: null, - atlasId: collection.id, - teamId: collection.teamId, - userId: collection.creatorId, - lastModifiedById: collection.creatorId, - createdById: collection.creatorId, - title: 'Introduction', - text: '# Introduction\n\nLets get started...', - }); - await collection.buildStructure(); - await collection.save(); - }, + /* type: atlas */ + navigationTree: DataTypes.JSONB, }, - instanceMethods: { - getUrl() { - // const slugifiedName = slug(this.name); - // return `/${slugifiedName}-c${this.urlId}`; - return `/collections/${this.id}`; - }, - async buildStructure() { - if (this.navigationTree) return this.navigationTree; + { + tableName: 'atlases', + paranoid: true, + hooks: { + beforeValidate: collection => { + collection.urlId = collection.urlId || randomstring.generate(10); + }, + afterCreate: async collection => { + if (collection.type !== 'atlas') return; - const getNodeForDocument = async (document) => { - const children = await Document.findAll({ where: { - parentDocumentId: document.id, - atlasId: this.id, - } }); - - const childNodes = []; - await Promise.all(children.map(async (child) => { - return childNodes.push(await getNodeForDocument(child)); - })); - - return { - title: document.title, - id: document.id, - url: document.getUrl(), - children: childNodes, - }; - }; - - const rootDocument = await Document.findOne({ - where: { + await Document.create({ parentDocumentId: null, - atlasId: this.id, - }, - }); - - this.navigationTree = await getNodeForDocument(rootDocument); - return this.navigationTree; + atlasId: collection.id, + teamId: collection.teamId, + userId: collection.creatorId, + lastModifiedById: collection.creatorId, + createdById: collection.creatorId, + title: 'Introduction', + text: '# Introduction\n\nLets get started...', + }); + await collection.buildStructure(); + await collection.save(); + }, }, - async updateNavigationTree(tree = this.navigationTree) { - const nodeIds = []; - nodeIds.push(tree.id); + instanceMethods: { + getUrl() { + // const slugifiedName = slug(this.name); + // return `/${slugifiedName}-c${this.urlId}`; + return `/collections/${this.id}`; + }, + async buildStructure() { + if (this.navigationTree) return this.navigationTree; - const rootDocument = await Document.findOne({ - where: { - id: tree.id, - atlasId: this.id, - }, - }); - if (!rootDocument) throw new Error; - - const newTree = { - id: tree.id, - title: rootDocument.title, - url: rootDocument.getUrl(), - children: [], - }; - - const getIdsForChildren = async (children) => { - const childNodes = []; - for (const child of children) { - const childDocument = await Document.findOne({ + const getNodeForDocument = async document => { + const children = await Document.findAll({ where: { - id: child.id, + parentDocumentId: document.id, atlasId: this.id, }, }); - if (childDocument) { - childNodes.push({ - id: childDocument.id, - title: childDocument.title, - url: childDocument.getUrl(), - children: await getIdsForChildren(child.children), - }); - nodeIds.push(child.id); - } - } - return childNodes; - }; - newTree.children = await getIdsForChildren(tree.children); - const documents = await Document.findAll({ - attributes: ['id'], - where: { - atlasId: this.id, - }, - }); - const documentIds = documents.map(doc => doc.id); + const childNodes = []; + await Promise.all( + children.map(async child => { + return childNodes.push(await getNodeForDocument(child)); + }) + ); - if (!_.isEqual(nodeIds.sort(), documentIds.sort())) { - throw new Error('Invalid navigation tree'); - } + return { + title: document.title, + id: document.id, + url: document.getUrl(), + children: childNodes, + }; + }; - this.navigationTree = newTree; - await this.save(); - - return newTree; - }, - async addNodeToNavigationTree(document) { - const newNode = { - id: document.id, - title: document.title, - url: document.getUrl(), - children: [], - }; - - const insertNode = (node) => { - if (document.parentDocumentId === node.id) { - node.children.push(newNode); - } else { - node.children = node.children.map(childNode => { - return insertNode(childNode); - }); - } - - return node; - }; - - this.navigationTree = insertNode(this.navigationTree); - return this.navigationTree; - }, - async deleteDocument(document) { - const deleteNodeAndDocument = async (node, documentId, shouldDelete = false) => { - // Delete node if id matches - if (document.id === node.id) shouldDelete = true; - - const newChildren = []; - node.children.forEach(async childNode => { - const child = await deleteNodeAndDocument(childNode, documentId, shouldDelete); - if (child) newChildren.push(child); + const rootDocument = await Document.findOne({ + where: { + parentDocumentId: null, + atlasId: this.id, + }, }); - node.children = newChildren; - if (shouldDelete) { - const doc = await Document.findById(node.id); - await doc.destroy(); + this.navigationTree = await getNodeForDocument(rootDocument); + return this.navigationTree; + }, + async updateNavigationTree(tree = this.navigationTree) { + const nodeIds = []; + nodeIds.push(tree.id); + + const rootDocument = await Document.findOne({ + where: { + id: tree.id, + atlasId: this.id, + }, + }); + if (!rootDocument) throw new Error(); + + const newTree = { + id: tree.id, + title: rootDocument.title, + url: rootDocument.getUrl(), + children: [], + }; + + const getIdsForChildren = async children => { + const childNodes = []; + for (const child of children) { + const childDocument = await Document.findOne({ + where: { + id: child.id, + atlasId: this.id, + }, + }); + if (childDocument) { + childNodes.push({ + id: childDocument.id, + title: childDocument.title, + url: childDocument.getUrl(), + children: await getIdsForChildren(child.children), + }); + nodeIds.push(child.id); + } + } + return childNodes; + }; + newTree.children = await getIdsForChildren(tree.children); + + const documents = await Document.findAll({ + attributes: ['id'], + where: { + atlasId: this.id, + }, + }); + const documentIds = documents.map(doc => doc.id); + + if (!_.isEqual(nodeIds.sort(), documentIds.sort())) { + throw new Error('Invalid navigation tree'); } - return shouldDelete ? null : node; - }; + this.navigationTree = newTree; + await this.save(); - this.navigationTree = await deleteNodeAndDocument(this.navigationTree, document.id); + return newTree; + }, + async addNodeToNavigationTree(document) { + const newNode = { + id: document.id, + title: document.title, + url: document.getUrl(), + children: [], + }; + + const insertNode = node => { + if (document.parentDocumentId === node.id) { + node.children.push(newNode); + } else { + node.children = node.children.map(childNode => { + return insertNode(childNode); + }); + } + + return node; + }; + + this.navigationTree = insertNode(this.navigationTree); + return this.navigationTree; + }, + async deleteDocument(document) { + const deleteNodeAndDocument = async ( + node, + documentId, + shouldDelete = false + ) => { + // Delete node if id matches + if (document.id === node.id) shouldDelete = true; + + const newChildren = []; + node.children.forEach(async childNode => { + const child = await deleteNodeAndDocument( + childNode, + documentId, + shouldDelete + ); + if (child) newChildren.push(child); + }); + node.children = newChildren; + + if (shouldDelete) { + const doc = await Document.findById(node.id); + await doc.destroy(); + } + + return shouldDelete ? null : node; + }; + + this.navigationTree = await deleteNodeAndDocument( + this.navigationTree, + document.id + ); + }, }, - }, -}); + } +); Atlas.hasMany(Document, { as: 'documents', foreignKey: 'atlasId' }); diff --git a/server/models/Document.js b/server/models/Document.js index d5ecb648d..b20a98b30 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -1,22 +1,15 @@ import slug from 'slug'; import _ from 'lodash'; import randomstring from 'randomstring'; -import { - DataTypes, - sequelize, -} from '../sequelize'; -import { - convertToMarkdown, -} from '../../frontend/utils/markdown'; -import { - truncateMarkdown, -} from '../utils/truncate'; +import { DataTypes, sequelize } from '../sequelize'; +import { convertToMarkdown } from '../../frontend/utils/markdown'; +import { truncateMarkdown } from '../utils/truncate'; import User from './User'; import Revision from './Revision'; slug.defaults.mode = 'rfc3986'; -const createRevision = async (doc) => { +const createRevision = async doc => { // Create revision of the current (latest) await Revision.create({ title: doc.title, @@ -28,7 +21,7 @@ const createRevision = async (doc) => { }); }; -const documentBeforeSave = async (doc) => { +const documentBeforeSave = async doc => { doc.html = convertToMarkdown(doc.text); doc.preview = truncateMarkdown(doc.text, 160); @@ -52,50 +45,58 @@ const documentBeforeSave = async (doc) => { return doc; }; -const Document = sequelize.define('document', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, - urlId: { type: DataTypes.STRING, primaryKey: true }, - private: { type: DataTypes.BOOLEAN, defaultValue: true }, - title: DataTypes.STRING, - text: DataTypes.TEXT, - html: DataTypes.TEXT, - preview: DataTypes.TEXT, - revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 }, +const Document = sequelize.define( + 'document', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + urlId: { type: DataTypes.STRING, primaryKey: true }, + private: { type: DataTypes.BOOLEAN, defaultValue: true }, + title: DataTypes.STRING, + text: DataTypes.TEXT, + html: DataTypes.TEXT, + preview: DataTypes.TEXT, + revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 }, - parentDocumentId: DataTypes.UUID, - createdById: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', + parentDocumentId: DataTypes.UUID, + createdById: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + }, }, - }, - lastModifiedById: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', + lastModifiedById: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'users', + }, }, + collaboratorIds: DataTypes.ARRAY(DataTypes.UUID), }, - collaboratorIds: DataTypes.ARRAY(DataTypes.UUID), -}, { - paranoid: true, - hooks: { - beforeValidate: (doc) => { - doc.urlId = doc.urlId || randomstring.generate(10); + { + paranoid: true, + hooks: { + beforeValidate: doc => { + doc.urlId = doc.urlId || randomstring.generate(10); + }, + beforeCreate: documentBeforeSave, + beforeUpdate: documentBeforeSave, + afterCreate: async doc => await createRevision(doc), + afterUpdate: async doc => await createRevision(doc), }, - beforeCreate: documentBeforeSave, - beforeUpdate: documentBeforeSave, - afterCreate: async (doc) => await createRevision(doc), - afterUpdate: async (doc) => await createRevision(doc), - }, - instanceMethods: { - getUrl() { - const slugifiedTitle = slug(this.title); - return `/d/${slugifiedTitle}-${this.urlId}`; + instanceMethods: { + getUrl() { + const slugifiedTitle = slug(this.title); + return `/d/${slugifiedTitle}-${this.urlId}`; + }, }, - }, -}); + } +); Document.belongsTo(User); @@ -112,18 +113,14 @@ Document.searchForUser = async (user, query, options = {}) => { LIMIT :limit OFFSET :offset; `; - const documents = await sequelize - .query( - sql, - { - replacements: { - query, - limit, - offset, - }, - model: Document, - } - ); + const documents = await sequelize.query(sql, { + replacements: { + query, + limit, + offset, + }, + model: Document, + }); return documents; }; diff --git a/server/models/Revision.js b/server/models/Revision.js index e4e17e4af..831078566 100644 --- a/server/models/Revision.js +++ b/server/models/Revision.js @@ -1,10 +1,11 @@ -import { - DataTypes, - sequelize, -} from '../sequelize'; +import { DataTypes, sequelize } from '../sequelize'; const Revision = sequelize.define('revision', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, title: DataTypes.STRING, text: DataTypes.TEXT, html: DataTypes.TEXT, @@ -15,7 +16,7 @@ const Revision = sequelize.define('revision', { allowNull: false, references: { model: 'users', - } + }, }, documentId: { @@ -24,7 +25,7 @@ const Revision = sequelize.define('revision', { references: { model: 'documents', onDelete: 'CASCADE', - } + }, }, }); diff --git a/server/models/Team.js b/server/models/Team.js index ef1f04b02..fba3dff36 100644 --- a/server/models/Team.js +++ b/server/models/Team.js @@ -1,36 +1,41 @@ -import { - DataTypes, - sequelize, -} from '../sequelize'; +import { DataTypes, sequelize } from '../sequelize'; import Atlas from './Atlas'; import Document from './Document'; import User from './User'; -const Team = sequelize.define('team', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, - name: DataTypes.STRING, - slackId: { type: DataTypes.STRING, allowNull: true }, - slackData: DataTypes.JSONB, -}, { - instanceMethods: { - async createFirstAtlas(userId) { - const atlas = await Atlas.create({ - name: this.name, - description: 'Your first Atlas', - type: 'atlas', - teamId: this.id, - creatorId: userId, - }); - return atlas; +const Team = sequelize.define( + 'team', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, }, + name: DataTypes.STRING, + slackId: { type: DataTypes.STRING, allowNull: true }, + slackData: DataTypes.JSONB, }, - indexes: [ - { - unique: true, - fields: ['slackId'], + { + instanceMethods: { + async createFirstAtlas(userId) { + const atlas = await Atlas.create({ + name: this.name, + description: 'Your first Atlas', + type: 'atlas', + teamId: this.id, + creatorId: userId, + }); + return atlas; + }, }, - ], -}); + indexes: [ + { + unique: true, + fields: ['slackId'], + }, + ], + } +); Team.hasMany(Atlas, { as: 'atlases' }); Team.hasMany(Document, { as: 'documents' }); diff --git a/server/models/User.js b/server/models/User.js index 0d6c2b3b5..e2e456815 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -1,61 +1,65 @@ import crypto from 'crypto'; import bcrypt from 'bcrypt'; -import { - DataTypes, - sequelize, - encryptedFields, -} from '../sequelize'; +import { DataTypes, sequelize, encryptedFields } from '../sequelize'; import JWT from 'jsonwebtoken'; const BCRYPT_COST = process.env.NODE_ENV !== 'production' ? 4 : 12; -const User = sequelize.define('user', { - id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, - email: { type: DataTypes.STRING, unique: true }, - username: { type: DataTypes.STRING, unique: true }, - name: DataTypes.STRING, - password: DataTypes.VIRTUAL, - passwordDigest: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, - slackAccessToken: encryptedFields.vault('slackAccessToken'), - slackId: { type: DataTypes.STRING, allowNull: true }, - slackData: DataTypes.JSONB, - jwtSecret: encryptedFields.vault('jwtSecret'), -}, { - instanceMethods: { - getJwtToken() { - return JWT.sign({ id: this.id }, this.jwtSecret); +const User = sequelize.define( + 'user', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, }, - async getTeam() { - return this.team; - }, - verifyPassword(password) { - return new Promise((resolve, reject) => { - if (!this.passwordDigest) { - resolve(false); - return; - } - - bcrypt.compare(password, this.passwordDigest, (err, ok) => { - if (err) { - reject(err); + email: { type: DataTypes.STRING, unique: true }, + username: { type: DataTypes.STRING, unique: true }, + name: DataTypes.STRING, + password: DataTypes.VIRTUAL, + passwordDigest: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + slackAccessToken: encryptedFields.vault('slackAccessToken'), + slackId: { type: DataTypes.STRING, allowNull: true }, + slackData: DataTypes.JSONB, + jwtSecret: encryptedFields.vault('jwtSecret'), + }, + { + instanceMethods: { + getJwtToken() { + return JWT.sign({ id: this.id }, this.jwtSecret); + }, + async getTeam() { + return this.team; + }, + verifyPassword(password) { + return new Promise((resolve, reject) => { + if (!this.passwordDigest) { + resolve(false); return; } - resolve(ok); - }); - }); - }, - }, - indexes: [ - { - fields: ['email'], - }, - ], -}); + bcrypt.compare(password, this.passwordDigest, (err, ok) => { + if (err) { + reject(err); + return; + } -const setRandomJwtSecret = (model) => { + resolve(ok); + }); + }); + }, + }, + indexes: [ + { + fields: ['email'], + }, + ], + } +); + +const setRandomJwtSecret = model => { model.jwtSecret = crypto.randomBytes(64).toString('hex'); }; const hashPassword = function hashPassword(model) { diff --git a/server/models/index.js b/server/models/index.js index db1d5bc05..a3d7e55da 100644 --- a/server/models/index.js +++ b/server/models/index.js @@ -5,11 +5,4 @@ import Document from './Document'; import Revision from './Revision'; import ApiKey from './ApiKey'; -export { - User, - Team, - Atlas, - Document, - Revision, - ApiKey, -}; +export { User, Team, Atlas, Document, Revision, ApiKey }; diff --git a/server/presenters/user.test.js b/server/presenters/user.test.js index a26c10fdb..672a0a1b1 100644 --- a/server/presenters/user.test.js +++ b/server/presenters/user.test.js @@ -3,31 +3,25 @@ import presentUser from './user'; import ctx from '../../__mocks__/ctx'; it('presents a user', async () => { - const user = await presentUser( - ctx, - { - id: '123', - name: 'Test User', - username: 'testuser', - slackData: { - image_192: 'http://example.com/avatar.png', - }, + const user = await presentUser(ctx, { + id: '123', + name: 'Test User', + username: 'testuser', + slackData: { + image_192: 'http://example.com/avatar.png', }, - ); + }); expect(user).toMatchSnapshot(); }); it('presents a user without slack data', async () => { - const user = await presentUser( - ctx, - { - id: '123', - name: 'Test User', - username: 'testuser', - slackData: null, - }, - ); + const user = await presentUser(ctx, { + id: '123', + name: 'Test User', + username: 'testuser', + slackData: null, + }); expect(user).toMatchSnapshot(); }); diff --git a/server/static/service-worker.js b/server/static/service-worker.js index 2adf7d2b7..63502e914 100644 --- a/server/static/service-worker.js +++ b/server/static/service-worker.js @@ -16,52 +16,856 @@ limitations under the License. */ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toolbox = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;or.value[TIMESTAMP_PROPERTY]){var t=r.value[URL_PROPERTY];u.push(t),s["delete"](t),r["continue"]()}},c.oncomplete=function(){t(u)},c.onabort=o}):Promise.resolve([])}function expireExtraEntries(e,r){return r?new Promise(function(n,t){var o=[],i=e.transaction(STORE_NAME,"readwrite"),u=i.objectStore(STORE_NAME),c=u.index(TIMESTAMP_PROPERTY),s=c.count();c.count().onsuccess=function(){var e=s.result;e>r&&(c.openCursor().onsuccess=function(n){var t=n.target.result;if(t){var i=t.value[URL_PROPERTY];o.push(i),u["delete"](i),e-o.length>r&&t["continue"]()}})},i.oncomplete=function(){n(o)},i.onabort=t}):Promise.resolve([])}function expireEntries(e,r,n,t){return expireOldEntries(e,n,t).then(function(n){return expireExtraEntries(e,r).then(function(e){return n.concat(e)})})}var DB_PREFIX="sw-toolbox-",DB_VERSION=1,STORE_NAME="store",URL_PROPERTY="url",TIMESTAMP_PROPERTY="timestamp",cacheNameToDbPromise={};module.exports={getDb:getDb,setTimestampForUrl:setTimestampForUrl,expireEntries:expireEntries}; -},{}],3:[function(require,module,exports){ -"use strict";var scope;scope=self.registration?self.registration.scope:self.scope||new URL("./",self.location).href,module.exports={cache:{name:"$$$toolbox-cache$$$"+scope+"$$$",maxAgeSeconds:null,maxEntries:null},debug:!1,networkTimeoutSeconds:null,preCacheItems:[],successResponses:/^0|([123]\d\d)|(40[14567])|410$/}; -},{}],4:[function(require,module,exports){ -"use strict";var url=new URL("./",self.location),basePath=url.pathname,pathRegexp=require("path-to-regexp"),Route=function(e,t,i,s){t instanceof RegExp?this.fullUrlRegExp=t:(0!==t.indexOf("/")&&(t=basePath+t),this.keys=[],this.regexp=pathRegexp(t,this.keys)),this.method=e,this.options=s,this.handler=i};Route.prototype.makeHandler=function(e){var t;if(this.regexp){var i=this.regexp.exec(e);t={},this.keys.forEach(function(e,s){t[e.name]=i[s+1]})}return function(e){return this.handler(e,t,this.options)}.bind(this)},module.exports=Route; -},{"path-to-regexp":13}],5:[function(require,module,exports){ -"use strict";function regexEscape(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var Route=require("./route"),keyMatch=function(e,t){for(var r=e.entries(),o=r.next();!o.done;){var n=new RegExp(o.value[0]);if(n.test(t))return o.value[1];o=r.next()}return null},Router=function(){this.routes=new Map,this["default"]=null};["get","post","put","delete","head","any"].forEach(function(e){Router.prototype[e]=function(t,r,o){return this.add(e,t,r,o)}}),Router.prototype.add=function(e,t,r,o){o=o||{};var n;t instanceof RegExp?n=RegExp:(n=o.origin||self.location.origin,n=n instanceof RegExp?n.source:regexEscape(n)),e=e.toLowerCase();var u=new Route(e,t,r,o);this.routes.has(n)||this.routes.set(n,new Map);var a=this.routes.get(n);a.has(e)||a.set(e,new Map);var s=a.get(e),i=u.regexp||u.fullUrlRegExp;s.set(i.source,u)},Router.prototype.matchMethod=function(e,t){var r=new URL(t),o=r.origin,n=r.pathname;return this._match(e,keyMatch(this.routes,o),n)||this._match(e,this.routes.get(RegExp),t)},Router.prototype._match=function(e,t,r){if(t){var o=t.get(e.toLowerCase());if(o){var n=keyMatch(o,r);if(n)return n.makeHandler(r)}}return null},Router.prototype.match=function(e){return this.matchMethod(e.method,e.url)||this.matchMethod("any",e.url)},module.exports=new Router; -},{"./route":4}],6:[function(require,module,exports){ -"use strict";function cacheFirst(e,r,t){return helpers.debug("Strategy: cache first ["+e.url+"]",t),helpers.openCache(t).then(function(r){return r.match(e).then(function(r){return r?r:helpers.fetchAndCache(e,t)})})}var helpers=require("../helpers");module.exports=cacheFirst; -},{"../helpers":1}],7:[function(require,module,exports){ -"use strict";function cacheOnly(e,r,c){return helpers.debug("Strategy: cache only ["+e.url+"]",c),helpers.openCache(c).then(function(r){return r.match(e)})}var helpers=require("../helpers");module.exports=cacheOnly; -},{"../helpers":1}],8:[function(require,module,exports){ -"use strict";function fastest(e,n,t){return helpers.debug("Strategy: fastest ["+e.url+"]",t),new Promise(function(r,s){var c=!1,o=[],a=function(e){o.push(e.toString()),c?s(new Error('Both cache and network failed: "'+o.join('", "')+'"')):c=!0},h=function(e){e instanceof Response?r(e):a("No result returned")};helpers.fetchAndCache(e.clone(),t).then(h,a),cacheOnly(e,n,t).then(h,a)})}var helpers=require("../helpers"),cacheOnly=require("./cacheOnly");module.exports=fastest; -},{"../helpers":1,"./cacheOnly":7}],9:[function(require,module,exports){ -module.exports={networkOnly:require("./networkOnly"),networkFirst:require("./networkFirst"),cacheOnly:require("./cacheOnly"),cacheFirst:require("./cacheFirst"),fastest:require("./fastest")}; -},{"./cacheFirst":6,"./cacheOnly":7,"./fastest":8,"./networkFirst":10,"./networkOnly":11}],10:[function(require,module,exports){ -"use strict";function networkFirst(e,r,t){t=t||{};var s=t.successResponses||globalOptions.successResponses,n=t.networkTimeoutSeconds||globalOptions.networkTimeoutSeconds;return helpers.debug("Strategy: network first ["+e.url+"]",t),helpers.openCache(t).then(function(r){var o,u,c=[];if(n){var i=new Promise(function(t){o=setTimeout(function(){r.match(e).then(function(e){e&&t(e)})},1e3*n)});c.push(i)}var a=helpers.fetchAndCache(e,t).then(function(e){if(o&&clearTimeout(o),s.test(e.status))return e;throw helpers.debug("Response was an HTTP error: "+e.statusText,t),u=e,new Error("Bad response")})["catch"](function(){return helpers.debug("Network or response error, fallback to cache ["+e.url+"]",t),r.match(e).then(function(e){return e||u})});return c.push(a),Promise.race(c)})}var globalOptions=require("../options"),helpers=require("../helpers");module.exports=networkFirst; -},{"../helpers":1,"../options":3}],11:[function(require,module,exports){ -"use strict";function networkOnly(e,r,t){return helpers.debug("Strategy: network only ["+e.url+"]",t),fetch(e)}var helpers=require("../helpers");module.exports=networkOnly; -},{"../helpers":1}],12:[function(require,module,exports){ -"use strict";function cache(e,t){return helpers.openCache(t).then(function(t){return t.add(e)})}function uncache(e,t){return helpers.openCache(t).then(function(t){return t["delete"](e)})}function precache(e){Array.isArray(e)||(e=[e]),options.preCacheItems=options.preCacheItems.concat(e)}require("serviceworker-cache-polyfill");var options=require("./options"),router=require("./router"),helpers=require("./helpers"),strategies=require("./strategies");helpers.debug("Service Worker Toolbox is loading");var flatten=function(e){return e.reduce(function(e,t){return e.concat(t)},[])};self.addEventListener("install",function(e){var t=options.cache.name+"$$$inactive$$$";helpers.debug("install event fired"),helpers.debug("creating cache ["+t+"]"),e.waitUntil(helpers.openCache({cache:{name:t}}).then(function(e){return Promise.all(options.preCacheItems).then(flatten).then(function(t){return helpers.debug("preCache list: "+(t.join(", ")||"(none)")),e.addAll(t)})}))}),self.addEventListener("activate",function(e){helpers.debug("activate event fired");var t=options.cache.name+"$$$inactive$$$";e.waitUntil(helpers.renameCache(t,options.cache.name))}),self.addEventListener("fetch",function(e){var t=router.match(e.request);t?e.respondWith(t(e.request)):router["default"]&&"GET"===e.request.method&&e.respondWith(router["default"](e.request))}),module.exports={networkOnly:strategies.networkOnly,networkFirst:strategies.networkFirst,cacheOnly:strategies.cacheOnly,cacheFirst:strategies.cacheFirst,fastest:strategies.fastest,router:router,options:options,cache:cache,uncache:uncache,precache:precache}; -},{"./helpers":1,"./options":3,"./router":5,"./strategies":9,"serviceworker-cache-polyfill":15}],13:[function(require,module,exports){ -function parse(e){for(var t,r=[],n=0,o=0,p="";null!=(t=PATH_REGEXP.exec(e));){var a=t[0],i=t[1],s=t.index;if(p+=e.slice(o,s),o=s+a.length,i)p+=i[1];else{p&&(r.push(p),p="");var u=t[2],c=t[3],l=t[4],f=t[5],g=t[6],x=t[7],h="+"===g||"*"===g,m="?"===g||"*"===g,y=u||"/",T=l||f||(x?".*":"[^"+y+"]+?");r.push({name:c||n++,prefix:u||"",delimiter:y,optional:m,repeat:h,pattern:escapeGroup(T)})}}return o r.value[TIMESTAMP_PROPERTY]) { + var t = r.value[URL_PROPERTY]; + u.push(t), s['delete'](t), r['continue'](); + } + }), (c.oncomplete = function() { + t(u); + }), (c.onabort = o); + }) + : Promise.resolve([]); + } + function expireExtraEntries(e, r) { + return r + ? new Promise(function(n, t) { + var o = [], + i = e.transaction(STORE_NAME, 'readwrite'), + u = i.objectStore(STORE_NAME), + c = u.index(TIMESTAMP_PROPERTY), + s = c.count(); + (c.count().onsuccess = function() { + var e = s.result; + e > r && + (c.openCursor().onsuccess = function(n) { + var t = n.target.result; + if (t) { + var i = t.value[URL_PROPERTY]; + o.push(i), u['delete'](i), e - o.length > r && + t['continue'](); + } + }); + }), (i.oncomplete = function() { + n(o); + }), (i.onabort = t); + }) + : Promise.resolve([]); + } + function expireEntries(e, r, n, t) { + return expireOldEntries(e, n, t).then(function(n) { + return expireExtraEntries(e, r).then(function(e) { + return n.concat(e); + }); + }); + } + var DB_PREFIX = 'sw-toolbox-', + DB_VERSION = 1, + STORE_NAME = 'store', + URL_PROPERTY = 'url', + TIMESTAMP_PROPERTY = 'timestamp', + cacheNameToDbPromise = {}; + module.exports = { + getDb: getDb, + setTimestampForUrl: setTimestampForUrl, + expireEntries: expireEntries, + }; + }, + {}, + ], + 3: [ + function(require, module, exports) { + 'use strict'; + var scope; + (scope = self.registration + ? self.registration.scope + : self.scope || + new URL('./', self.location).href), (module.exports = { + cache: { + name: '$$$toolbox-cache$$$' + scope + '$$$', + maxAgeSeconds: null, + maxEntries: null, + }, + debug: !1, + networkTimeoutSeconds: null, + preCacheItems: [], + successResponses: /^0|([123]\d\d)|(40[14567])|410$/, + }); + }, + {}, + ], + 4: [ + function(require, module, exports) { + 'use strict'; + var url = new URL('./', self.location), + basePath = url.pathname, + pathRegexp = require('path-to-regexp'), + Route = function(e, t, i, s) { + t instanceof RegExp + ? (this.fullUrlRegExp = t) + : (0 !== t.indexOf('/') && (t = basePath + t), (this.keys = [ + ]), (this.regexp = pathRegexp( + t, + this.keys + ))), (this.method = e), (this.options = s), (this.handler = i); + }; + (Route.prototype.makeHandler = function(e) { + var t; + if (this.regexp) { + var i = this.regexp.exec(e); + (t = {}), this.keys.forEach(function(e, s) { + t[e.name] = i[s + 1]; + }); + } + return function(e) { + return this.handler(e, t, this.options); + }.bind(this); + }), (module.exports = Route); + }, + { 'path-to-regexp': 13 }, + ], + 5: [ + function(require, module, exports) { + 'use strict'; + function regexEscape(e) { + return e.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + var Route = require('./route'), + keyMatch = function(e, t) { + for (var r = e.entries(), o = r.next(); !o.done; ) { + var n = new RegExp(o.value[0]); + if (n.test(t)) return o.value[1]; + o = r.next(); + } + return null; + }, + Router = function() { + (this.routes = new Map()), (this['default'] = null); + }; + ['get', 'post', 'put', 'delete', 'head', 'any'].forEach(function(e) { + Router.prototype[e] = function(t, r, o) { + return this.add(e, t, r, o); + }; + }), (Router.prototype.add = function(e, t, r, o) { + o = o || {}; + var n; + t instanceof RegExp + ? (n = RegExp) + : ((n = o.origin || self.location.origin), (n = n instanceof + RegExp + ? n.source + : regexEscape(n))), (e = e.toLowerCase()); + var u = new Route(e, t, r, o); + this.routes.has(n) || this.routes.set(n, new Map()); + var a = this.routes.get(n); + a.has(e) || a.set(e, new Map()); + var s = a.get(e), i = u.regexp || u.fullUrlRegExp; + s.set(i.source, u); + }), (Router.prototype.matchMethod = function(e, t) { + var r = new URL(t), o = r.origin, n = r.pathname; + return ( + this._match(e, keyMatch(this.routes, o), n) || + this._match(e, this.routes.get(RegExp), t) + ); + }), (Router.prototype._match = function(e, t, r) { + if (t) { + var o = t.get(e.toLowerCase()); + if (o) { + var n = keyMatch(o, r); + if (n) return n.makeHandler(r); + } + } + return null; + }), (Router.prototype.match = function(e) { + return ( + this.matchMethod(e.method, e.url) || + this.matchMethod('any', e.url) + ); + }), (module.exports = new Router()); + }, + { './route': 4 }, + ], + 6: [ + function(require, module, exports) { + 'use strict'; + function cacheFirst(e, r, t) { + return helpers.debug( + 'Strategy: cache first [' + e.url + ']', + t + ), helpers.openCache(t).then(function(r) { + return r.match(e).then(function(r) { + return r ? r : helpers.fetchAndCache(e, t); + }); + }); + } + var helpers = require('../helpers'); + module.exports = cacheFirst; + }, + { '../helpers': 1 }, + ], + 7: [ + function(require, module, exports) { + 'use strict'; + function cacheOnly(e, r, c) { + return helpers.debug( + 'Strategy: cache only [' + e.url + ']', + c + ), helpers.openCache(c).then(function(r) { + return r.match(e); + }); + } + var helpers = require('../helpers'); + module.exports = cacheOnly; + }, + { '../helpers': 1 }, + ], + 8: [ + function(require, module, exports) { + 'use strict'; + function fastest(e, n, t) { + return helpers.debug( + 'Strategy: fastest [' + e.url + ']', + t + ), new Promise(function(r, s) { + var c = !1, + o = [], + a = function(e) { + o.push(e.toString()), c + ? s( + new Error( + 'Both cache and network failed: "' + + o.join('", "') + + '"' + ) + ) + : (c = !0); + }, + h = function(e) { + e instanceof Response ? r(e) : a('No result returned'); + }; + helpers + .fetchAndCache(e.clone(), t) + .then(h, a), cacheOnly(e, n, t).then(h, a); + }); + } + var helpers = require('../helpers'), + cacheOnly = require('./cacheOnly'); + module.exports = fastest; + }, + { '../helpers': 1, './cacheOnly': 7 }, + ], + 9: [ + function(require, module, exports) { + module.exports = { + networkOnly: require('./networkOnly'), + networkFirst: require('./networkFirst'), + cacheOnly: require('./cacheOnly'), + cacheFirst: require('./cacheFirst'), + fastest: require('./fastest'), + }; + }, + { + './cacheFirst': 6, + './cacheOnly': 7, + './fastest': 8, + './networkFirst': 10, + './networkOnly': 11, + }, + ], + 10: [ + function(require, module, exports) { + 'use strict'; + function networkFirst(e, r, t) { + t = t || {}; + var s = t.successResponses || globalOptions.successResponses, + n = + t.networkTimeoutSeconds || globalOptions.networkTimeoutSeconds; + return helpers.debug( + 'Strategy: network first [' + e.url + ']', + t + ), helpers.openCache(t).then(function(r) { + var o, u, c = []; + if (n) { + var i = new Promise(function(t) { + o = setTimeout(function() { + r.match(e).then(function(e) { + e && t(e); + }); + }, 1e3 * n); + }); + c.push(i); + } + var a = helpers.fetchAndCache(e, t).then(function(e) { + if ((o && clearTimeout(o), s.test(e.status))) return e; + throw (helpers.debug( + 'Response was an HTTP error: ' + e.statusText, + t + ), (u = e), new Error('Bad response')); + })['catch'](function() { + return helpers.debug( + 'Network or response error, fallback to cache [' + + e.url + + ']', + t + ), r.match(e).then(function(e) { + return e || u; + }); + }); + return c.push(a), Promise.race(c); + }); + } + var globalOptions = require('../options'), + helpers = require('../helpers'); + module.exports = networkFirst; + }, + { '../helpers': 1, '../options': 3 }, + ], + 11: [ + function(require, module, exports) { + 'use strict'; + function networkOnly(e, r, t) { + return helpers.debug( + 'Strategy: network only [' + e.url + ']', + t + ), fetch(e); + } + var helpers = require('../helpers'); + module.exports = networkOnly; + }, + { '../helpers': 1 }, + ], + 12: [ + function(require, module, exports) { + 'use strict'; + function cache(e, t) { + return helpers.openCache(t).then(function(t) { + return t.add(e); + }); + } + function uncache(e, t) { + return helpers.openCache(t).then(function(t) { + return t['delete'](e); + }); + } + function precache(e) { + Array.isArray(e) || + (e = [e]), (options.preCacheItems = options.preCacheItems.concat( + e + )); + } + require('serviceworker-cache-polyfill'); + var options = require('./options'), + router = require('./router'), + helpers = require('./helpers'), + strategies = require('./strategies'); + helpers.debug('Service Worker Toolbox is loading'); + var flatten = function(e) { + return e.reduce(function(e, t) { + return e.concat(t); + }, []); + }; + self.addEventListener('install', function(e) { + var t = options.cache.name + '$$$inactive$$$'; + helpers.debug( + 'install event fired' + ), helpers.debug('creating cache [' + t + ']'), e.waitUntil( + helpers.openCache({ cache: { name: t } }).then(function(e) { + return Promise.all(options.preCacheItems) + .then(flatten) + .then(function(t) { + return helpers.debug( + 'preCache list: ' + (t.join(', ') || '(none)') + ), e.addAll(t); + }); + }) + ); + }), self.addEventListener('activate', function(e) { + helpers.debug('activate event fired'); + var t = options.cache.name + '$$$inactive$$$'; + e.waitUntil(helpers.renameCache(t, options.cache.name)); + }), self.addEventListener('fetch', function(e) { + var t = router.match(e.request); + t + ? e.respondWith(t(e.request)) + : router['default'] && + 'GET' === e.request.method && + e.respondWith(router['default'](e.request)); + }), (module.exports = { + networkOnly: strategies.networkOnly, + networkFirst: strategies.networkFirst, + cacheOnly: strategies.cacheOnly, + cacheFirst: strategies.cacheFirst, + fastest: strategies.fastest, + router: router, + options: options, + cache: cache, + uncache: uncache, + precache: precache, + }); + }, + { + './helpers': 1, + './options': 3, + './router': 5, + './strategies': 9, + 'serviceworker-cache-polyfill': 15, + }, + ], + 13: [ + function(require, module, exports) { + function parse(e) { + for ( + var t, r = [], n = 0, o = 0, p = ''; + null != (t = PATH_REGEXP.exec(e)); + + ) { + var a = t[0], i = t[1], s = t.index; + if (((p += e.slice(o, s)), (o = s + a.length), i)) p += i[1]; + else { + p && (r.push(p), (p = '')); + var u = t[2], + c = t[3], + l = t[4], + f = t[5], + g = t[6], + x = t[7], + h = '+' === g || '*' === g, + m = '?' === g || '*' === g, + y = u || '/', + T = l || f || (x ? '.*' : '[^' + y + ']+?'); + r.push({ + name: c || n++, + prefix: u || '', + delimiter: y, + optional: m, + repeat: h, + pattern: escapeGroup(T), + }); + } + } + return o < e.length && (p += e.substr(o)), p && r.push(p), r; + } + function compile(e) { + return tokensToFunction(parse(e)); + } + function tokensToFunction(e) { + for (var t = new Array(e.length), r = 0; r < e.length; r++) + 'object' == typeof e[r] && + (t[r] = new RegExp('^' + e[r].pattern + '$')); + return function(r) { + for (var n = '', o = r || {}, p = 0; p < e.length; p++) { + var a = e[p]; + if ('string' != typeof a) { + var i, s = o[a.name]; + if (null == s) { + if (a.optional) continue; + throw new TypeError( + 'Expected "' + a.name + '" to be defined' + ); + } + if (isarray(s)) { + if (!a.repeat) + throw new TypeError( + 'Expected "' + + a.name + + '" to not repeat, but received "' + + s + + '"' + ); + if (0 === s.length) { + if (a.optional) continue; + throw new TypeError( + 'Expected "' + a.name + '" to not be empty' + ); + } + for (var u = 0; u < s.length; u++) { + if (((i = encodeURIComponent(s[u])), !t[p].test(i))) + throw new TypeError( + 'Expected all "' + + a.name + + '" to match "' + + a.pattern + + '", but received "' + + i + + '"' + ); + n += (0 === u ? a.prefix : a.delimiter) + i; + } + } else { + if (((i = encodeURIComponent(s)), !t[p].test(i))) + throw new TypeError( + 'Expected "' + + a.name + + '" to match "' + + a.pattern + + '", but received "' + + i + + '"' + ); + n += a.prefix + i; + } + } else n += a; + } + return n; + }; + } + function escapeString(e) { + return e.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1'); + } + function escapeGroup(e) { + return e.replace(/([=!:$\/()])/g, '\\$1'); + } + function attachKeys(e, t) { + return (e.keys = t), e; + } + function flags(e) { + return e.sensitive ? '' : 'i'; + } + function regexpToRegexp(e, t) { + var r = e.source.match(/\((?!\?)/g); + if (r) + for (var n = 0; n < r.length; n++) + t.push({ + name: n, + prefix: null, + delimiter: null, + optional: !1, + repeat: !1, + pattern: null, + }); + return attachKeys(e, t); + } + function arrayToRegexp(e, t, r) { + for (var n = [], o = 0; o < e.length; o++) + n.push(pathToRegexp(e[o], t, r).source); + var p = new RegExp('(?:' + n.join('|') + ')', flags(r)); + return attachKeys(p, t); + } + function stringToRegexp(e, t, r) { + for ( + var n = parse(e), o = tokensToRegExp(n, r), p = 0; + p < n.length; + p++ + ) + 'string' != typeof n[p] && t.push(n[p]); + return attachKeys(o, t); + } + function tokensToRegExp(e, t) { + t = t || {}; + for ( + var r = t.strict, + n = t.end !== !1, + o = '', + p = e[e.length - 1], + a = 'string' == typeof p && /\/$/.test(p), + i = 0; + i < e.length; + i++ + ) { + var s = e[i]; + if ('string' == typeof s) o += escapeString(s); + else { + var u = escapeString(s.prefix), c = s.pattern; + s.repeat && (c += '(?:' + u + c + ')*'), (c = s.optional + ? u ? '(?:' + u + '(' + c + '))?' : '(' + c + ')?' + : u + '(' + c + ')'), (o += c); + } + } + return r || + (o = (a ? o.slice(0, -2) : o) + '(?:\\/(?=$))?'), (o += n + ? '$' + : r && a ? '' : '(?=\\/|$)'), new RegExp('^' + o, flags(t)); + } + function pathToRegexp(e, t, r) { + return (t = t || []), isarray(t) + ? r || (r = {}) + : ((r = t), (t = [])), e instanceof RegExp + ? regexpToRegexp(e, t, r) + : isarray(e) ? arrayToRegexp(e, t, r) : stringToRegexp(e, t, r); + } + var isarray = require('isarray'); + (module.exports = pathToRegexp), (module.exports.parse = parse), (module.exports.compile = compile), (module.exports.tokensToFunction = tokensToFunction), (module.exports.tokensToRegExp = tokensToRegExp); + var PATH_REGEXP = new RegExp( + [ + '(\\\\.)', + '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))', + ].join('|'), + 'g' + ); + }, + { isarray: 14 }, + ], + 14: [ + function(require, module, exports) { + module.exports = + Array.isArray || + function(r) { + return '[object Array]' == Object.prototype.toString.call(r); + }; + }, + {}, + ], + 15: [ + function(require, module, exports) { + Cache.prototype.addAll || + (Cache.prototype.addAll = function(t) { + function e(t) { + (this.name = + 'NetworkError'), (this.code = 19), (this.message = t); + } + var r = this; + return (e.prototype = Object.create( + Error.prototype + )), Promise.resolve() + .then(function() { + if (arguments.length < 1) throw new TypeError(); + return (t = t.map(function(t) { + return t instanceof Request ? t : String(t); + })), Promise.all( + t.map(function(t) { + 'string' == typeof t && (t = new Request(t)); + var r = new URL(t.url).protocol; + if ('http:' !== r && 'https:' !== r) + throw new e('Invalid scheme'); + return fetch(t.clone()); + }) + ); + }) + .then(function(e) { + return Promise.all( + e.map(function(e, n) { + return r.put(t[n], e); + }) + ); + }) + .then(function() {}); + }); + }, + {}, + ], + }, + {}, + [12] + )(12); }); (global => { 'use strict'; - // Assets - global.toolbox.router.get(/\/static\//, global.toolbox.cacheFirst, { cache: { name: 'static' } }); - global.toolbox.router.get('/(.*)', global.toolbox.fastest, {origin: 'https://secure.gravatar.com'}); + global.toolbox.router.get(/\/static\//, global.toolbox.cacheFirst, { + cache: { name: 'static' }, + }); + global.toolbox.router.get('/(.*)', global.toolbox.fastest, { + origin: 'https://secure.gravatar.com', + }); // API global.toolbox.router.get(/\/api\//, global.toolbox.networkFirst, { - cache: { - name: 'api', - maxEntries: 100 - } + cache: { + name: 'api', + maxEntries: 100, + }, }); // API GET calls @@ -69,8 +873,10 @@ Cache.prototype.addAll||(Cache.prototype.addAll=function(t){function e(t){this.n // Boilerplate to ensure our service worker takes control of the page as soon // as possible. - global.addEventListener('install', - event => event.waitUntil(global.skipWaiting())); - global.addEventListener('activate', - event => event.waitUntil(global.clients.claim())); + global.addEventListener('install', event => + event.waitUntil(global.skipWaiting()) + ); + global.addEventListener('activate', event => + event.waitUntil(global.clients.claim()) + ); })(self); diff --git a/server/test/helper.js b/server/test/helper.js index 2823ad678..0b30e09d7 100644 --- a/server/test/helper.js +++ b/server/test/helper.js @@ -21,8 +21,7 @@ function runMigrations() { path: './server/migrations', }, }); - return umzug.up() - .then(() => { + return umzug.up().then(() => { return sequelize.close(); }); } diff --git a/server/test/support.js b/server/test/support.js index c1646d62b..f2e9e534b 100644 --- a/server/test/support.js +++ b/server/test/support.js @@ -3,8 +3,9 @@ import { sequelize } from '../sequelize'; export function flushdb() { const sql = sequelize.getQueryInterface(); - const tables = Object.keys(sequelize.models).map((model) => - sql.quoteTable(sequelize.models[model].getTableName())); + const tables = Object.keys(sequelize.models).map(model => + sql.quoteTable(sequelize.models[model].getTableName()) + ); const query = `TRUNCATE ${tables.join(', ')} CASCADE`; return sequelize.query(query); @@ -24,7 +25,4 @@ const seed = async () => { }); }; -export { - seed, - sequelize, -}; +export { seed, sequelize }; diff --git a/server/utils/s3.js b/server/utils/s3.js index 4d4ab1f69..65351a98e 100644 --- a/server/utils/s3.js +++ b/server/utils/s3.js @@ -4,29 +4,28 @@ import moment from 'moment'; const makePolicy = () => { const policy = { conditions: [ - {'bucket': process.env.AWS_S3_UPLOAD_BUCKET_NAME}, + { bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME }, ['starts-with', '$key', ''], - {'acl': 'public-read'}, + { acl: 'public-read' }, ['content-length-range', 0, process.env.AWS_S3_UPLOAD_MAX_SIZE], ['starts-with', '$Content-Type', 'image'], ['starts-with', '$Cache-Control', ''], ], - expiration: moment().add(24*60, 'minutes').format('YYYY-MM-DDTHH:mm:ss\\Z'), + expiration: moment() + .add(24 * 60, 'minutes') + .format('YYYY-MM-DDTHH:mm:ss\\Z'), }; - return new Buffer(JSON.stringify(policy)).toString('base64') + return new Buffer(JSON.stringify(policy)).toString('base64'); }; -const signPolicy = (policy) => { - const signature = crypto.createHmac( - 'sha1', - process.env.AWS_SECRET_ACCESS_KEY - ).update(policy).digest('base64'); +const signPolicy = policy => { + const signature = crypto + .createHmac('sha1', process.env.AWS_SECRET_ACCESS_KEY) + .update(policy) + .digest('base64'); return signature; }; -export { - makePolicy, - signPolicy, -}; +export { makePolicy, signPolicy }; diff --git a/server/utils/truncate.js b/server/utils/truncate.js index 6fba7378f..85664daa4 100644 --- a/server/utils/truncate.js +++ b/server/utils/truncate.js @@ -5,7 +5,7 @@ truncate.defaultOptions = { stripTags: false, ellipsis: '...', decodeEntities: false, - excludes: ['h1', 'pre', ], + excludes: ['h1', 'pre'], }; const truncateMarkdown = (text, length) => { @@ -13,6 +13,4 @@ const truncateMarkdown = (text, length) => { return truncate(html, length); }; -export { - truncateMarkdown, -}; +export { truncateMarkdown }; diff --git a/yarn.lock b/yarn.lock index 5fc6dfc28..e0fe5b775 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,7 +79,7 @@ amdefine@>=0.0.4: version "1.0.0" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33" -ansi-escapes@^1.1.0, ansi-escapes@^1.4.0: +ansi-escapes@^1.0.0, ansi-escapes@^1.1.0, ansi-escapes@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -95,6 +95,12 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-styles@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.0.0.tgz#5404e93a544c4fec7f048262977bebfe3155e0c1" + dependencies: + color-convert "^1.0.0" + ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" @@ -114,6 +120,10 @@ ap@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" +app-root-path@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" + append-transform@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.3.0.tgz#d6933ce4a85f09445d9ccc4cc119051b7381a813" @@ -220,6 +230,10 @@ assert@^1.1.1: dependencies: util "0.10.3" +ast-types@0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.8.tgz#6cb6a40beba31f49f20928e28439fc14a3dab078" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -267,7 +281,15 @@ aws4@^1.2.1: version "1.5.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" -babel-code-frame@^6.16.0, babel-code-frame@^6.8.0: +babel-code-frame@6.22.0, babel-code-frame@^6.16.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-code-frame@^6.8.0: version "6.16.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.16.0.tgz#f90e60da0862909d3ce098733b5d3987c97cb8de" dependencies: @@ -1043,6 +1065,10 @@ babel-types@^6.0.19, babel-types@^6.10.2, babel-types@^6.13.0, babel-types@^6.14 lodash "^4.2.0" to-fast-properties "^1.0.1" +babylon@7.0.0-beta.8: + version "7.0.0-beta.8" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.8.tgz#2bdc5ae366041442c27e068cce6f0d7c06ea9949" + babylon@^6.0.18, babylon@^6.11.0, babylon@^6.7.0, babylon@^6.8.1: version "6.11.4" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.11.4.tgz#75e1f52187efa0cde5a541a7f7fdda38f6eb5bd2" @@ -1389,7 +1415,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1506,18 +1532,29 @@ cli-color@~0.3.2: memoizee "~0.3.8" timers-ext "0.1" -cli-cursor@^1.0.1: +cli-cursor@^1.0.1, cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" dependencies: restore-cursor "^1.0.1" +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + cli-table@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" dependencies: colors "1.0.3" +cli-truncate@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" + dependencies: + slice-ansi "0.0.4" + string-width "^1.0.1" + cli-usage@^0.1.1: version "0.1.4" resolved "https://registry.yarnpkg.com/cli-usage/-/cli-usage-0.1.4.tgz#7c01e0dc706c234b39c933838c8e20b2175776e2" @@ -1585,7 +1622,7 @@ codemirror@5.16.0, codemirror@^5.13.4: version "5.16.0" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.16.0.tgz#468031dc9bda1b52e041f0482e5aa7f2b4e79cef" -color-convert@^1.3.0: +color-convert@^1.0.0, color-convert@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.5.0.tgz#7a2b4efb4488df85bca6443cb038b7100fbe7de1" @@ -1841,6 +1878,19 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cosmiconfig@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37" + dependencies: + graceful-fs "^4.1.2" + js-yaml "^3.4.3" + minimist "^1.2.0" + object-assign "^4.0.1" + os-homedir "^1.0.1" + parse-json "^2.2.0" + pinkie-promise "^2.0.0" + require-from-string "^1.1.0" + crc@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.3.0.tgz#fa622e1bc388bf257309082d6b65200ce67090ba" @@ -1889,6 +1939,14 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -2068,6 +2126,10 @@ dashify@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/dashify/-/dashify-0.2.2.tgz#6a07415a01c91faf4a32e38d9dfba71f61cb20fe" +date-fns@^1.27.2: + version "1.28.4" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.4.tgz#7938aec34ba31fc8bd134d2344bc2e0bbfd95165" + date-now@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/date-now/-/date-now-1.0.1.tgz#bb7d086438debe4182a485fb3df3fbfb99d6153c" @@ -2341,6 +2403,10 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +elegant-spinner@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" + elliptic@^6.0.0: version "6.3.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.2.tgz#e4c81e0829cf0a65ab70e998b8232723b5c1bc48" @@ -2579,6 +2645,12 @@ eslint-config-airbnb@9.0.1: dependencies: eslint-config-airbnb-base "^3.0.0" +eslint-config-prettier@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-1.7.0.tgz#cda3ce22df1e852daa9370f1f3446e8b8a02ce44" + dependencies: + get-stdin "^5.0.1" + eslint-import-resolver-node@^0.2.0: version "0.2.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c" @@ -2629,6 +2701,12 @@ eslint-plugin-jsx-a11y@^1.5.3: jsx-ast-utils "^1.0.0" object-assign "^4.0.1" +eslint-plugin-prettier@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.0.1.tgz#2ae1216cf053dd728360ca8560bf1aabc8af3fa9" + dependencies: + requireindex "~1.1.0" + eslint-plugin-react@5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-5.2.2.tgz#7db068e1f5487f6871e4deef36a381c303eac161" @@ -2704,7 +2782,7 @@ estraverse@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" -esutils@^2.0.0, esutils@^2.0.2: +esutils@2.0.2, esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -2747,6 +2825,18 @@ exec-sh@^0.2.0: dependencies: merge "^1.1.3" +execa@^0.6.0: + version "0.6.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -2860,7 +2950,7 @@ fetch-test-server@^1.1.0: debug "^2.2.0" node-fetch "^1.5.1" -figures@^1.3.5: +figures@^1.3.5, figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" dependencies: @@ -2982,6 +3072,10 @@ flatten@1.0.2, flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" +flow-parser@0.43.0: + version "0.43.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.43.0.tgz#e2b8eb1ac83dd53f7b6b04a7c35b6a52c33479b7" + for-in@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8" @@ -3112,10 +3206,18 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stdin@5.0.1, get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -3168,16 +3270,7 @@ glob@5.x, glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: +glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -3188,6 +3281,15 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^4.3.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + glob@~3.1.21: version "3.1.21" resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" @@ -3703,6 +3805,10 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indent-string@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -3910,6 +4016,10 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" @@ -3934,7 +4044,7 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" -is-stream@^1.0.0, is-stream@^1.0.1: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -4209,6 +4319,13 @@ jest-matcher-utils@^15.1.0: dependencies: chalk "^1.1.3" +jest-matcher-utils@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz#5ecd9b63565d2b001f61fbf7ec4c7f537964564d" + dependencies: + chalk "^1.1.3" + pretty-format "^19.0.0" + jest-matchers@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-15.1.1.tgz#faff50acbbf9743323ec2270a24743cb59d638f0" @@ -4277,6 +4394,15 @@ jest-util@^15.1.1: jest-mock "^15.0.0" mkdirp "^0.5.1" +jest-validate@19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.0.tgz#8c6318a20ecfeaba0ba5378bfbb8277abded4173" + dependencies: + chalk "^1.1.1" + jest-matcher-utils "^19.0.0" + leven "^2.0.0" + pretty-format "^19.0.0" + jodid25519@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" @@ -4313,11 +4439,15 @@ js-tokens@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + js-tree@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/js-tree/-/js-tree-1.1.0.tgz#087ee3ec366a5b74eb14f486016c5e0e631f1670" -js-yaml@3.x, js-yaml@^3.5.1, js-yaml@~3.6.1: +js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@~3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" dependencies: @@ -4627,6 +4757,10 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +leven@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -4648,6 +4782,64 @@ liftoff@^2.1.0: rechoir "^0.6.2" resolve "^1.1.7" +lint-staged@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-3.4.0.tgz#52fa85dfc92bb1c6fe8ad0d0d98ca13924e03e4b" + dependencies: + app-root-path "^2.0.0" + cosmiconfig "^1.1.0" + execa "^0.6.0" + listr "^0.11.0" + minimatch "^3.0.0" + npm-which "^3.0.1" + staged-git-files "0.0.4" + +listr-silent-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + +listr-update-renderer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + strip-ansi "^3.0.1" + +listr-verbose-renderer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz#44dc01bb0c34a03c572154d4d08cde9b1dc5620f" + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +listr@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.11.0.tgz#5e778bc23806ac3ab984ed75564458151f39b03e" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + figures "^1.7.0" + indent-string "^2.1.0" + is-promise "^2.1.0" + is-stream "^1.1.0" + listr-silent-renderer "^1.1.1" + listr-update-renderer "^0.2.0" + listr-verbose-renderer "^0.4.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + ora "^0.2.3" + rxjs "^5.0.0-beta.11" + stream-to-observable "^0.1.0" + strip-ansi "^3.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5051,6 +5243,19 @@ lodash@~4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.9.0.tgz#4c20d742f03ce85dc700e0dd7ab9bcab85e6fc14" +log-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" + dependencies: + chalk "^1.0.0" + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -5303,7 +5508,7 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -5646,6 +5851,26 @@ normalizr@2.0.1: dependencies: lodash "^4.0.0" +npm-path@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.3.tgz#15cff4e1c89a38da77f56f6055b24f975dfb2bbe" + dependencies: + which "^1.2.10" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npm-which@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" + dependencies: + commander "^2.9.0" + npm-path "^2.0.2" + which "^1.2.10" + "npmlog@0 || 1 || 2 || 3": version "3.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-3.1.2.tgz#2d46fa874337af9498a2f12bb43d8d0be4a36873" @@ -5786,6 +6011,15 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" +ora@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + orchestrator@^0.3.0: version "0.3.7" resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.7.tgz#c45064e22c5a2a7b99734f409a95ffedc7d3c3df" @@ -5827,6 +6061,10 @@ osenv@0, osenv@^0.1.0, osenv@^0.1.3: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + package-json@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0" @@ -5934,6 +6172,10 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -6324,6 +6566,21 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +prettier@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.2.2.tgz#22d17c1132faaaea1f1d4faea31f19f7a1959f3e" + dependencies: + ast-types "0.9.8" + babel-code-frame "6.22.0" + babylon "7.0.0-beta.8" + chalk "1.1.3" + esutils "2.0.2" + flow-parser "0.43.0" + get-stdin "5.0.1" + glob "7.1.1" + jest-validate "19.0.0" + minimist "1.2.0" + pretty-error@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.0.1.tgz#8a7375b9fe26e43b5101794e4dbeac362a8d629a" @@ -6331,6 +6588,12 @@ pretty-error@^2.0.0: renderkid "~2.0.0" utila "~0.4" +pretty-format@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-19.0.0.tgz#56530d32acb98a3fa4851c4e2b9d37b420684c84" + dependencies: + ansi-styles "^3.0.0" + pretty-format@^3.7.0: version "3.8.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" @@ -6856,6 +7119,10 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -6867,6 +7134,10 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" +requireindex@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" + resolve-dir@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" @@ -6947,6 +7218,12 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" +rxjs@^5.0.0-beta.11: + version "5.3.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.3.0.tgz#d88ccbdd46af290cbdb97d5d8055e52453fabe2d" + dependencies: + symbol-observable "^1.0.1" + safestart@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/safestart/-/safestart-0.8.0.tgz#f6716cb863afa54db7fb2169c29ce85e30b5654d" @@ -7134,6 +7411,12 @@ shallowequal@0.2.2: dependencies: lodash.keys "^3.1.2" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" @@ -7283,6 +7566,10 @@ stackframe@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" +staged-git-files@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35" + statuses@1, "statuses@>= 1.2.1 < 2", statuses@^1.0.0, statuses@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.0.tgz#8e55758cb20e7682c1f4fce8dcab30bf01d1e07a" @@ -7325,6 +7612,10 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" +stream-to-observable@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -7378,6 +7669,10 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -7423,6 +7718,10 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" +symbol-observable@^1.0.1: + 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": version "3.1.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.1.4.tgz#02b279348d337debc39694c5c95f882d448a312a"