From 553a2505e6aa1e9cd371ddd64395ee2739de49f4 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Jan 2019 16:52:18 -0500 Subject: [PATCH 01/16] add google and github oauth2 login support --- api/controllers/AuthController.js | 2 ++ api/helpers/passport.js | 33 +++++++++++++++++++----------- assets/js/actions/login.js | 4 ++-- assets/js/actions/targets.js | 18 ++++++++++++---- assets/js/containers/Carousel.js | 6 ++++-- assets/js/lib/Ajax.js | 11 +++++----- assets/js/login.js | 6 ++++-- assets/js/reducers/targets.js | 7 +++++++ assets/js/targets.js | 6 ++++-- assets/styles/lib/vars.scss | 6 +++--- assets/styles/shared/carousel.scss | 3 +++ config/env/development.js | 2 +- config/env/production.js | 2 +- config/passport.js | 12 +++++++++++ config/protocols.js | 17 +++++++++++++++ 15 files changed, 101 insertions(+), 34 deletions(-) diff --git a/api/controllers/AuthController.js b/api/controllers/AuthController.js index f1d1ac9..502afca 100644 --- a/api/controllers/AuthController.js +++ b/api/controllers/AuthController.js @@ -134,6 +134,8 @@ module.exports = { // redirect if there is a 'next' param if (req.query.next) { res.status(302).set('Location', req.query.next) + } else if (req.query.code) { // if came from oauth callback + res.status(302).set('Location', '/app') } sails.log.info('user', user, 'authenticated successfully') diff --git a/api/helpers/passport.js b/api/helpers/passport.js index c034f07..0879682 100644 --- a/api/helpers/passport.js +++ b/api/helpers/passport.js @@ -48,8 +48,8 @@ function PassportHelper () { const protocol = strategies[key].protocol const callbackURL = strategies[key].callback let baseURL = '' - if (sails.config.appUrl && sails.config.appUrl !== null) { - baseURL = sails.config.appUrl + if (sails.config.custom.baseURL && sails.config.custom.baseURL !== null) { + baseURL = sails.config.custom.baseURL } else { sails.log.warn('Please add \'appUrl\' to configuration') baseURL = sails.getBaseurl() @@ -74,7 +74,12 @@ function PassportHelper () { if (!_.has(strategies, provider)) return res.redirect('/login') - passport.authenticate(provider, {})(req, res, req.next) + const scopes = { + google: ['email'], + github: ['user:email'] + } + + passport.authenticate(provider, { scope: scopes[provider] })(req, res, req.next) } // a callback helper to split by req this.callback = function (req, res, next) { @@ -116,29 +121,33 @@ function PassportHelper () { const pass = await Passport.findOne({ provider, - identifier: q.identifier.toString() + identifier: q.identifier }) let user if (!req.user) { - if (!pass) { // new user signing up, create a new user - user = await User.create(userAttrs).fetch() + if (!pass) { // new user signing up, create a new user and/or passport + user = await User.findOne({ email: userAttrs.email }) + if (!user) { + user = await User.create(userAttrs).fetch() + } await Passport.create({ ...q, user: user.id }) next(null, user) } else { // existing user logging in - if (_.has(q, 'tokens') && q.tokens !== passport.tokens) { - passport.tokens = q.tokens + if (_.has(q, 'tokens') && q.tokens !== pass.tokens) { + pass.tokens = q.tokens } - await passport.save() - user = User.findOne(passport.user) - next(null, user) + delete pass.id + await Passport.update({ id: pass.id }, { tokens: pass.tokens }) + user = await User.find({ id: passport.user }).limit(1) + next(null, user[0]) } } else { // user logged in and trying to add new Passport - if (!passport) { + if (!pass) { await Passport.create({ ...q, user: req.user.id diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js index 5d690f1..1976253 100644 --- a/assets/js/actions/login.js +++ b/assets/js/actions/login.js @@ -94,7 +94,7 @@ export const checkPassword = (email, password) => async (dispatch, getState) => } catch (e) { dispatch(setError({ type: 'password', - error: e.toString() + error: e.message })) dispatch(setWorking(false)) } @@ -122,7 +122,7 @@ export const signup = (email, password) => async (dispatch, getState) => { } catch (e) { dispatch(setError({ type: 'email', - error: e.toString() + error: e.message })) } } else { diff --git a/assets/js/actions/targets.js b/assets/js/actions/targets.js index 9c6fe56..ddaffdc 100644 --- a/assets/js/actions/targets.js +++ b/assets/js/actions/targets.js @@ -9,7 +9,8 @@ const ACTIONS = { delete_url: 'delete_url', list_url: 'list_url', set_editing: 'set_editing', - error: 'error' + error: 'error', + set_user: 'set_user' } export default ACTIONS @@ -24,6 +25,11 @@ export const setUrls = (urls) => ({ data: urls }) +export const setUser = user => ({ + type: ACTIONS.set_user, + data: user +}) + export const addUrl = url => ({ type: ACTIONS.add_url, data: url @@ -63,13 +69,17 @@ export const removeUrl = id => async (dispatch, getState) => { } } -export const fetchUrls = () => async (dispatch, getState) => { +export const fetchData = () => async (dispatch, getState) => { dispatch(setWorking(true)) try { - const { data } = await Ajax.get({ + const { data: user } = await Ajax.get({ + url: '/api/me' + }) + dispatch(setUser(user)) + const { data: urls } = await Ajax.get({ url: '/api/targets' }) - dispatch(setUrls(data)) + dispatch(setUrls(urls)) } catch (e) { dispatch({ type: ACTIONS.error, diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js index ec6aaa4..a97fc9e 100644 --- a/assets/js/containers/Carousel.js +++ b/assets/js/containers/Carousel.js @@ -45,9 +45,11 @@ const CarouselItem = props => ( {props.button} - {props.footer && + {props.footers && props.footers.length && } ) diff --git a/assets/js/lib/Ajax.js b/assets/js/lib/Ajax.js index c05d09d..cef001f 100644 --- a/assets/js/lib/Ajax.js +++ b/assets/js/lib/Ajax.js @@ -86,28 +86,29 @@ export default class Ajax { } xhr.onerror = () => { var data = xhr.response + try { data = JSON.parse(data) } catch (e) {} // method not allowed if (xhr.status === 405) { - reject(new AjaxError('405 Method Not Allowed', data, xhr)) + reject(new AjaxError('405 Method Not Allowed', data.error || data, xhr)) return } else if (xhr.status === 404) { - reject(new AjaxError('404 Not Found', data, xhr)) + reject(new AjaxError('404 Not Found', data.error || data, xhr)) return } try { // if the access token is invalid, try to use the refresh token - var json = JSON.parse(data) + var json = data if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('invalid') && ajaxcfg.refresh_token) { return Ajax.refresh(opts) } else if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('revoked')) { reject(new AjaxError('token revoked', data, xhr)) } } catch (e) { - reject(new AjaxError(e.toString(), data, xhr)) + reject(new AjaxError(e.toString(), data.error || data, xhr)) } finally { - reject(new AjaxError(data, xhr.status, xhr)) + reject(new AjaxError(data.error || data, xhr.status, xhr)) } } diff --git a/assets/js/login.js b/assets/js/login.js index 6295d50..790ffdf 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -87,7 +87,8 @@ class App extends React.Component { onButtonClick={() => this.dispatch(signup(this.state.user.email, this.state.user.password))} smallButton='Have an account?' onSmallButtonClick={() => this.dispatch(setCarousel(1))} - footer='Sign up with your Google account' /> + footers={['Sign up with Google', 'Sign up with Github']} + footerHrefs={['/auth/google', '/auth/github']} /> this.dispatch(checkEmail(this.state.user.email))} smallButton='Create account' onSmallButtonClick={() => this.dispatch(setCarousel(0))} - footer='Sign in with your Google account' /> + footers={['Sign in with Google', 'Sign in with Github']} + footerHrefs={['/auth/google', '/auth/github']} /> { return { working: data } + case Actions.set_user: + return { + user: { + ...state.user, + ...data + } + } case Actions.list_url: return { urls: data || [] diff --git a/assets/js/targets.js b/assets/js/targets.js index 6ccd4f0..04fd330 100644 --- a/assets/js/targets.js +++ b/assets/js/targets.js @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom' import Progress from './components/Progress' import UriListItem from './containers/UriListItem' import reducer from './reducers/targets' -import { fetchUrls, createNewUrl, setEditing } from './actions/targets' +import { fetchData, createNewUrl, setEditing } from './actions/targets' import '../styles/targets.scss' class App extends React.Component { @@ -38,7 +38,7 @@ class App extends React.Component { } } componentDidMount () { - this.dispatch(fetchUrls()) + this.dispatch(fetchData()) } getRegisteredUris () { return this.state.urls.map((item, i) => { @@ -55,6 +55,8 @@ class App extends React.Component {
diff --git a/assets/styles/lib/vars.scss b/assets/styles/lib/vars.scss index 6209ac7..a6d742d 100644 --- a/assets/styles/lib/vars.scss +++ b/assets/styles/lib/vars.scss @@ -24,8 +24,8 @@ $text-dark-2: $black-2; $text-light-1: white; $text-light-2: rgba(255,255,255,.75); -$accent-1: #731212; -$accent-2: #9a834d; -$accent-3: #D4DBF1; +$accent-1: #102237; +$accent-2: #18517c; +$accent-3: #4f91b8; $red: #FE4C52; diff --git a/assets/styles/shared/carousel.scss b/assets/styles/shared/carousel.scss index 0139b8f..5d83240 100644 --- a/assets/styles/shared/carousel.scss +++ b/assets/styles/shared/carousel.scss @@ -83,6 +83,7 @@ bottom: 20px; width: 100%; margin: 0; + line-height: 30px; color: $text-dark-2; .btn { @@ -91,6 +92,8 @@ a { color: $accent-2; text-decoration: none; + display: inline-block; + width: 100%; } } .button-row { diff --git a/config/env/development.js b/config/env/development.js index 7326a6a..7e5c205 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -347,7 +347,7 @@ module.exports = { * * ***************************************************************************/ custom: { - baseUrl: 'https://example.com', + baseURL: 'http://localhost:3000', internalEmailAddress: 'support@example.com' // mailgunDomain: 'mg.example.com', diff --git a/config/env/production.js b/config/env/production.js index 7d80a30..37892ef 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -348,7 +348,7 @@ module.exports = { * * ***************************************************************************/ custom: { - baseUrl: 'https://example.com', + baseURL: 'https://example.com', internalEmailAddress: 'support@example.com' // mailgunDomain: 'mg.example.com', diff --git a/config/passport.js b/config/passport.js index 7f54c0d..6e239dc 100644 --- a/config/passport.js +++ b/config/passport.js @@ -5,5 +5,17 @@ module.exports.passport = { local: { strategy: require('passport-local').Strategy + }, + google: { + strategy: require('passport-google-oauth20').Strategy, + protocol: 'oauth2', + callback: '/auth/google/callback', + options: {} + }, + github: { + strategy: require('passport-github2').Strategy, + protocol: 'oauth2', + callback: '/auth/github/callback', + options: {} } } diff --git a/config/protocols.js b/config/protocols.js index 6392ccc..0a6183e 100644 --- a/config/protocols.js +++ b/config/protocols.js @@ -90,6 +90,23 @@ module.exports.protocols = { return next(e) } } + }, + oauth2: { + login: async function (req, accessToken, refreshToken, profile, next) { + try { + const passportHelper = await sails.helpers.passport() + await passportHelper.connect(req, { + tokens: { + accessToken, + refreshToken + }, + identifier: profile.id, + protocol: 'oauth2' + }, profile, next) + } catch (e) { + return next(e, false) + } + } } } From 0ce58283af20a78bbd0d308a87531211a3d32c73 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 14:22:20 -0500 Subject: [PATCH 02/16] merge changes --- api/controllers/BooksController.js | 2 +- assets/js/actions/{targets.js => index.js} | 0 assets/js/containers/UriListItem.js | 2 +- assets/js/{targets.js => index.js} | 6 +-- assets/js/reducers/{targets.js => index.js} | 2 +- assets/styles/{targets.scss => index.scss} | 7 ---- assets/styles/lib/vars.scss | 10 ++++- assets/styles/shared/twopanels.scss | 37 ++++++++++++++++++ assets/templates/{targets.html => index.html} | 2 +- package.json | 38 +++++++++---------- webpack.config.js | 17 ++++++--- 11 files changed, 82 insertions(+), 41 deletions(-) rename assets/js/actions/{targets.js => index.js} (100%) rename assets/js/{targets.js => index.js} (93%) rename assets/js/reducers/{targets.js => index.js} (95%) rename assets/styles/{targets.scss => index.scss} (89%) rename assets/templates/{targets.html => index.html} (94%) diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index b4d15b7..ec5a6da 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -22,7 +22,7 @@ module.exports = { if (bookExists) { throw new HttpError(400, 'Version already exists') } else { - result = await Book.create(body) + result = await Book.create(body).fetch() } req.file('opds').upload(sails.config.skipperConfig, async function (err, uploaded) { diff --git a/assets/js/actions/targets.js b/assets/js/actions/index.js similarity index 100% rename from assets/js/actions/targets.js rename to assets/js/actions/index.js diff --git a/assets/js/containers/UriListItem.js b/assets/js/containers/UriListItem.js index 477379a..b32889d 100644 --- a/assets/js/containers/UriListItem.js +++ b/assets/js/containers/UriListItem.js @@ -4,7 +4,7 @@ import React from 'react' import IconButton from '../components/IconButton' import UnderlineInput from '../components/UnderlineInput' import '../../styles/shared/urilistitem.scss' -import { changeUrlField, setUrl, removeUrl, setEditing } from '../actions/targets' +import { changeUrlField, setUrl, removeUrl, setEditing } from '../actions' const uriRegex = /(.+:\/\/)?(.+\.)*(.+\.).{1,}(:\d+)?(.+)?/i const isbnRegex = /^(97(8|9))?\d{9}(\d|X)$/ diff --git a/assets/js/targets.js b/assets/js/index.js similarity index 93% rename from assets/js/targets.js rename to assets/js/index.js index 04fd330..b271865 100644 --- a/assets/js/targets.js +++ b/assets/js/index.js @@ -4,9 +4,9 @@ import React from 'react' import ReactDOM from 'react-dom' import Progress from './components/Progress' import UriListItem from './containers/UriListItem' -import reducer from './reducers/targets' -import { fetchData, createNewUrl, setEditing } from './actions/targets' -import '../styles/targets.scss' +import reducer from './reducers' +import { fetchData, createNewUrl, setEditing } from './actions' +import '../styles/index.scss' class App extends React.Component { constructor () { diff --git a/assets/js/reducers/targets.js b/assets/js/reducers/index.js similarity index 95% rename from assets/js/reducers/targets.js rename to assets/js/reducers/index.js index 1cc51ca..7c394aa 100644 --- a/assets/js/reducers/targets.js +++ b/assets/js/reducers/index.js @@ -1,6 +1,6 @@ 'use strict' -import Actions from '../actions/targets' +import Actions from '../actions' const reducer = (state = {}, action) => { const { type, data } = action diff --git a/assets/styles/targets.scss b/assets/styles/index.scss similarity index 89% rename from assets/styles/targets.scss rename to assets/styles/index.scss index 9283fe1..e55ba57 100644 --- a/assets/styles/targets.scss +++ b/assets/styles/index.scss @@ -1,13 +1,6 @@ @import 'lib/default'; @import 'shared/twopanels'; -.nav { - header { - height: 50px; - line-height: 50px; - padding: 0 14px; - } -} .content { padding: 14px 0 42px 0; position: relative; diff --git a/assets/styles/lib/vars.scss b/assets/styles/lib/vars.scss index a6d742d..034473a 100644 --- a/assets/styles/lib/vars.scss +++ b/assets/styles/lib/vars.scss @@ -14,6 +14,12 @@ $black-3: rgba(0,0,0,.38); $black-4: rgba(0,0,0,.12); $black-5: rgba(0,0,0,.07); +$white-1: white; +$white-2: rgba(255,255,255,.75); +$white-3: rgba(255,255,255,.35); +$white-4: rgba(255,255,255,.10); +$white-5: rgba(255,255,255,.03); + $auth-width: 450px; $background-1: #f2f2f2; @@ -21,8 +27,8 @@ $background-2: white; $text-dark-1: $black-1; $text-dark-2: $black-2; -$text-light-1: white; -$text-light-2: rgba(255,255,255,.75); +$text-light-1: $white-1; +$text-light-2: $white-2; $accent-1: #102237; $accent-2: #18517c; diff --git a/assets/styles/shared/twopanels.scss b/assets/styles/shared/twopanels.scss index 54b4ff3..915f873 100644 --- a/assets/styles/shared/twopanels.scss +++ b/assets/styles/shared/twopanels.scss @@ -4,4 +4,41 @@ background: $accent-1; color: $text-light-1; box-shadow: $shadow-1; + + header { + height: 50px; + line-height: 50px; + padding: 0 14px; + } + ul { + list-style: none; + margin: 0; + padding: 0; + + li { + height: 50px; + line-height: 50px; + border-bottom: 1px solid $white-4; + + &:hover a { + background: $white-5; + } + &:last-of-type { + border-bottom: none + } + + a { + display: inline-block; + height: 100%; + width: 100%; + padding: 0 12px; + text-decoration: none !important; + color: $white-2; + + &.active { + background: $white-4; + } + } + } + } } diff --git a/assets/templates/targets.html b/assets/templates/index.html similarity index 94% rename from assets/templates/targets.html rename to assets/templates/index.html index aa9704d..15e0a22 100644 --- a/assets/templates/targets.html +++ b/assets/templates/index.html @@ -4,7 +4,7 @@ - RoE - Push Targets + River of Ebooks <% for (item of htmlWebpackPlugin.options.links) { if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %> <%= key %>="<%= item[key] %>"<% } %> /><% diff --git a/package.json b/package.json index 89ab54f..d22e7fb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,25 @@ "version": "0.0.0", "description": "a Sails application", "keywords": [], + "scripts": { + "start": "npm-run-all --parallel open:client lift", + "start:debug": "npm-run-all --parallel open:client debug", + "start:prod": "npm-run-all --parallel build:prod lift", + "open:client": "webpack-dev-server --mode development", + "build": "npm run build:prod", + "build:dev": "webpack --mode development", + "build:prod": "webpack --mode production", + "clean": "rimraf .tmp && mkdirp .tmp/public", + "lift": "sails lift", + "forever": "sudo ./node_modules/.bin/pm2 start ecosystem.config.js --env production", + "stop": "sudo ./node_modules/.bin/pm2 delete roe-base", + "test": "npm run lint && npm run custom-tests && echo 'Done.'", + "lint": "standard && echo '✔ Your .js files look good.'", + "debug": "node --inspect app.js", + "custom-tests": "echo 'Nothing yet'", + "db:migrate": "knex migrate:latest", + "db:rollback": "knex migrate:rollback" + }, "dependencies": { "@sailshq/connect-redis": "^3.2.1", "@sailshq/lodash": "^3.10.3", @@ -50,25 +69,6 @@ "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" }, - "scripts": { - "start": "npm-run-all --parallel open:client lift", - "start:debug": "npm-run-all --parallel open:client debug", - "start:prod": "npm-run-all --parallel build:prod lift", - "open:client": "webpack-dev-server --mode development", - "build": "npm run build:prod", - "build:dev": "webpack --mode development", - "build:prod": "webpack --mode production", - "clean": "rimraf .tmp && mkdirp .tmp/public", - "lift": "sails lift", - "forever": "sudo ./node_modules/.bin/pm2 start ecosystem.config.js --env production", - "stop": "sudo ./node_modules/.bin/pm2 delete roe-base", - "test": "npm run lint && npm run custom-tests && echo 'Done.'", - "lint": "standard && echo '✔ Your .js files look good.'", - "debug": "node --inspect app.js", - "custom-tests": "echo 'Nothing yet'", - "db:migrate": "knex migrate:latest", - "db:rollback": "knex migrate:rollback" - }, "main": "app.js", "repository": { "type": "git", diff --git a/webpack.config.js b/webpack.config.js index 51f049a..90fb3ad 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (env, argv) => { mode: mode || 'development', entry: { login: './assets/js/login.js', - targets: './assets/js/targets.js' + index: './assets/js/index.js' }, output: { path: path.join(__dirname, '/.tmp/public'), @@ -41,10 +41,10 @@ module.exports = (env, argv) => { chunks: ['login'] }), new HtmlWebpackPlugin({ - template: 'assets/templates/targets.html', - links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'targets.css' }] : [], - filename: path.join(__dirname, '/.tmp/public/targets.html'), - chunks: ['targets'] + template: 'assets/templates/index.html', + links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'index.css' }] : [], + filename: path.join(__dirname, '/.tmp/public/index.html'), + chunks: ['index'] }), new MiniCssExtractPlugin({ filename: '[name].css' @@ -52,6 +52,11 @@ module.exports = (env, argv) => { new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(mode) }) - ] + ], + devServer: { + historyApiFallback: true, + disableHostCheck: true, + port: 8080 + } } } From 9eae9a0215308aab292abba2cb8a881c757b63f9 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 14:43:18 -0500 Subject: [PATCH 03/16] add request package and post to target urls --- api/controllers/BooksController.js | 12 +- api/controllers/TargetController.js | 8 +- assets/js/index.js | 8 +- assets/styles/shared/twopanels.scss | 14 ++ package.json | 1 + shrinkwrap.yaml | 323 ++++++++++++++++++++++++++++ 6 files changed, 358 insertions(+), 8 deletions(-) create mode 100644 shrinkwrap.yaml diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index ec5a6da..da3ec6e 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -6,6 +6,7 @@ */ const HttpError = require('../errors/HttpError') +const request = require('request') module.exports = { publish: async function (req, res) { @@ -65,9 +66,18 @@ module.exports = { } async function sendUpdatesAsync (id) { - const book = await Book.find({ id }) + const book = await Book.findOne({ id }) const targets = await TargetUrl.find() for (const i in targets) { sails.log('sending ' + book.id + ' info to ' + targets[i].url) + request.post({ + url: targets[i].url, + headers: { 'User-Agent': 'RoE-aggregator' }, + form: book + }, function (err, httpResp, body) { + if (err) { + sails.log(`error: failed to send book ${id} to ${targets[i].url}`) + } + }) } } diff --git a/api/controllers/TargetController.js b/api/controllers/TargetController.js index 822d909..057027e 100644 --- a/api/controllers/TargetController.js +++ b/api/controllers/TargetController.js @@ -21,10 +21,10 @@ module.exports = { try { const id = req.param('id') const value = req.param('url') - const author = req.param('author') - const publisher = req.param('publisher') - const title = req.param('title') - const isbn = req.param('isbn') + const author = req.param('author') || '' + const publisher = req.param('publisher') || '' + const title = req.param('title') || '' + const isbn = req.param('isbn') || '' if (value.length) { const url = await TargetUrl.update({ id, user: req.user.id }, { url: value, diff --git a/assets/js/index.js b/assets/js/index.js index b271865..6afbcc9 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -54,9 +54,11 @@ class App extends React.Component {
this.dispatch(setEditing(null))}>
diff --git a/assets/styles/shared/twopanels.scss b/assets/styles/shared/twopanels.scss index 915f873..54d6965 100644 --- a/assets/styles/shared/twopanels.scss +++ b/assets/styles/shared/twopanels.scss @@ -9,6 +9,20 @@ height: 50px; line-height: 50px; padding: 0 14px; + + h2 { + margin: -10px 0 0 0; + padding: 0; + font-weight: normal; + font-size: 12pt; + height: 36px; + line-height: 36px; + color: $white-3; + } + a { + text-decoration: none; + color: $accent-3; + } } ul { list-style: none; diff --git a/package.json b/package.json index d22e7fb..aba769e 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "pm2": "^3.2.2", "react": "^16.6.0", "react-dom": "^16.6.0", + "request": "^2.88.0", "sails": "^1.0.2", "sails-hook-grunt": "^3.0.2", "sails-hook-orm": "^2.1.1", diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml new file mode 100644 index 0000000..5dcf30d --- /dev/null +++ b/shrinkwrap.yaml @@ -0,0 +1,323 @@ +dependencies: + request: 2.88.0 +packages: + /ajv/6.8.1: + dependencies: + fast-deep-equal: 2.0.1 + fast-json-stable-stringify: 2.0.0 + json-schema-traverse: 0.4.1 + uri-js: 4.2.2 + dev: false + resolution: + integrity: sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ== + /asn1/0.2.4: + dependencies: + safer-buffer: 2.1.2 + dev: false + resolution: + integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + /assert-plus/1.0.0: + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + /asynckit/0.4.0: + dev: false + resolution: + integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k= + /aws-sign2/0.7.0: + dev: false + resolution: + integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + /aws4/1.8.0: + dev: false + resolution: + integrity: sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + /bcrypt-pbkdf/1.0.2: + dependencies: + tweetnacl: 0.14.5 + dev: false + resolution: + integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + /caseless/0.12.0: + dev: false + resolution: + integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + /combined-stream/1.0.7: + dependencies: + delayed-stream: 1.0.0 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + /core-util-is/1.0.2: + dev: false + resolution: + integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + /dashdash/1.14.1: + dependencies: + assert-plus: 1.0.0 + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + /delayed-stream/1.0.0: + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + /ecc-jsbn/0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: false + resolution: + integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + /extend/3.0.2: + dev: false + resolution: + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + /extsprintf/1.3.0: + dev: false + engines: + '0': node >=0.6.0 + resolution: + integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + /extsprintf/1.4.0: + dev: false + engines: + '0': node >=0.6.0 + resolution: + integrity: sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + /fast-deep-equal/2.0.1: + dev: false + resolution: + integrity: sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + /fast-json-stable-stringify/2.0.0: + dev: false + resolution: + integrity: sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + /forever-agent/0.6.1: + dev: false + resolution: + integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + /form-data/2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.7 + mime-types: 2.1.21 + dev: false + engines: + node: '>= 0.12' + resolution: + integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + /getpass/0.1.7: + dependencies: + assert-plus: 1.0.0 + dev: false + resolution: + integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + /har-schema/2.0.0: + dev: false + engines: + node: '>=4' + resolution: + integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + /har-validator/5.1.3: + dependencies: + ajv: 6.8.1 + har-schema: 2.0.0 + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + /http-signature/1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.1 + sshpk: 1.16.1 + dev: false + engines: + node: '>=0.8' + npm: '>=1.3.7' + resolution: + integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + /is-typedarray/1.0.0: + dev: false + resolution: + integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + /isstream/0.1.2: + dev: false + resolution: + integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + /jsbn/0.1.1: + dev: false + resolution: + integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + /json-schema-traverse/0.4.1: + dev: false + resolution: + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + /json-schema/0.2.3: + dev: false + resolution: + integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + /json-stringify-safe/5.0.1: + dev: false + resolution: + integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + /jsprim/1.4.1: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.2.3 + verror: 1.10.0 + dev: false + engines: + '0': node >=0.6.0 + resolution: + integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + /mime-db/1.37.0: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== + /mime-types/2.1.21: + dependencies: + mime-db: 1.37.0 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + /oauth-sign/0.9.0: + dev: false + resolution: + integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + /performance-now/2.1.0: + dev: false + resolution: + integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + /psl/1.1.31: + dev: false + resolution: + integrity: sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== + /punycode/1.4.1: + dev: false + resolution: + integrity: sha1-wNWmOycYgArY4esPpSachN1BhF4= + /punycode/2.1.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + /qs/6.5.2: + dev: false + engines: + node: '>=0.6' + resolution: + integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + /request/2.88.0: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.8.0 + caseless: 0.12.0 + combined-stream: 1.0.7 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.3 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.21 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.2 + safe-buffer: 5.1.2 + tough-cookie: 2.4.3 + tunnel-agent: 0.6.0 + uuid: 3.3.2 + dev: false + engines: + node: '>= 4' + resolution: + integrity: sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + /safe-buffer/5.1.2: + dev: false + resolution: + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + /safer-buffer/2.1.2: + dev: false + resolution: + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + /sshpk/1.16.1: + dependencies: + asn1: 0.2.4 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: false + engines: + node: '>=0.10.0' + hasBin: true + resolution: + integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + /tough-cookie/2.4.3: + dependencies: + psl: 1.1.31 + punycode: 1.4.1 + dev: false + engines: + node: '>=0.8' + resolution: + integrity: sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + /tunnel-agent/0.6.0: + dependencies: + safe-buffer: 5.1.2 + dev: false + resolution: + integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + /tweetnacl/0.14.5: + dev: false + resolution: + integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + /uri-js/4.2.2: + dependencies: + punycode: 2.1.1 + dev: false + resolution: + integrity: sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + /uuid/3.3.2: + dev: false + hasBin: true + resolution: + integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + /verror/1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.0 + dev: false + engines: + '0': node >=0.6.0 + resolution: + integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= +registry: 'https://registry.npmjs.org/' +shrinkwrapMinorVersion: 9 +shrinkwrapVersion: 3 +specifiers: + request: ^2.88.0 From 0fcafdc71115edb174b3571382c29f1c68130343 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 14:44:39 -0500 Subject: [PATCH 04/16] fix color of user email --- assets/styles/shared/twopanels.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/shared/twopanels.scss b/assets/styles/shared/twopanels.scss index 54d6965..8c72e21 100644 --- a/assets/styles/shared/twopanels.scss +++ b/assets/styles/shared/twopanels.scss @@ -17,7 +17,7 @@ font-size: 12pt; height: 36px; line-height: 36px; - color: $white-3; + color: $white-2; } a { text-decoration: none; From 8a0c56644ca02f67dcef845728c8a31ac28022fc Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 16:08:45 -0500 Subject: [PATCH 05/16] add user settings page --- api/controllers/AuthController.js | 5 +- api/controllers/TargetController.js | 2 +- api/controllers/UserController.js | 4 +- assets/js/actions/index.js | 28 +++++++ assets/js/actions/login.js | 2 +- assets/js/index.js | 124 ++++++++++++++++++++++------ assets/js/login.js | 4 +- assets/js/reducers/index.js | 2 +- assets/styles/index.scss | 15 +++- assets/styles/shared/twopanels.scss | 1 - config/protocols.js | 43 +++++++++- config/routes.js | 8 +- package.json | 1 + shrinkwrap.yaml | 104 +++++++++++++++++++++++ views/pages/app.ejs | 1 + views/pages/targets.ejs | 1 - 16 files changed, 300 insertions(+), 45 deletions(-) create mode 100644 views/pages/app.ejs delete mode 100644 views/pages/targets.ejs diff --git a/api/controllers/AuthController.js b/api/controllers/AuthController.js index 502afca..5460626 100644 --- a/api/controllers/AuthController.js +++ b/api/controllers/AuthController.js @@ -115,6 +115,8 @@ module.exports = { } passportHelper.callback(req, res, function (err, user, info, status) { + // console.log(err) + // console.log(user) if (err || !user) { sails.log.warn(user, err, info, status) if (!err && info) { @@ -126,6 +128,7 @@ module.exports = { req.login(user, function (err) { if (err) { sails.log.warn(err) + // console.log(err) return negotiateError(err) } @@ -135,7 +138,7 @@ module.exports = { if (req.query.next) { res.status(302).set('Location', req.query.next) } else if (req.query.code) { // if came from oauth callback - res.status(302).set('Location', '/app') + res.status(302).set('Location', '/targets') } sails.log.info('user', user, 'authenticated successfully') diff --git a/api/controllers/TargetController.js b/api/controllers/TargetController.js index 057027e..6497265 100644 --- a/api/controllers/TargetController.js +++ b/api/controllers/TargetController.js @@ -3,7 +3,7 @@ const HttpError = require('../errors/HttpError') module.exports = { show: function (req, res) { - res.view('pages/targets', { + res.view('pages/app', { email: req.user.email }) }, diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index 03f8604..768051f 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -17,12 +17,11 @@ module.exports = { error: err.toString() }) } - res.json(user) }) }, - update: async function (req, res, next) { + edit: async function (req, res, next) { const passportHelper = await sails.helpers.passport() passportHelper.protocols.local.update(req.body, function (err, user) { if (err) { @@ -30,7 +29,6 @@ module.exports = { error: err.toString() }) } - res.json(user) }) }, diff --git a/assets/js/actions/index.js b/assets/js/actions/index.js index ddaffdc..8f1f566 100644 --- a/assets/js/actions/index.js +++ b/assets/js/actions/index.js @@ -126,3 +126,31 @@ export const setUrl = (value) => async (dispatch, getState) => { dispatch(setWorking(false)) } } + +export const editUser = (user) => async (dispatch, getState) => { + dispatch(setWorking(true)) + + try { + if (!user.currentPassword) throw new Error('Please enter your current password.') + await Ajax.patch({ + url: '/api/me', + data: { + id: user.id, + email: user.email, + password: user.password, + currentPassword: user.currentPassword + } + }) + dispatch({ + type: ACTIONS.error, + data: null + }) + } catch (e) { + dispatch({ + type: ACTIONS.error, + data: e + }) + } finally { + dispatch(setWorking(false)) + } +} diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js index 1976253..26347a5 100644 --- a/assets/js/actions/login.js +++ b/assets/js/actions/login.js @@ -47,7 +47,7 @@ export const clearError = () => ({ export const setLoggedIn = (data) => (dispatch, getState) => { window.localStorage.setItem('roe-token', JSON.stringify(data)) - window.location.href = '/app' + window.location.href = '/targets' } export const checkEmail = email => async (dispatch, getState) => { diff --git a/assets/js/index.js b/assets/js/index.js index 6afbcc9..37a484e 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -2,10 +2,12 @@ import React from 'react' import ReactDOM from 'react-dom' +import { BrowserRouter as Router, Route, NavLink, Switch, Redirect } from 'react-router-dom' import Progress from './components/Progress' import UriListItem from './containers/UriListItem' import reducer from './reducers' -import { fetchData, createNewUrl, setEditing } from './actions' +import { fetchData, createNewUrl, setEditing, editUser } from './actions' +import UnderlineInput from './components/UnderlineInput' import '../styles/index.scss' class App extends React.Component { @@ -14,8 +16,10 @@ class App extends React.Component { this.state = { error: '', user: { + id: '', email: '', - password: '' + password: '', + currentPassword: '' }, urls: [], editingUrl: null, @@ -24,6 +28,8 @@ class App extends React.Component { this.dispatch = this.dispatch.bind(this) this.getRegisteredUris = this.getRegisteredUris.bind(this) + this.setUserValue = this.setUserValue.bind(this) + this.saveUser = this.saveUser.bind(this) } dispatch (action) { if (!action) throw new Error('dispatch: missing action') @@ -49,33 +55,97 @@ class App extends React.Component { editing={this.state.editingUrl === item.id} />) }) } + setUserValue (which, e) { + this.setState({ + user: { + ...this.state.user, + [which]: e.target.value + } + }) + } + saveUser () { + this.dispatch(editUser(this.state.user)) + this.setState({ + user: { + ...this.state.user, + email: this.state.user.email, + password: '', + currentPassword: '' + } + }) + } render () { return ( -
this.dispatch(setEditing(null))}> - -
- - {this.state.error &&
{this.state.error}
} -
-
-

Push URIs

-

Newly published books will be sent to these addresses.

-
- -
-
    - {this.getRegisteredUris()} -
-
-
+ +
this.dispatch(setEditing(null))}> + +
+ + {this.state.error &&
{this.state.error}
} + + ( +
+
+
+

Push URIs

+

Newly published books will be sent to these addresses.

+
+ +
+
    + {this.getRegisteredUris()} +
+
+ )} /> + + ( +
+
+
+

My account

+

User account settings

+
+
+
+ this.setUserValue('email', e)} /> + this.setUserValue('password', e)} /> + this.setUserValue('currentPassword', e)} /> +
+ +
+
+
+ )} /> + + } /> +
+
+
+
) } } diff --git a/assets/js/login.js b/assets/js/login.js index 790ffdf..940e983 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -108,8 +108,8 @@ class App extends React.Component { error={this.state.passwordError} button='Sign in' onButtonClick={() => this.dispatch(checkPassword(this.state.user.email, this.state.user.password))} - smallButton='Forgot password?' - onSmallButtonClick={() => this.dispatch(setCarousel(3))} /> + comment={null/*smallButton='Forgot password?' + onSmallButtonClick={() => this.dispatch(setCarousel(3))}*/} /> { } case Actions.error: return { - error: data.message + error: (data || {}).message || '' } default: return {} } diff --git a/assets/styles/index.scss b/assets/styles/index.scss index e55ba57..42e3935 100644 --- a/assets/styles/index.scss +++ b/assets/styles/index.scss @@ -13,9 +13,10 @@ padding: 0 14px; margin: -14px 0 8px 0; } - & > header { - padding: 0 14px; - + & > div { + & > header { + padding: 0 14px; + } h1 { text-shadow: 1px 1px 2px $black-3; } @@ -35,6 +36,14 @@ list-style: none; // overflow: hidden; } + .inputs { + padding: 20px 14px; + + .buttons { + margin-top: 14px; + text-align: right; + } + } &.working { & > .progress { top: 0; diff --git a/assets/styles/shared/twopanels.scss b/assets/styles/shared/twopanels.scss index 8c72e21..b53c1a1 100644 --- a/assets/styles/shared/twopanels.scss +++ b/assets/styles/shared/twopanels.scss @@ -6,7 +6,6 @@ box-shadow: $shadow-1; header { - height: 50px; line-height: 50px; padding: 0 14px; diff --git a/config/protocols.js b/config/protocols.js index 0a6183e..aed2dbe 100644 --- a/config/protocols.js +++ b/config/protocols.js @@ -66,7 +66,48 @@ module.exports.protocols = { } }, update: async function (user, next) { - throw new Error('not implemented') + try { + const dbUser = await User.findOne({ + id: user.id + }) + if (!dbUser) throw new Error('an account with that id was not found') + + const passport = await Passport.findOne({ + protocol: 'local', + user: user.id + }) + if (!user.currentPassword && passport) throw new Error('Missing current password') + if (passport) { + const res = await Passport.validatePassword(user.currentPassword, passport) + if (!res) throw new Error('incorrect password') + + await User.update({ id: user.id }, { + email: user.email + }) + if (user.password && user.password.length) { + await Passport.update({ id: passport.id }, { + password: user.password + }) + } + } else { // no password yet, add one + await User.update({ id: user.id }, { + email: user.email + }) + if (user.password && user.password.length) { + const token = generateToken() + await Passport.create({ + protocol: 'local', + password: user.password, + user: dbUser.id, + accesstoken: token + }) + } + } + delete dbUser.password + next(null, dbUser) + } catch (e) { + return next(e) + } }, connect: async function (req, res, next) { try { diff --git a/config/routes.js b/config/routes.js index dfe64c9..e85fc02 100644 --- a/config/routes.js +++ b/config/routes.js @@ -31,7 +31,9 @@ module.exports.routes = { 'GET /register': { view: 'pages/login' }, - 'GET /app': 'TargetController.show', + // figure out why proper clientside routing breaks the backend session + 'GET /account': 'TargetController.show', + 'GET /targets': 'TargetController.show', /*************************************************************************** * * @@ -55,6 +57,8 @@ module.exports.routes = { 'POST /auth/email_available': 'AuthController.emailAvailable', // 'POST /auth/local': 'AuthController.callback', // 'POST /auth/local/:action': 'AuthController.callback', + 'GET /api/me': 'UserController.me', + 'PATCH /api/me': 'UserController.edit', 'POST /auth/:provider': 'AuthController.callback', 'POST /auth/:provider/:action': 'AuthController.callback', @@ -64,9 +68,7 @@ module.exports.routes = { 'GET /auth/:provider/:action': 'AuthController.callback', 'POST /api/publish': 'BooksController.publish', - 'GET /api/books': 'BooksController.list', - 'GET /api/me': 'UserController.me', 'POST /api/targets': 'TargetController.create', 'PATCH /api/targets/:id': 'TargetController.edit', diff --git a/package.json b/package.json index aba769e..5fff937 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "mocha": "^5.2.0", "node-sass": "^4.9.4", "npm-run-all": "^4.1.3", + "react-router-dom": "^4.3.1", "rimraf": "^2.6.2", "sass-loader": "^7.1.0", "standard": "^12.0.1", diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index 5dcf30d..282f6e6 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -1,5 +1,7 @@ dependencies: request: 2.88.0 +devDependencies: + react-router-dom: 4.3.1 packages: /ajv/6.8.1: dependencies: @@ -136,6 +138,20 @@ packages: node: '>=6' resolution: integrity: sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + /history/4.7.2: + dependencies: + invariant: 2.2.4 + loose-envify: 1.4.0 + resolve-pathname: 2.2.0 + value-equal: 0.4.0 + warning: 3.0.0 + dev: true + resolution: + integrity: sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== + /hoist-non-react-statics/2.5.5: + dev: true + resolution: + integrity: sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== /http-signature/1.2.0: dependencies: assert-plus: 1.0.0 @@ -147,14 +163,28 @@ packages: npm: '>=1.3.7' resolution: integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + /invariant/2.2.4: + dependencies: + loose-envify: 1.4.0 + dev: true + resolution: + integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== /is-typedarray/1.0.0: dev: false resolution: integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + /isarray/0.0.1: + dev: true + resolution: + integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= /isstream/0.1.2: dev: false resolution: integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + /js-tokens/4.0.0: + dev: true + resolution: + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== /jsbn/0.1.1: dev: false resolution: @@ -182,6 +212,13 @@ packages: '0': node >=0.6.0 resolution: integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + /loose-envify/1.4.0: + dependencies: + js-tokens: 4.0.0 + dev: true + hasBin: true + resolution: + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== /mime-db/1.37.0: dev: false engines: @@ -200,10 +237,29 @@ packages: dev: false resolution: integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + /object-assign/4.1.1: + dev: true + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + /path-to-regexp/1.7.0: + dependencies: + isarray: 0.0.1 + dev: true + resolution: + integrity: sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= /performance-now/2.1.0: dev: false resolution: integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + /prop-types/15.6.2: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: true + resolution: + integrity: sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== /psl/1.1.31: dev: false resolution: @@ -224,6 +280,33 @@ packages: node: '>=0.6' resolution: integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + /react-router-dom/4.3.1: + dependencies: + history: 4.7.2 + invariant: 2.2.4 + loose-envify: 1.4.0 + prop-types: 15.6.2 + react-router: 4.3.1 + warning: 4.0.2 + dev: true + peerDependencies: + react: '>=15' + resolution: + integrity: sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== + /react-router/4.3.1: + dependencies: + history: 4.7.2 + hoist-non-react-statics: 2.5.5 + invariant: 2.2.4 + loose-envify: 1.4.0 + path-to-regexp: 1.7.0 + prop-types: 15.6.2 + warning: 4.0.2 + dev: true + peerDependencies: + react: '>=15' + resolution: + integrity: sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== /request/2.88.0: dependencies: aws-sign2: 0.7.0 @@ -251,6 +334,10 @@ packages: node: '>= 4' resolution: integrity: sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + /resolve-pathname/2.2.0: + dev: true + resolution: + integrity: sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== /safe-buffer/5.1.2: dev: false resolution: @@ -306,6 +393,10 @@ packages: hasBin: true resolution: integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + /value-equal/0.4.0: + dev: true + resolution: + integrity: sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== /verror/1.10.0: dependencies: assert-plus: 1.0.0 @@ -316,8 +407,21 @@ packages: '0': node >=0.6.0 resolution: integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + /warning/3.0.0: + dependencies: + loose-envify: 1.4.0 + dev: true + resolution: + integrity: sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + /warning/4.0.2: + dependencies: + loose-envify: 1.4.0 + dev: true + resolution: + integrity: sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== registry: 'https://registry.npmjs.org/' shrinkwrapMinorVersion: 9 shrinkwrapVersion: 3 specifiers: + react-router-dom: ^4.3.1 request: ^2.88.0 diff --git a/views/pages/app.ejs b/views/pages/app.ejs new file mode 100644 index 0000000..11f0cbb --- /dev/null +++ b/views/pages/app.ejs @@ -0,0 +1 @@ +<%- partial('../../.tmp/public/index.html') %> diff --git a/views/pages/targets.ejs b/views/pages/targets.ejs deleted file mode 100644 index 9816e60..0000000 --- a/views/pages/targets.ejs +++ /dev/null @@ -1 +0,0 @@ -<%- partial('../../.tmp/public/targets.html') %> From 4d03946f521b74fd1907d825e989c8a8bdeafef4 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 16:22:17 -0500 Subject: [PATCH 06/16] update base url --- config/env/production.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/env/production.js b/config/env/production.js index 37892ef..a70e4aa 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -348,7 +348,7 @@ module.exports = { * * ***************************************************************************/ custom: { - baseURL: 'https://example.com', + baseURL: 'http://ec2-18-219-223-27.us-east-2.compute.amazonaws.com', internalEmailAddress: 'support@example.com' // mailgunDomain: 'mg.example.com', From d55bedd01db470a3bc30b3a2deebe2c8bc35c3a7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 16:32:38 -0500 Subject: [PATCH 07/16] add sails-postgresql to packages.json --- package.json | 3 +- shrinkwrap.yaml | 933 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 934 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5fff937..146e463 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "sails": "^1.0.2", "sails-hook-grunt": "^3.0.2", "sails-hook-orm": "^2.1.1", - "sails-hook-sockets": "^1.4.0" + "sails-hook-sockets": "^1.4.0", + "sails-postgresql": "^1.0.2" }, "devDependencies": { "@babel/core": "^7.1.2", diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index 282f6e6..2f50a21 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -1,8 +1,13 @@ dependencies: request: 2.88.0 + sails-postgresql: 1.0.2 devDependencies: react-router-dom: 4.3.1 packages: + /@sailshq/lodash/3.10.3: + dev: false + resolution: + integrity: sha512-XTF5BtsTSiSpTnfqrCGS5Q8FvSHWCywA0oRxFAZo8E1a8k1MMFUvk3VlRk3q/SusEYwy7gvVdyt9vvNlTa2VuA== /ajv/6.8.1: dependencies: fast-deep-equal: 2.0.1 @@ -12,6 +17,45 @@ packages: dev: false resolution: integrity: sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ== + /anchor/1.4.0: + dependencies: + '@sailshq/lodash': 3.10.3 + validator: 5.7.0 + dev: false + resolution: + integrity: sha512-xEu0UWxNa3p5v3MmXN9id5tsMSiniCyzWamf/R3KRkJieSRdXdAWu0Z+tXIpDZbbVLWZSMnD1VEguuYX2s9xag== + /ansi-regex/2.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + /ansi-styles/2.2.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + /arr-diff/2.0.0: + dependencies: + arr-flatten: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= + /arr-flatten/1.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + /array-unique/0.2.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= /asn1/0.2.4: dependencies: safer-buffer: 2.1.2 @@ -24,6 +68,12 @@ packages: node: '>=0.8' resolution: integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + /async/2.0.1: + dependencies: + lodash: 4.17.11 + dev: false + resolution: + integrity: sha1-twnMAoCpw28J9FNr6CPIOKkEniU= /asynckit/0.4.0: dev: false resolution: @@ -36,16 +86,68 @@ packages: dev: false resolution: integrity: sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + /babel-runtime/6.26.0: + dependencies: + core-js: 2.6.3 + regenerator-runtime: 0.11.1 + dev: false + resolution: + integrity: sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + /balanced-match/1.0.0: + dev: false + resolution: + integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c= /bcrypt-pbkdf/1.0.2: dependencies: tweetnacl: 0.14.5 dev: false resolution: integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + /bluebird/3.2.1: + dev: false + resolution: + integrity: sha1-POzzUEkEwwzj55wXCHfok6EZEP0= + /bluebird/3.5.3: + dev: false + resolution: + integrity: sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + /brace-expansion/1.1.11: + dependencies: + balanced-match: 1.0.0 + concat-map: 0.0.1 + dev: false + resolution: + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + /braces/1.8.5: + dependencies: + expand-range: 1.8.2 + preserve: 0.2.0 + repeat-element: 1.1.3 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= + /buffer-writer/1.0.1: + dev: false + resolution: + integrity: sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg= /caseless/0.12.0: dev: false resolution: integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + /chalk/1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= /combined-stream/1.0.7: dependencies: delayed-stream: 1.0.0 @@ -54,6 +156,18 @@ packages: node: '>= 0.8' resolution: integrity: sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + /commander/2.19.0: + dev: false + resolution: + integrity: sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + /concat-map/0.0.1: + dev: false + resolution: + integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + /core-js/2.6.3: + dev: false + resolution: + integrity: sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ== /core-util-is/1.0.2: dev: false resolution: @@ -66,12 +180,32 @@ packages: node: '>=0.10' resolution: integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + /debug/2.2.0: + dependencies: + ms: 0.7.1 + dev: false + resolution: + integrity: sha1-+HBX6ZWxofauaklgZkE3vFbwOdo= + /debug/2.6.9: + dependencies: + ms: 2.0.0 + dev: false + resolution: + integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== /delayed-stream/1.0.0: dev: false engines: node: '>=0.4.0' resolution: integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + /detect-file/0.1.0: + dependencies: + fs-exists-sync: 0.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-STXe39lIhkjgBrASlWbpOGcR6mM= /ecc-jsbn/0.1.2: dependencies: jsbn: 0.1.1 @@ -79,10 +213,48 @@ packages: dev: false resolution: integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + /escape-string-regexp/1.0.5: + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + /expand-brackets/0.1.5: + dependencies: + is-posix-bracket: 0.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= + /expand-range/1.8.2: + dependencies: + fill-range: 2.2.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= + /expand-tilde/1.2.2: + dependencies: + os-homedir: 1.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= /extend/3.0.2: dev: false resolution: integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + /extglob/0.3.2: + dependencies: + is-extglob: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= /extsprintf/1.3.0: dev: false engines: @@ -103,6 +275,61 @@ packages: dev: false resolution: integrity: sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + /filename-regex/2.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= + /fill-range/2.2.4: + dependencies: + is-number: 2.1.0 + isobject: 2.1.0 + randomatic: 3.1.1 + repeat-element: 1.1.3 + repeat-string: 1.6.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== + /findup-sync/0.4.3: + dependencies: + detect-file: 0.1.0 + is-glob: 2.0.1 + micromatch: 2.3.11 + resolve-dir: 0.1.1 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI= + /flagged-respawn/0.3.2: + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU= + /flaverr/1.9.2: + dependencies: + '@sailshq/lodash': 3.10.3 + dev: false + resolution: + integrity: sha512-14CoGOGUhFkhzDCgGdpFFJE9PrdMPhGmhuS39WxxgTpTKVzfWW3DAVfolUiHwYpaROz7UFrJuaSJtsxhem+i9g== + /for-in/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + /for-own/0.1.5: + dependencies: + for-in: 1.0.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= /forever-agent/0.6.1: dev: false resolution: @@ -117,12 +344,94 @@ packages: node: '>= 0.12' resolution: integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + /fs-exists-sync/0.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + /fs-extra/0.30.0: + dependencies: + graceful-fs: 4.1.15 + jsonfile: 2.4.0 + klaw: 1.3.1 + path-is-absolute: 1.0.1 + rimraf: 2.6.3 + dev: false + resolution: + integrity: sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= + /fs.realpath/1.0.0: + dev: false + resolution: + integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + /generic-pool/2.4.3: + dev: false + engines: + node: '>= 0.2.0' + resolution: + integrity: sha1-eAw29p360FpaBF3Te+etyhGk9v8= + /generic-pool/2.5.4: + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-OMYYhRPhQDCUjsblz2VSPZd5KZs= /getpass/0.1.7: dependencies: assert-plus: 1.0.0 dev: false resolution: integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + /glob-base/0.3.0: + dependencies: + glob-parent: 2.0.0 + is-glob: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= + /glob-parent/2.0.0: + dependencies: + is-glob: 2.0.1 + dev: false + resolution: + integrity: sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= + /glob/7.1.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.3 + minimatch: 3.0.4 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: false + resolution: + integrity: sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + /global-modules/0.2.3: + dependencies: + global-prefix: 0.1.5 + is-windows: 0.2.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= + /global-prefix/0.1.5: + dependencies: + homedir-polyfill: 1.0.1 + ini: 1.3.5 + is-windows: 0.2.0 + which: 1.3.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= + /graceful-fs/4.1.15: + dev: false + resolution: + integrity: sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== /har-schema/2.0.0: dev: false engines: @@ -138,6 +447,14 @@ packages: node: '>=6' resolution: integrity: sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + /has-ansi/2.0.0: + dependencies: + ansi-regex: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= /history/4.7.2: dependencies: invariant: 2.2.4 @@ -152,6 +469,14 @@ packages: dev: true resolution: integrity: sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + /homedir-polyfill/1.0.1: + dependencies: + parse-passwd: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-TCu8inWJmP7r9e1oWA921GdotLw= /http-signature/1.2.0: dependencies: assert-plus: 1.0.0 @@ -163,24 +488,134 @@ packages: npm: '>=1.3.7' resolution: integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + /inflight/1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: false + resolution: + integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + /inherits/2.0.3: + dev: false + resolution: + integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + /ini/1.3.5: + dev: false + resolution: + integrity: sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + /interpret/0.6.6: + dev: false + resolution: + integrity: sha1-/s16GOfOXKar+5U+H4YhOknxYls= /invariant/2.2.4: dependencies: loose-envify: 1.4.0 dev: true resolution: integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + /is-buffer/1.1.6: + dev: false + resolution: + integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + /is-dotfile/1.0.3: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= + /is-equal-shallow/0.1.3: + dependencies: + is-primitive: 2.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= + /is-extendable/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + /is-extglob/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= + /is-glob/2.0.1: + dependencies: + is-extglob: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= + /is-number/2.1.0: + dependencies: + kind-of: 3.2.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= + /is-number/4.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + /is-posix-bracket/0.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= + /is-primitive/2.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-IHurkWOEmcB7Kt8kCkGochADRXU= /is-typedarray/1.0.0: dev: false resolution: integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + /is-windows/0.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-3hqm1j6indJIc3tp8f+LgALSEIw= /isarray/0.0.1: - dev: true resolution: integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + /isarray/1.0.0: + dev: false + resolution: + integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + /isexe/2.0.0: + dev: false + resolution: + integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + /isobject/2.1.0: + dependencies: + isarray: 1.0.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= /isstream/0.1.2: dev: false resolution: integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + /js-string-escape/1.0.1: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= /js-tokens/4.0.0: dev: true resolution: @@ -201,6 +636,12 @@ packages: dev: false resolution: integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + /jsonfile/2.4.0: + dev: false + optionalDependencies: + graceful-fs: 4.1.15 + resolution: + integrity: sha1-NzaitCi4e72gzIO1P6PWM6NcKug= /jsprim/1.4.1: dependencies: assert-plus: 1.0.0 @@ -212,6 +653,66 @@ packages: '0': node >=0.6.0 resolution: integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + /kind-of/3.2.2: + dependencies: + is-buffer: 1.1.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + /kind-of/6.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + /klaw/1.3.1: + dev: false + optionalDependencies: + graceful-fs: 4.1.15 + resolution: + integrity: sha1-QIhDO0azsbolnXh4XY6W9zugJDk= + /knex/0.12.7: + dependencies: + babel-runtime: 6.26.0 + bluebird: 3.5.3 + chalk: 1.1.3 + commander: 2.19.0 + debug: 2.6.9 + generic-pool: 2.5.4 + inherits: 2.0.3 + interpret: 0.6.6 + liftoff: 2.2.5 + lodash: 4.17.11 + minimist: 1.1.3 + mkdirp: 0.5.1 + pg-connection-string: 0.1.3 + readable-stream: 1.1.14 + safe-buffer: 5.1.2 + tildify: 1.0.0 + uuid: 3.3.2 + v8flags: 2.1.1 + dev: false + hasBin: true + resolution: + integrity: sha1-V5P1efB6KTi/mHRpPXS7pVxbqhw= + /liftoff/2.2.5: + dependencies: + extend: 3.0.2 + findup-sync: 0.4.3 + flagged-respawn: 0.3.2 + rechoir: 0.6.2 + resolve: 1.10.0 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-mYwods/0hLED5EI7k9NW2kRzTJE= + /lodash/4.17.11: + dev: false + resolution: + integrity: sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== /loose-envify/1.4.0: dependencies: js-tokens: 4.0.0 @@ -219,6 +720,52 @@ packages: hasBin: true resolution: integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + /machine/15.2.2: + dependencies: + '@sailshq/lodash': 3.10.3 + anchor: 1.4.0 + flaverr: 1.9.2 + parley: 3.8.0 + rttc: 10.0.0-4 + dev: false + engines: + node: '>= 4.0.0' + resolution: + integrity: sha512-gXA/U4bjMyQd2QPw8i+AxzXEDkQBImQVE2P7mmTmXPcfszT+NJc5Me0I1Tn6Fj8zsO5EsmsFxD8Xdia751ik/w== + /machinepack-postgresql/1.0.2: + dependencies: + '@sailshq/lodash': 3.10.3 + debug: 2.2.0 + machine: 15.2.2 + pg: 6.1.6 + waterline-sql-builder: 1.0.0 + dev: false + resolution: + integrity: sha512-BaIos/SHoIGk/+ho0TVh2gwEJK948tXClwaSL+06iAcIR7T2OTFMYgAaNlZG/4YtIuMyGoEdWv/tLgHag0W6rQ== + /math-random/1.0.4: + dev: false + resolution: + integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== + /micromatch/2.3.11: + dependencies: + arr-diff: 2.0.0 + array-unique: 0.2.1 + braces: 1.8.5 + expand-brackets: 0.1.5 + extglob: 0.3.2 + filename-regex: 2.0.1 + is-extglob: 1.0.0 + is-glob: 2.0.1 + kind-of: 3.2.2 + normalize-path: 2.1.1 + object.omit: 2.0.1 + parse-glob: 3.0.4 + regex-cache: 0.4.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= /mime-db/1.37.0: dev: false engines: @@ -233,16 +780,119 @@ packages: node: '>= 0.6' resolution: integrity: sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + /minimatch/3.0.4: + dependencies: + brace-expansion: 1.1.11 + dev: false + resolution: + integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + /minimist/0.0.8: + dev: false + resolution: + integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + /minimist/1.1.3: + dev: false + resolution: + integrity: sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= + /mkdirp/0.5.1: + dependencies: + minimist: 0.0.8 + dev: false + hasBin: true + resolution: + integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + /ms/0.7.1: + dev: false + resolution: + integrity: sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg= + /ms/2.0.0: + dev: false + resolution: + integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + /normalize-path/2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= /oauth-sign/0.9.0: dev: false resolution: integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + /object-assign/4.1.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ejs9DpgGPUP0wD8uiubNUahog6A= /object-assign/4.1.1: dev: true engines: node: '>=0.10.0' resolution: integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + /object.omit/2.0.1: + dependencies: + for-own: 0.1.5 + is-extendable: 0.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= + /once/1.4.0: + dependencies: + wrappy: 1.0.2 + dev: false + resolution: + integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + /os-homedir/1.0.2: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + /packet-reader/0.2.0: + dev: false + resolution: + integrity: sha1-gZ300BC4LV6lZx+KGjrPA5vNdwA= + /parley/3.8.0: + dependencies: + '@sailshq/lodash': 3.10.3 + bluebird: 3.2.1 + flaverr: 1.9.2 + dev: false + resolution: + integrity: sha512-WhNhNMPoxTydFg7U/MCAraE4JJLVSYeCJ3Wg5xHb1Z3EKC5N+SXldq6NYAtrgBSLpo4jHKQI2SWOhWEHJ82CGw== + /parse-glob/3.0.4: + dependencies: + glob-base: 0.3.0 + is-dotfile: 1.0.3 + is-extglob: 1.0.0 + is-glob: 2.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ssN2z7EfNVE7rdFz7wu246OIORw= + /parse-passwd/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + /path-is-absolute/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + /path-parse/1.0.6: + dev: false + resolution: + integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== /path-to-regexp/1.7.0: dependencies: isarray: 0.0.1 @@ -253,6 +903,86 @@ packages: dev: false resolution: integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + /pg-connection-string/0.1.3: + dev: false + resolution: + integrity: sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= + /pg-int8/1.0.1: + dev: false + engines: + node: '>=4.0.0' + resolution: + integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + /pg-pool/1.8.0: + dependencies: + generic-pool: 2.4.3 + object-assign: 4.1.0 + dev: false + resolution: + integrity: sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc= + /pg-types/1.13.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 1.0.3 + postgres-bytea: 1.0.0 + postgres-date: 1.0.3 + postgres-interval: 1.1.2 + dev: false + resolution: + integrity: sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ== + /pg/6.1.6: + dependencies: + buffer-writer: 1.0.1 + js-string-escape: 1.0.1 + packet-reader: 0.2.0 + pg-connection-string: 0.1.3 + pg-pool: 1.8.0 + pg-types: 1.13.0 + pgpass: 1.0.2 + semver: 4.3.2 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-3SJ1glDVrW7vWiv4jZbydtHpWw0= + /pgpass/1.0.2: + dependencies: + split: 1.0.1 + dev: false + resolution: + integrity: sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= + /postgres-array/1.0.3: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ== + /postgres-bytea/1.0.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + /postgres-date/1.0.3: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g= + /postgres-interval/1.1.2: + dependencies: + xtend: 4.0.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ== + /preserve/0.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= /prop-types/15.6.2: dependencies: loose-envify: 1.4.0 @@ -274,12 +1004,28 @@ packages: node: '>=6' resolution: integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + /qs/6.4.0: + dev: false + engines: + node: '>=0.6' + resolution: + integrity: sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= /qs/6.5.2: dev: false engines: node: '>=0.6' resolution: integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + /randomatic/3.1.1: + dependencies: + is-number: 4.0.0 + kind-of: 6.0.2 + math-random: 1.0.4 + dev: false + engines: + node: '>= 0.10.0' + resolution: + integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== /react-router-dom/4.3.1: dependencies: history: 4.7.2 @@ -307,6 +1053,51 @@ packages: react: '>=15' resolution: integrity: sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== + /readable-stream/1.1.14: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.3 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: false + resolution: + integrity: sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + /rechoir/0.6.2: + dependencies: + resolve: 1.10.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + /regenerator-runtime/0.11.1: + dev: false + resolution: + integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + /regex-cache/0.4.4: + dependencies: + is-equal-shallow: 0.1.3 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== + /remove-trailing-separator/1.1.0: + dev: false + resolution: + integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + /repeat-element/1.1.3: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + /repeat-string/1.6.1: + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-jcrkcOHIirwtYA//Sndihtp15jc= /request/2.88.0: dependencies: aws-sign2: 0.7.0 @@ -334,10 +1125,41 @@ packages: node: '>= 4' resolution: integrity: sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + /resolve-dir/0.1.1: + dependencies: + expand-tilde: 1.2.2 + global-modules: 0.2.3 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-shklmlYC+sXFxJatiUpujMQwJh4= /resolve-pathname/2.2.0: dev: true resolution: integrity: sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== + /resolve/1.10.0: + dependencies: + path-parse: 1.0.6 + dev: false + resolution: + integrity: sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + /rimraf/2.6.3: + dependencies: + glob: 7.1.3 + dev: false + hasBin: true + resolution: + integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + /rttc/10.0.0-4: + dependencies: + '@sailshq/lodash': 3.10.3 + dev: false + engines: + node: '>= 0.10.0' + npm: '>= 1.4.0' + resolution: + integrity: sha512-HroJ9z+RVipbPCeFdglopiVM18w9BM5PFqXivM6ZceNQEphjpDGZ154srk1JhviNacrmFqhdzDbGQZsB13g6JA== /safe-buffer/5.1.2: dev: false resolution: @@ -346,6 +1168,28 @@ packages: dev: false resolution: integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + /sails-postgresql/1.0.2: + dependencies: + '@sailshq/lodash': 3.10.3 + async: 2.0.1 + flaverr: 1.9.2 + machine: 15.2.2 + machinepack-postgresql: 1.0.2 + waterline-utils: 1.4.2 + dev: false + resolution: + integrity: sha512-q+g4gIPzavBSY2lVWEfAUmOLxDYqdJ1SOf0/sZldYfSPsAt+Rh01cBGZ4sQ/GuooKoIRFSwMllOL9yz9pWtsPg== + /semver/4.3.2: + dev: false + hasBin: true + resolution: + integrity: sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= + /split/1.0.1: + dependencies: + through: 2.3.8 + dev: false + resolution: + integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== /sshpk/1.16.1: dependencies: asn1: 0.2.4 @@ -363,6 +1207,36 @@ packages: hasBin: true resolution: integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + /string_decoder/0.10.31: + dev: false + resolution: + integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + /strip-ansi/3.0.1: + dependencies: + ansi-regex: 2.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + /supports-color/2.0.0: + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + /through/2.3.8: + dev: false + resolution: + integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + /tildify/1.0.0: + dependencies: + user-home: 1.1.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-KgIdtej73gqPi03zetqo+x05190= /tough-cookie/2.4.3: dependencies: psl: 1.1.31 @@ -388,11 +1262,32 @@ packages: dev: false resolution: integrity: sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + /user-home/1.1.1: + dev: false + engines: + node: '>=0.10.0' + hasBin: true + resolution: + integrity: sha1-K1viOjK2Onyd640PKNSFcko98ZA= /uuid/3.3.2: dev: false hasBin: true resolution: integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + /v8flags/2.1.1: + dependencies: + user-home: 1.1.1 + dev: false + engines: + node: '>= 0.10.0' + resolution: + integrity: sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ= + /validator/5.7.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw= /value-equal/0.4.0: dev: true resolution: @@ -419,9 +1314,45 @@ packages: dev: true resolution: integrity: sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== + /waterline-sql-builder/1.0.0: + dependencies: + '@sailshq/lodash': 3.10.3 + knex: 0.12.7 + waterline-utils: 1.4.2 + dev: false + resolution: + integrity: sha512-2oWLfAV5dpJHcUC3+wHoE4wjilks4kp6h0AtlgEvn3Wl8pac1MvPJmZqwRyJH2prakGfhxE+E5yBXaMQfOnUrQ== + /waterline-utils/1.4.2: + dependencies: + '@sailshq/lodash': 3.10.3 + async: 2.0.1 + flaverr: 1.9.2 + fs-extra: 0.30.0 + qs: 6.4.0 + dev: false + resolution: + integrity: sha512-WsS1yRw8xT6O7iIK8oAcYr3/vhMYiTHdA/vQnU/JsGbcad2Xi4p8bie8/F+BoEyN7SEpBZ8nFT9OdszDAt7Ugg== + /which/1.3.1: + dependencies: + isexe: 2.0.0 + dev: false + hasBin: true + resolution: + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + /wrappy/1.0.2: + dev: false + resolution: + integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + /xtend/4.0.1: + dev: false + engines: + node: '>=0.4' + resolution: + integrity: sha1-pcbVMr5lbiPbgg77lDofBJmNY68= registry: 'https://registry.npmjs.org/' shrinkwrapMinorVersion: 9 shrinkwrapVersion: 3 specifiers: react-router-dom: ^4.3.1 request: ^2.88.0 + sails-postgresql: ^1.0.2 From b01c46dbc7eb8776395a7dc929cc6fc0c34bf623 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 16:35:12 -0500 Subject: [PATCH 08/16] fix wrong column names --- api/models/Book.js | 3 +-- api/models/Passport.js | 3 +-- api/models/User.js | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/models/Book.js b/api/models/Book.js index b0ba98e..11962de 100644 --- a/api/models/Book.js +++ b/api/models/Book.js @@ -15,8 +15,7 @@ module.exports = { id: { type: 'number', unique: true, - autoIncrement: true, - columnName: '_id' + autoIncrement: true }, title: { type: 'string', required: true }, author: { type: 'string' }, diff --git a/api/models/Passport.js b/api/models/Passport.js index c3922a6..16519a7 100644 --- a/api/models/Passport.js +++ b/api/models/Passport.js @@ -28,8 +28,7 @@ module.exports = { id: { type: 'number', unique: true, - autoIncrement: true, - columnName: '_id' + autoIncrement: true }, // local, oauth2, etc protocol: { diff --git a/api/models/User.js b/api/models/User.js index 08bd61e..480dfe2 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -14,8 +14,7 @@ module.exports = { id: { type: 'number', unique: true, - autoIncrement: true, - columnName: '_id' + autoIncrement: true }, email: { type: 'string', From 40d8200eac74bd2cdd9080529d782ef581552384 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Feb 2019 16:59:13 -0500 Subject: [PATCH 09/16] allow github accounts with no email visible to sign up --- api/helpers/passport.js | 6 ++++-- api/models/User.js | 4 +--- assets/js/actions/index.js | 2 +- config/protocols.js | 8 ++++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/helpers/passport.js b/api/helpers/passport.js index 0879682..7b5381f 100644 --- a/api/helpers/passport.js +++ b/api/helpers/passport.js @@ -117,7 +117,7 @@ function PassportHelper () { // if the profile object from passport has an email, use it if (profile.emails && profile.emails[0]) userAttrs.email = profile.emails[0].value - if (!userAttrs.email) return next(new Error('No email available')) + // if (!userAttrs.email) return next(new Error('No email available')) const pass = await Passport.findOne({ provider, @@ -128,7 +128,9 @@ function PassportHelper () { if (!req.user) { if (!pass) { // new user signing up, create a new user and/or passport - user = await User.findOne({ email: userAttrs.email }) + if (userAttrs.email) { + user = await User.findOne({ email: userAttrs.email }) + } if (!user) { user = await User.create(userAttrs).fetch() } diff --git a/api/models/User.js b/api/models/User.js index 480dfe2..c8e0292 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -17,9 +17,7 @@ module.exports = { autoIncrement: true }, email: { - type: 'string', - unique: true, - required: true + type: 'string' } // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ diff --git a/assets/js/actions/index.js b/assets/js/actions/index.js index 8f1f566..fa57a66 100644 --- a/assets/js/actions/index.js +++ b/assets/js/actions/index.js @@ -131,7 +131,7 @@ export const editUser = (user) => async (dispatch, getState) => { dispatch(setWorking(true)) try { - if (!user.currentPassword) throw new Error('Please enter your current password.') + // if (!user.currentPassword) throw new Error('Please enter your current password.') await Ajax.patch({ url: '/api/me', data: { diff --git a/config/protocols.js b/config/protocols.js index aed2dbe..a4ae57c 100644 --- a/config/protocols.js +++ b/config/protocols.js @@ -70,17 +70,19 @@ module.exports.protocols = { const dbUser = await User.findOne({ id: user.id }) - if (!dbUser) throw new Error('an account with that id was not found') + if (!dbUser) throw new Error('An account with that id was not found.') const passport = await Passport.findOne({ protocol: 'local', user: user.id }) - if (!user.currentPassword && passport) throw new Error('Missing current password') + if (!user.currentPassword && passport) throw new Error('Please enter your current password.') if (passport) { const res = await Passport.validatePassword(user.currentPassword, passport) if (!res) throw new Error('incorrect password') + const otherUser = await User.findOne({ email: user.email }) + if (otherUser && otherUser.id !== dbUser.id) throw new Error('There is already an account with that email.') await User.update({ id: user.id }, { email: user.email }) @@ -90,6 +92,8 @@ module.exports.protocols = { }) } } else { // no password yet, add one + const otherUser = await User.findOne({ email: user.email }) + if (otherUser && otherUser.id !== dbUser.id) throw new Error('There is already an account with that email.') await User.update({ id: user.id }, { email: user.email }) From 4e0adc80c0571cd28af7f3766d25035601833f80 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 13 Feb 2019 19:38:30 -0500 Subject: [PATCH 10/16] add page to books api and add usage docs --- api/controllers/BooksController.js | 13 ++-- docs/api.md | 112 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 docs/api.md diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index da3ec6e..51779ad 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -31,7 +31,8 @@ module.exports = { await Book.destroy({ id: result.id }) throw new HttpError(500, err.message) } - await Book.update({ id: result.id }, { storage: uploaded[0].fd }) + const fd = (uploaded[0] || {}).fd + await Book.update({ id: result.id }, { storage: fd }) sendUpdatesAsync(result.id) return res.json({ ...result @@ -48,9 +49,13 @@ module.exports = { list: async function (req, res) { try { const body = req.allParams() - if (!body) throw new HttpError(400, 'Missing parameters') - - const books = await Book.find(body) + let page = 1 + const perPage = 200 + if (body.page) { + page = Math.abs(+body.page) || 1 + delete body.page + } + const books = await Book.find(body || {}).skip((page * perPage) - perPage).limit(perPage) if (!books.length) { throw new HttpError(404, 'No books matching those parameters were found.') diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..9a994e8 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,112 @@ +# River of Ebooks REST API +## Information on how to use the api endpoints to publish and view ebook metadata + +### Publishing a book + +``` +POST to /api/publish containing the body: + +{ + title: The book's title, + author: The author (optional), + version: A version number (optional), + isbn: The ISBN (optional), + opds: file +} +``` + +Each tuple of `(title, author, version, isbn)` must be unique. + +The `opds` parameter is an opds file sent along with the post body. + +The server will respond with either: + +``` +200 OK +{ + "created_at": 1550102480021, + "updated_at": 1550102480021, + "id": number, + "title": string, + "author": string, + "isbn": string, + "version": string +} +``` + +or + +``` +400 BAD REQUEST +{ + "error": string, + "hint": string +} +``` + +### Fetching published books + +GET from /api/books with the query string parameters: + +``` +title: The book's title (optional) +author: The author (optional) +version: A version number (optional) +isbn: The ISBN (optional) + +page: The page of results to view (200 results per page) +``` + +For example: `GET /api/books?title=foo&page=3` + +The server will respond with either: + +``` +200 OK +[ + { + "storage": "path/to/opds/storage/location", + "created_at": timestamp, + "updated_at": timestamp, + "id": number, + "title": string, + "author": string, + "isbn": string, + "version": string + } +] +``` + +or + +``` +404 NOT FOUND +{ + "error": string, + "hint": string +} +``` + +### Receiving push notifications to your webhooks: + +- Log in to the River of Ebooks website +- Add your webhook URL and desired filters + +The server will send a POST request to the provided URL whenever a new ebook is published through the pipeline with the following data: + +``` +HTTP Headers: + User-Agent RoE-aggregator + +HTTP Body: +{ + "storage": "path/to/opds/storage/location", + "created_at": timestamp, + "updated_at": timestamp, + "id": number, + "title": string, + "author": string, + "isbn": string, + "version": string +} +``` From 460cf44c4c2bbc86f309ed9d1c9dc96f2ca1a507 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 13 Feb 2019 19:41:42 -0500 Subject: [PATCH 11/16] formatting --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 9a994e8..0820c60 100644 --- a/docs/api.md +++ b/docs/api.md @@ -96,7 +96,7 @@ The server will send a POST request to the provided URL whenever a new ebook is ``` HTTP Headers: - User-Agent RoE-aggregator + User-Agent: RoE-aggregator HTTP Body: { From a734069b74b76f6be2102a2003abe10fbdf0018c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Feb 2019 13:04:24 -0500 Subject: [PATCH 12/16] actually use push target filters when new books are published --- api/controllers/BooksController.js | 32 +++++++++++++------ api/models/Book.js | 1 + .../20190220123908_AddPublisherToBooks.js | 15 +++++++++ package.json | 3 +- 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 migrations/20190220123908_AddPublisherToBooks.js diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index 51779ad..1b2a456 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -7,6 +7,7 @@ const HttpError = require('../errors/HttpError') const request = require('request') +const uriRegex = /^(.+:\/\/)?(.+\.)*(.+\.).{1,}(:\d+)?(.+)?/i module.exports = { publish: async function (req, res) { @@ -73,16 +74,27 @@ module.exports = { async function sendUpdatesAsync (id) { const book = await Book.findOne({ id }) const targets = await TargetUrl.find() + if (!book) return for (const i in targets) { - sails.log('sending ' + book.id + ' info to ' + targets[i].url) - request.post({ - url: targets[i].url, - headers: { 'User-Agent': 'RoE-aggregator' }, - form: book - }, function (err, httpResp, body) { - if (err) { - sails.log(`error: failed to send book ${id} to ${targets[i].url}`) - } - }) + const item = targets[i] + const { author: fAuthor, publisher: fPublisher, title: fTitle, isbn: fIsbn, url } = item + const { author: bAuthor, publisher: bPublisher, title: bTitle, isbn: bIsbn } = book + sails.log('sending ' + book.id + ' info to ' + url) + + if (uriRegex.test(url)) { + if (fAuthor && !((bAuthor || '').includes(fAuthor))) continue + if (fPublisher && !((bPublisher || '').includes(fPublisher))) continue + if (fTitle && !((bTitle || '').includes(fTitle))) continue + if (fIsbn && !((bIsbn || '').includes(fIsbn))) continue + request.post({ + url: url, + headers: { 'User-Agent': 'RoE-aggregator' }, + form: book + }, function (err, httpResp, body) { + if (err) { + sails.log(`error: failed to send book ${id} to ${url}`) + } + }) + } } } diff --git a/api/models/Book.js b/api/models/Book.js index 11962de..2c62d4e 100644 --- a/api/models/Book.js +++ b/api/models/Book.js @@ -19,6 +19,7 @@ module.exports = { }, title: { type: 'string', required: true }, author: { type: 'string' }, + publisher: { type: 'string' }, isbn: { type: 'string' }, version: { type: 'string' } diff --git a/migrations/20190220123908_AddPublisherToBooks.js b/migrations/20190220123908_AddPublisherToBooks.js new file mode 100644 index 0000000..108aeaa --- /dev/null +++ b/migrations/20190220123908_AddPublisherToBooks.js @@ -0,0 +1,15 @@ +exports.up = function (knex, Promise) { + return Promise.all([ + knex.schema.table('book', t => { + t.string('publisher') + }) + ]) +} + +exports.down = function (knex, Promise) { + return Promise.all([ + knex.schema.table('book', t => { + t.dropColumns('publisher') + }) + ]) +} diff --git a/package.json b/package.json index 146e463..37c182a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "debug": "node --inspect app.js", "custom-tests": "echo 'Nothing yet'", "db:migrate": "knex migrate:latest", - "db:rollback": "knex migrate:rollback" + "db:rollback": "knex migrate:rollback", + "db:gmig": "knex migrate:make" }, "dependencies": { "@sailshq/connect-redis": "^3.2.1", From 9796a10409742e50d7d05eb19eeadb5dec9638a8 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Feb 2019 13:13:08 -0500 Subject: [PATCH 13/16] ratelimit the publish endpoint specifically: --- config/http.js | 12 +++++++++++- package.json | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/config/http.js b/config/http.js index 88d7de2..b955ef3 100644 --- a/config/http.js +++ b/config/http.js @@ -14,7 +14,15 @@ const rateLimiter = rateLimit({ windowMs: 10 * 60 * 1000, // 10 minutes max: 100, // limit each IP to 100 requests per windowMs skip (req, res) { - return !req.path.startsWith('/api') + return !req.path.startsWith('/api') || req.path.startsWith('/api/publish') + } +}) + +const publishLimiter = rateLimit({ + windowMs: 1000 * 60 * 60 * 24, // 24 hours + max: 1000, // 1000 publish requests per day + skip (req, res) { + return !req.path.startsWith('/api/publish') } }) @@ -40,6 +48,7 @@ module.exports.http = { order: [ 'rateLimit', + 'publishLimit', 'cookieParser', 'session', 'passportInit', @@ -52,6 +61,7 @@ module.exports.http = { 'favicon' ], rateLimit: rateLimiter, + publishLimit: publishLimiter, passportInit: require('passport').initialize(), passportSession: require('passport').session() diff --git a/package.json b/package.json index 37c182a..8f79764 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "custom-tests": "echo 'Nothing yet'", "db:migrate": "knex migrate:latest", "db:rollback": "knex migrate:rollback", - "db:gmig": "knex migrate:make" + "g:migration": "knex migrate:make" }, "dependencies": { "@sailshq/connect-redis": "^3.2.1", From 810d7b8e5c068c11a62863b0b0a6ddb58e600a13 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Feb 2019 13:24:13 -0500 Subject: [PATCH 14/16] require at least 2 filled out fields to publish --- api/controllers/BooksController.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index 1b2a456..0fa48d7 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -24,7 +24,13 @@ module.exports = { if (bookExists) { throw new HttpError(400, 'Version already exists') } else { - result = await Book.create(body).fetch() + const { title, isbn, author, publisher } = body + // require at least 2 fields to be filled out + if ([title, isbn, author, publisher].reduce((a, x) => a + (x ? 1 : 0), 0) >= 2) { + result = await Book.create(body).fetch() + } else { + throw new HttpError(400, 'Please fill out at least 2 fields (title, author, publisher, isbn)') + } } req.file('opds').upload(sails.config.skipperConfig, async function (err, uploaded) { From 714418c1b220567814636e319b137004d7d154fc Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Feb 2019 13:29:36 -0500 Subject: [PATCH 15/16] require opds file upload --- api/controllers/BooksController.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/api/controllers/BooksController.js b/api/controllers/BooksController.js index 0fa48d7..ca5a829 100644 --- a/api/controllers/BooksController.js +++ b/api/controllers/BooksController.js @@ -33,18 +33,22 @@ module.exports = { } } - req.file('opds').upload(sails.config.skipperConfig, async function (err, uploaded) { - if (err) { - await Book.destroy({ id: result.id }) - throw new HttpError(500, err.message) - } - const fd = (uploaded[0] || {}).fd - await Book.update({ id: result.id }, { storage: fd }) - sendUpdatesAsync(result.id) - return res.json({ - ...result + if (req.file('opds')) { + req.file('opds').upload(sails.config.skipperConfig, async function (err, uploaded) { + if (err) { + await Book.destroy({ id: result.id }) + throw new HttpError(500, err.message) + } + const fd = (uploaded[0] || {}).fd + await Book.update({ id: result.id }, { storage: fd }) + sendUpdatesAsync(result.id) + return res.json({ + ...result + }) }) - }) + } else { + throw new HttpError(400, 'Missing OPDS file upload') + } } catch (e) { if (e instanceof HttpError) return e.send(res) return res.status(500).json({ From 3d15b0672e15c1680e59c85464fdd68580463324 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Feb 2019 13:38:25 -0500 Subject: [PATCH 16/16] add missing columns --- api/models/Book.js | 3 ++- migrations/20190220133443_add_srcHost_to_book.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 migrations/20190220133443_add_srcHost_to_book.js diff --git a/api/models/Book.js b/api/models/Book.js index 2c62d4e..5a7fd92 100644 --- a/api/models/Book.js +++ b/api/models/Book.js @@ -21,7 +21,8 @@ module.exports = { author: { type: 'string' }, publisher: { type: 'string' }, isbn: { type: 'string' }, - version: { type: 'string' } + version: { type: 'string' }, + hostname: { type: 'string' } // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ // ║╣ ║║║╠╩╗║╣ ║║╚═╗ diff --git a/migrations/20190220133443_add_srcHost_to_book.js b/migrations/20190220133443_add_srcHost_to_book.js new file mode 100644 index 0000000..77d47f6 --- /dev/null +++ b/migrations/20190220133443_add_srcHost_to_book.js @@ -0,0 +1,16 @@ + +exports.up = function (knex, Promise) { + return Promise.all([ + knex.schema.table('book', t => { + t.string('hostname') + }) + ]) +} + +exports.down = function (knex, Promise) { + return Promise.all([ + knex.schema.table('book', t => { + t.dropColumns('hostname') + }) + ]) +}