diff --git a/config.sample.js b/config.sample.js index b32441f..8988aee 100644 --- a/config.sample.js +++ b/config.sample.js @@ -1,84 +1,84 @@ module.exports = { - /* - If set to true the user will need to specify the auto-generated token - on each API call, meaning random strangers wont be able to use the service - unless they have the token loli-safe provides you with. - If it's set to false, then upload will be public for anyone to use. - */ - private: true, + /* + If set to true the user will need to specify the auto-generated token + on each API call, meaning random strangers wont be able to use the service + unless they have the token loli-safe provides you with. + If it's set to false, then upload will be public for anyone to use. + */ + private: true, - // If true, users will be able to create accounts and access their uploaded files - enableUserAccounts: true, + // If true, users will be able to create accounts and access their uploaded files + enableUserAccounts: true, - /* - Here you can decide if you want lolisafe to serve the files or if you prefer doing so via nginx. - The main difference between the two is the ease of use and the chance of analytics in the future. - If you set it to `true`, the uploaded files will be located after the host like: - https://lolisafe.moe/yourFile.jpg + /* + Here you can decide if you want lolisafe to serve the files or if you prefer doing so via nginx. + The main difference between the two is the ease of use and the chance of analytics in the future. + If you set it to `true`, the uploaded files will be located after the host like: + https://lolisafe.moe/yourFile.jpg - If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your - downloads in. This also gives you the ability to serve them, for example, like this: - https://files.lolisafe.moe/yourFile.jpg + If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your + downloads in. This also gives you the ability to serve them, for example, like this: + https://files.lolisafe.moe/yourFile.jpg - Both cases require you to type the domain where the files will be served on the `domain` key below. - Which one you use is ultimately up to you. - */ - serveFilesWithNode: false, - domain: 'https://lolisafe.moe', + Both cases require you to type the domain where the files will be served on the `domain` key below. + Which one you use is ultimately up to you. + */ + serveFilesWithNode: false, + domain: 'https://lolisafe.moe', - // Port on which to run the server - port: 9999, + // Port on which to run the server + port: 9999, - // Pages to process for the frontend - pages: ['home', 'auth', 'dashboard', 'faq'], + // Pages to process for the frontend + pages: ['home', 'auth', 'dashboard', 'faq'], - // Add file extensions here which should be blocked - blockedExtensions: [ - '.exe', - '.bat', - '.cmd', - '.msi', - '.sh' - ], + // Add file extensions here which should be blocked + blockedExtensions: [ + '.exe', + '.bat', + '.cmd', + '.msi', + '.sh' + ], - // Uploads config - uploads: { + // Uploads config + uploads: { - // Folder where images should be stored - folder: 'uploads', + // Folder where images should be stored + folder: 'uploads', - /* - Max file size allowed. Needs to be in MB - Note: When maxSize is greater than 1 MiB, you must set the client_max_body_size to the same as maxSize. - */ - maxSize: '512MB', + /* + Max file size allowed. Needs to be in MB + Note: When maxSize is greater than 1 MiB, you must set the client_max_body_size to the same as maxSize. + */ + maxSize: '512MB', - // The length of the random generated name for the uploaded files - fileLength: 32, + // The length of the random generated name for the uploaded files + fileLength: 32, - /* - NOTE: Thumbnails are only for the admin panel and they require you - to install a separate binary called graphicsmagick (http://www.graphicsmagick.org) - for images and ffmpeg (https://ffmpeg.org/) for video files - */ - generateThumbnails: false, + /* + NOTE: Thumbnails are only for the admin panel and they require you + to install a separate binary called graphicsmagick (http://www.graphicsmagick.org) + for images and ffmpeg (https://ffmpeg.org/) for video files + */ + generateThumbnails: false, - /* - Allows users to download a .zip file of all files in an album. - The file is generated when the user clicks the download button in the view - and is re-used if the album has not changed between download requests - */ - generateZips: true - }, + /* + Allows users to download a .zip file of all files in an album. + The file is generated when the user clicks the download button in the view + and is re-used if the album has not changed between download requests + */ + generateZips: true + }, - // Folder where to store logs - logsFolder: 'logs', + // Folder where to store logs + logsFolder: 'logs', - // The following values shouldn't be touched - database: { - client: 'sqlite3', - connection: { filename: './database/db' }, - useNullAsDefault: true - } -}; + // The following values shouldn't be touched + database: { + client: 'sqlite3', + connection: { filename: './database/db' }, + useNullAsDefault: true + } +} diff --git a/controllers/albumsController.js b/controllers/albumsController.js index ed57cac..2b27dfa 100644 --- a/controllers/albumsController.js +++ b/controllers/albumsController.js @@ -1,179 +1,178 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); -const path = require('path'); -const fs = require('fs'); -const Zip = require('jszip'); -const albumsController = {}; +const config = require('../config.js') +const db = require('knex')(config.database) +const randomstring = require('randomstring') +const utils = require('./utilsController.js') +const path = require('path') +const fs = require('fs') +const Zip = require('jszip') +const albumsController = {} albumsController.list = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const fields = ['id', 'name']; - if (req.params.sidebar === undefined) { - fields.push('timestamp'); - fields.push('identifier'); - } + const fields = ['id', 'name'] + if (req.params.sidebar === undefined) { + fields.push('timestamp') + fields.push('identifier') + } - const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id }); - if (req.params.sidebar !== undefined) { - return res.json({ success: true, albums }); - } + const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id }) + if (req.params.sidebar !== undefined) { + return res.json({ success: true, albums }) + } - let ids = []; - for (let album of albums) { - album.date = new Date(album.timestamp * 1000) - album.date = utils.getPrettyDate(album.date) + let ids = [] + for (let album of albums) { + album.date = new Date(album.timestamp * 1000) + album.date = utils.getPrettyDate(album.date) - album.identifier = `${config.domain}/a/${album.identifier}`; - ids.push(album.id); - } + album.identifier = `${config.domain}/a/${album.identifier}` + ids.push(album.id) + } - const files = await db.table('files').whereIn('albumid', ids).select('albumid'); - const albumsCount = {}; + const files = await db.table('files').whereIn('albumid', ids).select('albumid') + const albumsCount = {} - for (let id of ids) albumsCount[id] = 0; - for (let file of files) albumsCount[file.albumid] += 1; - for (let album of albums) album.files = albumsCount[album.id]; + for (let id of ids) albumsCount[id] = 0 + for (let file of files) albumsCount[file.albumid] += 1 + for (let album of albums) album.files = albumsCount[album.id] - return res.json({ success: true, albums }); -}; + return res.json({ success: true, albums }) +} albumsController.create = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const name = req.body.name; - if (name === undefined || name === '') { - return res.json({ success: false, description: 'No album name specified' }); - } + const name = req.body.name + if (name === undefined || name === '') { + return res.json({ success: false, description: 'No album name specified' }) + } - const album = await db.table('albums').where({ - name: name, - enabled: 1, - userid: user.id - }).first(); + const album = await db.table('albums').where({ + name: name, + enabled: 1, + userid: user.id + }).first() - if (album) { - return res.json({ success: false, description: 'There\'s already an album with that name' }) - } + if (album) { + return res.json({ success: false, description: 'There\'s already an album with that name' }) + } - await db.table('albums').insert({ - name: name, - enabled: 1, - userid: user.id, - identifier: randomstring.generate(8), - timestamp: Math.floor(Date.now() / 1000) - }); + await db.table('albums').insert({ + name: name, + enabled: 1, + userid: user.id, + identifier: randomstring.generate(8), + timestamp: Math.floor(Date.now() / 1000) + }) - return res.json({ success: true }); -}; + return res.json({ success: true }) +} albumsController.delete = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No album specified' }); - } + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No album specified' }) + } - await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 }); - return res.json({ success: true }); -}; + await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 }) + return res.json({ success: true }) +} albumsController.rename = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No album specified' }); - } + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No album specified' }) + } - const name = req.body.name; - if (name === undefined || name === '') { - return res.json({ success: false, description: 'No name specified' }); - } + const name = req.body.name + if (name === undefined || name === '') { + return res.json({ success: false, description: 'No name specified' }) + } - const album = await db.table('albums').where({ name: name, userid: user.id }).first(); - if (album) { - return res.json({ success: false, description: 'Name already in use' }) - } + const album = await db.table('albums').where({ name: name, userid: user.id }).first() + if (album) { + return res.json({ success: false, description: 'Name already in use' }) + } - await db.table('albums').where({ id: id, userid: user.id }).update({ name: name }) - return res.json({ success: true }); -}; + await db.table('albums').where({ id: id, userid: user.id }).update({ name: name }) + return res.json({ success: true }) +} albumsController.get = async (req, res, next) => { - const identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); + const identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.json({ success: false, description: 'Album not found' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.json({ success: false, description: 'Album not found' }) - const title = album.name; - const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC'); + const title = album.name + const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC') - for (let file of files) { - file.file = `${config.domain}/${file.name}`; + for (let file of files) { + file.file = `${config.domain}/${file.name}` - const ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - } - } - - return res.json({ - success: true, - title: title, - count: files.length, - files - }); -}; + const ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png` + } + } + return res.json({ + success: true, + title: title, + count: files.length, + files + }) +} albumsController.generateZip = async (req, res, next) => { - const identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); - if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' }); + const identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) + if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.json({ success: false, description: 'Album not found' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.json({ success: false, description: 'Album not found' }) - if (album.zipGeneratedAt > album.editedAt) { - const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`); - const fileName = `${album.name}.zip`; - return res.download(filePath, fileName); - } else { - console.log(`Generating zip for album identifier: ${identifier}`); - const files = await db.table('files').select('name').where('albumid', album.id); - if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' }); + if (album.zipGeneratedAt > album.editedAt) { + const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`) + const fileName = `${album.name}.zip` + return res.download(filePath, fileName) + } else { + console.log(`Generating zip for album identifier: ${identifier}`) + const files = await db.table('files').select('name').where('albumid', album.id) + if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' }) - const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`); - let archive = new Zip(); + const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`) + let archive = new Zip() - for (let file of files) { - try { - const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name)); - archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name))); - } catch (err) { - console.log(err); - } - } + for (let file of files) { + try { + // const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name)) + archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name))) + } catch (err) { + console.log(err) + } + } - archive - .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) - .pipe(fs.createWriteStream(zipPath)) - .on('finish', async () => { - console.log(`Generated zip for album identifier: ${identifier}`); - await db.table('albums') - .where('id', album.id) - .update({ zipGeneratedAt: Math.floor(Date.now() / 1000) }); + archive + .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) + .pipe(fs.createWriteStream(zipPath)) + .on('finish', async () => { + console.log(`Generated zip for album identifier: ${identifier}`) + await db.table('albums') + .where('id', album.id) + .update({ zipGeneratedAt: Math.floor(Date.now() / 1000) }) - const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`); - const fileName = `${album.name}.zip`; - return res.download(filePath, fileName); - }); - } -}; + const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`) + const fileName = `${album.name}.zip` + return res.download(filePath, fileName) + }) + } +} -module.exports = albumsController; +module.exports = albumsController diff --git a/controllers/authController.js b/controllers/authController.js index 95a151e..0dce932 100644 --- a/controllers/authController.js +++ b/controllers/authController.js @@ -1,86 +1,86 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const bcrypt = require('bcrypt'); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const db = require('knex')(config.database) +const bcrypt = require('bcrypt') +const randomstring = require('randomstring') +const utils = require('./utilsController.js') -let authController = {}; +let authController = {} authController.verify = async (req, res, next) => { - const username = req.body.username; - const password = req.body.password; + const username = req.body.username + const password = req.body.password - if (username === undefined) return res.json({ success: false, description: 'No username provided' }); - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + if (username === undefined) return res.json({ success: false, description: 'No username provided' }) + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - const user = await db.table('users').where('username', username).first(); - if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' }); + const user = await db.table('users').where('username', username).first() + if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' }) - bcrypt.compare(password, user.password, (err, result) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'There was an error' }); - } - if (result === false) return res.json({ success: false, description: 'Wrong password' }); - return res.json({ success: true, token: user.token }); - }); -}; + bcrypt.compare(password, user.password, (err, result) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'There was an error' }) + } + if (result === false) return res.json({ success: false, description: 'Wrong password' }) + return res.json({ success: true, token: user.token }) + }) +} authController.register = async (req, res, next) => { - if (config.enableUserAccounts === false) { - return res.json({ success: false, description: 'Register is disabled at the moment' }); - } + if (config.enableUserAccounts === false) { + return res.json({ success: false, description: 'Register is disabled at the moment' }) + } - const username = req.body.username; - const password = req.body.password; + const username = req.body.username + const password = req.body.password - if (username === undefined) return res.json({ success: false, description: 'No username provided' }); - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + if (username === undefined) return res.json({ success: false, description: 'No username provided' }) + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - if (username.length < 4 || username.length > 32) { - return res.json({ success: false, description: 'Username must have 4-32 characters' }) - } - if (password.length < 6 || password.length > 64) { - return res.json({ success: false, description: 'Password must have 6-64 characters' }) - } + if (username.length < 4 || username.length > 32) { + return res.json({ success: false, description: 'Username must have 4-32 characters' }) + } + if (password.length < 6 || password.length > 64) { + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + } - const user = await db.table('users').where('username', username).first(); - if (user) return res.json({ success: false, description: 'Username already exists' }); + const user = await db.table('users').where('username', username).first() + if (user) return res.json({ success: false, description: 'Username already exists' }) - bcrypt.hash(password, 10, async (err, hash) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }); - } - const token = randomstring.generate(64); - await db.table('users').insert({ - username: username, - password: hash, - token: token - }); - return res.json({ success: true, token: token }) - }); -}; + bcrypt.hash(password, 10, async (err, hash) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + } + const token = randomstring.generate(64) + await db.table('users').insert({ + username: username, + password: hash, + token: token + }) + return res.json({ success: true, token: token }) + }) +} authController.changePassword = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - let password = req.body.password; - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + let password = req.body.password + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - if (password.length < 6 || password.length > 64) { - return res.json({ success: false, description: 'Password must have 6-64 characters' }); - } + if (password.length < 6 || password.length > 64) { + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + } - bcrypt.hash(password, 10, async (err, hash) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }); - } + bcrypt.hash(password, 10, async (err, hash) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + } - await db.table('users').where('id', user.id).update({ password: hash }); - return res.json({ success: true }); - }); -}; + await db.table('users').where('id', user.id).update({ password: hash }) + return res.json({ success: true }) + }) +} -module.exports = authController; +module.exports = authController diff --git a/controllers/tokenController.js b/controllers/tokenController.js index cbcc550..bb8f9c9 100644 --- a/controllers/tokenController.js +++ b/controllers/tokenController.js @@ -1,34 +1,34 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const db = require('knex')(config.database) +const randomstring = require('randomstring') +const utils = require('./utilsController.js') -const tokenController = {}; +const tokenController = {} tokenController.verify = async (req, res, next) => { - const token = req.body.token; - if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }); + const token = req.body.token + if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - const user = await db.table('users').where('token', token).first(); - if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }); - return res.json({ success: true, username: user.username }); -}; + const user = await db.table('users').where('token', token).first() + if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }) + return res.json({ success: true, username: user.username }) +} tokenController.list = async (req, res, next) => { - const user = await utils.authorize(req, res); - return res.json({ success: true, token: user.token }); -}; + const user = await utils.authorize(req, res) + return res.json({ success: true, token: user.token }) +} tokenController.change = async (req, res, next) => { - const user = await utils.authorize(req, res); - const newtoken = randomstring.generate(64); + const user = await utils.authorize(req, res) + const newtoken = randomstring.generate(64) - await db.table('users').where('token', user.token).update({ - token: newtoken, - timestamp: Math.floor(Date.now() / 1000) - }); + await db.table('users').where('token', user.token).update({ + token: newtoken, + timestamp: Math.floor(Date.now() / 1000) + }) - res.json({ success: true, token: newtoken }); -}; + res.json({ success: true, token: newtoken }) +} -module.exports = tokenController; +module.exports = tokenController diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 3fb9b55..94eb9a8 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -1,285 +1,285 @@ -const config = require('../config.js'); -const path = require('path'); -const multer = require('multer'); -const randomstring = require('randomstring'); -const db = require('knex')(config.database); -const crypto = require('crypto'); -const fs = require('fs'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const path = require('path') +const multer = require('multer') +const randomstring = require('randomstring') +const db = require('knex')(config.database) +const crypto = require('crypto') +const fs = require('fs') +const utils = require('./utilsController.js') -const uploadsController = {}; +const uploadsController = {} const storage = multer.diskStorage({ - destination: function(req, file, cb) { - cb(null, path.join(__dirname, '..', config.uploads.folder)); - }, - filename: function(req, file, cb) { - cb(null, randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname)); - } -}); + destination: function (req, file, cb) { + cb(null, path.join(__dirname, '..', config.uploads.folder)) + }, + filename: function (req, file, cb) { + cb(null, randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname)) + } +}) const upload = multer({ - storage: storage, - limits: { fileSize: config.uploads.maxSize }, - fileFilter: function(req, file, cb) { - if (config.blockedExtensions !== undefined) { - if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) { - return cb('This file extension is not allowed'); - } - return cb(null, true); - } - return cb(null, true); - } -}).array('files[]'); + storage: storage, + limits: { fileSize: config.uploads.maxSize }, + fileFilter: function (req, file, cb) { + if (config.blockedExtensions !== undefined) { + if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) { + return cb('This file extension is not allowed') // eslint-disable-line standard/no-callback-literal + } + return cb(null, true) + } + return cb(null, true) + } +}).array('files[]') uploadsController.upload = async (req, res, next) => { - if (config.private === true) { - await utils.authorize(req, res); - } + if (config.private === true) { + await utils.authorize(req, res) + } - const token = req.headers.token || ''; - const user = await db.table('users').where('token', token).first(); - const albumid = req.headers.albumid || req.params.albumid; + const token = req.headers.token || '' + const user = await db.table('users').where('token', token).first() + const albumid = req.headers.albumid || req.params.albumid - if (albumid && user) { - const album = await db.table('albums').where({ id: albumid, userid: user.id }).first(); - if (!album) { - return res.json({ - success: false, - description: 'Album doesn\'t exist or it doesn\'t belong to the user' - }); - } - return uploadsController.actuallyUpload(req, res, user, albumid); - } - return uploadsController.actuallyUpload(req, res, user, albumid); -}; + if (albumid && user) { + const album = await db.table('albums').where({ id: albumid, userid: user.id }).first() + if (!album) { + return res.json({ + success: false, + description: 'Album doesn\'t exist or it doesn\'t belong to the user' + }) + } + return uploadsController.actuallyUpload(req, res, user, albumid) + } + return uploadsController.actuallyUpload(req, res, user, albumid) +} uploadsController.actuallyUpload = async (req, res, userid, album) => { - upload(req, res, async err => { - if (err) { - console.error(err); - return res.json({ success: false, description: err }); - } + upload(req, res, async err => { + if (err) { + console.error(err) + return res.json({ success: false, description: err }) + } - if (req.files.length === 0) return res.json({ success: false, description: 'no-files' }); + if (req.files.length === 0) return res.json({ success: false, description: 'no-files' }) - const files = []; - const existingFiles = []; - let iteration = 1; + const files = [] + const existingFiles = [] + let iteration = 1 - req.files.forEach(async file => { - // Check if the file exists by checking hash and size - let hash = crypto.createHash('md5'); - let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename)); + req.files.forEach(async file => { + // Check if the file exists by checking hash and size + let hash = crypto.createHash('md5') + let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename)) - stream.on('data', data => { - hash.update(data, 'utf8'); - }); + stream.on('data', data => { + hash.update(data, 'utf8') + }) - stream.on('end', async () => { - const fileHash = hash.digest('hex'); - const dbFile = await db.table('files') - .where(function() { - if (userid === undefined) this.whereNull('userid'); - else this.where('userid', userid.id); - }) - .where({ - hash: fileHash, - size: file.size - }) - .first(); + stream.on('end', async () => { + const fileHash = hash.digest('hex') + const dbFile = await db.table('files') + .where(function () { + if (userid === undefined) this.whereNull('userid') + else this.where('userid', userid.id) + }) + .where({ + hash: fileHash, + size: file.size + }) + .first() - if (!dbFile) { - files.push({ - name: file.filename, - original: file.originalname, - type: file.mimetype, - size: file.size, - hash: fileHash, - ip: req.ip, - albumid: album, - userid: userid.id, - timestamp: Math.floor(Date.now() / 1000) - }); - } else { - uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err)); - existingFiles.push(dbFile); - } + if (!dbFile) { + files.push({ + name: file.filename, + original: file.originalname, + type: file.mimetype, + size: file.size, + hash: fileHash, + ip: req.ip, + albumid: album, + userid: userid ? userid.id : null, + timestamp: Math.floor(Date.now() / 1000) + }) + } else { + uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err)) + existingFiles.push(dbFile) + } - if (iteration === req.files.length) { - return uploadsController.processFilesForDisplay(req, res, files, existingFiles); - } - iteration++; - }); - }); - }); -}; + if (iteration === req.files.length) { + return uploadsController.processFilesForDisplay(req, res, files, existingFiles) + } + iteration++ + }) + }) + }) +} uploadsController.processFilesForDisplay = async (req, res, files, existingFiles) => { - let basedomain = config.domain; - if (files.length === 0) { - return res.json({ - success: true, - files: existingFiles.map(file => { - return { - name: file.name, - size: file.size, - url: `${basedomain}/${file.name}` - }; - }) - }); - } + let basedomain = config.domain + if (files.length === 0) { + return res.json({ + success: true, + files: existingFiles.map(file => { + return { + name: file.name, + size: file.size, + url: `${basedomain}/${file.name}` + } + }) + }) + } - await db.table('files').insert(files); - for (let efile of existingFiles) files.push(efile); + await db.table('files').insert(files) + for (let efile of existingFiles) files.push(efile) - res.json({ - success: true, - files: files.map(file => { - return { - name: file.name, - size: file.size, - url: `${basedomain}/${file.name}` - }; - }) - }); + res.json({ + success: true, + files: files.map(file => { + return { + name: file.name, + size: file.size, + url: `${basedomain}/${file.name}` + } + }) + }) - for (let file of files) { - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - utils.generateThumbs(file); - } + for (let file of files) { + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` + utils.generateThumbs(file) + } - if (file.albumid) { - db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {}) - .catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }); }); - } - } -}; + if (file.albumid) { + db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {}) + .catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }) }) + } + } +} uploadsController.delete = async (req, res) => { - const user = await utils.authorize(req, res); - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No file specified' }); - } + const user = await utils.authorize(req, res) + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No file specified' }) + } - const file = await db.table('files') - .where('id', id) - .where(function() { - if (user.username !== 'root') { - this.where('userid', user.id); - } - }) - .first(); + const file = await db.table('files') + .where('id', id) + .where(function () { + if (user.username !== 'root') { + this.where('userid', user.id) + } + }) + .first() - try { - await uploadsController.deleteFile(file.name); - await db.table('files').where('id', id).del(); - if (file.albumid) { - await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000)); - } - } catch (err) { - console.log(err); - } + try { + await uploadsController.deleteFile(file.name) + await db.table('files').where('id', id).del() + if (file.albumid) { + await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000)) + } + } catch (err) { + console.log(err) + } - return res.json({ success: true }); -}; + return res.json({ success: true }) +} -uploadsController.deleteFile = function(file) { - const ext = path.extname(file).toLowerCase(); - return new Promise((resolve, reject) => { - fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => { - if (err) { return reject(err); } - fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => { - if (err) { return reject(err); } - if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) { - return resolve(); - } - file = file.substr(0, file.lastIndexOf('.')) + '.png'; - fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => { - if (err) { - console.log(err); - return resolve(); - } - fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => { - if (err) { return reject(err); } - return resolve(); - }); - }); - }); - }); - }); -}; +uploadsController.deleteFile = function (file) { + const ext = path.extname(file).toLowerCase() + return new Promise((resolve, reject) => { + fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => { + if (err) { return reject(err) } + fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => { + if (err) { return reject(err) } + if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) { + return resolve() + } + file = file.substr(0, file.lastIndexOf('.')) + '.png' + fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => { + if (err) { + console.log(err) + return resolve() + } + fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => { + if (err) { return reject(err) } + return resolve() + }) + }) + }) + }) + }) +} uploadsController.list = async (req, res) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - let offset = req.params.page; - if (offset === undefined) offset = 0; + let offset = req.params.page + if (offset === undefined) offset = 0 - const files = await db.table('files') - .where(function() { - if (req.params.id === undefined) this.where('id', '<>', ''); - else this.where('albumid', req.params.id); - }) - .where(function() { - if (user.username !== 'root') this.where('userid', user.id); - }) - .orderBy('id', 'DESC') - .limit(25) - .offset(25 * offset) - .select('id', 'albumid', 'timestamp', 'name', 'userid'); + const files = await db.table('files') + .where(function () { + if (req.params.id === undefined) this.where('id', '<>', '') + else this.where('albumid', req.params.id) + }) + .where(function () { + if (user.username !== 'root') this.where('userid', user.id) + }) + .orderBy('id', 'DESC') + .limit(25) + .offset(25 * offset) + .select('id', 'albumid', 'timestamp', 'name', 'userid') - const albums = await db.table('albums'); - let basedomain = config.domain; - let userids = []; + const albums = await db.table('albums') + let basedomain = config.domain + let userids = [] - for (let file of files) { - file.file = `${basedomain}/${file.name}`; - file.date = new Date(file.timestamp * 1000); - file.date = utils.getPrettyDate(file.date); + for (let file of files) { + file.file = `${basedomain}/${file.name}` + file.date = new Date(file.timestamp * 1000) + file.date = utils.getPrettyDate(file.date) - file.album = ''; + file.album = '' - if (file.albumid !== undefined) { - for (let album of albums) { - if (file.albumid === album.id) { - file.album = album.name; - } - } - } + if (file.albumid !== undefined) { + for (let album of albums) { + if (file.albumid === album.id) { + file.album = album.name + } + } + } - // Only push usernames if we are root - if (user.username === 'root') { - if (file.userid !== undefined && file.userid !== null && file.userid !== '') { - userids.push(file.userid); - } - } + // Only push usernames if we are root + if (user.username === 'root') { + if (file.userid !== undefined && file.userid !== null && file.userid !== '') { + userids.push(file.userid) + } + } - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - } - } + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` + } + } - // If we are a normal user, send response - if (user.username !== 'root') return res.json({ success: true, files }); + // If we are a normal user, send response + if (user.username !== 'root') return res.json({ success: true, files }) - // If we are root but there are no uploads attached to a user, send response - if (userids.length === 0) return res.json({ success: true, files }); + // If we are root but there are no uploads attached to a user, send response + if (userids.length === 0) return res.json({ success: true, files }) - const users = await db.table('users').whereIn('id', userids); - for (let dbUser of users) { - for (let file of files) { - if (file.userid === dbUser.id) { - file.username = dbUser.username; - } - } - } + const users = await db.table('users').whereIn('id', userids) + for (let dbUser of users) { + for (let file of files) { + if (file.userid === dbUser.id) { + file.username = dbUser.username + } + } + } - return res.json({ success: true, files }); -}; + return res.json({ success: true, files }) +} -module.exports = uploadsController; +module.exports = uploadsController diff --git a/controllers/utilsController.js b/controllers/utilsController.js index ebfb36d..5cd2170 100644 --- a/controllers/utilsController.js +++ b/controllers/utilsController.js @@ -1,67 +1,67 @@ -const path = require('path'); -const config = require('../config.js'); -const fs = require('fs'); -const gm = require('gm'); -const ffmpeg = require('fluent-ffmpeg'); -const db = require('knex')(config.database); +const path = require('path') +const config = require('../config.js') +const fs = require('fs') +const gm = require('gm') +const ffmpeg = require('fluent-ffmpeg') +const db = require('knex')(config.database) -const utilsController = {}; -utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png']; -utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov']; +const utilsController = {} +utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'] +utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov'] -utilsController.getPrettyDate = function(date) { - return date.getFullYear() + '-' - + (date.getMonth() + 1) + '-' - + date.getDate() + ' ' - + (date.getHours() < 10 ? '0' : '') - + date.getHours() + ':' - + (date.getMinutes() < 10 ? '0' : '') - + date.getMinutes() + ':' - + (date.getSeconds() < 10 ? '0' : '') - + date.getSeconds(); +utilsController.getPrettyDate = function (date) { + return date.getFullYear() + '-' + + (date.getMonth() + 1) + '-' + + date.getDate() + ' ' + + (date.getHours() < 10 ? '0' : '') + + date.getHours() + ':' + + (date.getMinutes() < 10 ? '0' : '') + + date.getMinutes() + ':' + + (date.getSeconds() < 10 ? '0' : '') + + date.getSeconds() } utilsController.authorize = async (req, res) => { - const token = req.headers.token; - if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }); + const token = req.headers.token + if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - const user = await db.table('users').where('token', token).first(); - if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }); - return user; -}; + const user = await db.table('users').where('token', token).first() + if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }) + return user +} -utilsController.generateThumbs = function(file, basedomain) { - if (config.uploads.generateThumbnails !== true) return; - const ext = path.extname(file.name).toLowerCase(); +utilsController.generateThumbs = function (file, basedomain) { + if (config.uploads.generateThumbnails !== true) return + const ext = path.extname(file.name).toLowerCase() - let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png'); - fs.access(thumbname, err => { - if (err && err.code === 'ENOENT') { - if (utilsController.videoExtensions.includes(ext)) { - ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name)) - .thumbnail({ - timestamps: [0], - filename: '%b.png', - folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'), - size: '200x?' - }) - .on('error', error => console.log('Error - ', error.message)); - } else { - let size = { - width: 200, - height: 200 - }; - gm(path.join(__dirname, '..', config.uploads.folder, file.name)) - .resize(size.width, size.height + '>') - .gravity('Center') - .extent(size.width, size.height) - .background('transparent') - .write(thumbname, error => { - if (error) console.log('Error - ', error); - }); - } - } - }); -}; + let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png') + fs.access(thumbname, err => { + if (err && err.code === 'ENOENT') { + if (utilsController.videoExtensions.includes(ext)) { + ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name)) + .thumbnail({ + timestamps: [0], + filename: '%b.png', + folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'), + size: '200x?' + }) + .on('error', error => console.log('Error - ', error.message)) + } else { + let size = { + width: 200, + height: 200 + } + gm(path.join(__dirname, '..', config.uploads.folder, file.name)) + .resize(size.width, size.height + '>') + .gravity('Center') + .extent(size.width, size.height) + .background('transparent') + .write(thumbname, error => { + if (error) console.log('Error - ', error) + }) + } + } + }) +} -module.exports = utilsController; +module.exports = utilsController diff --git a/database/db.js b/database/db.js index f1ec75a..5e9be2b 100644 --- a/database/db.js +++ b/database/db.js @@ -1,50 +1,49 @@ -let init = function(db){ +let init = function (db) { + // Create the tables we need to store galleries and files + db.schema.createTableIfNotExists('albums', function (table) { + table.increments() + table.integer('userid') + table.string('name') + table.string('identifier') + table.integer('enabled') + table.integer('timestamp') + }).then(() => {}) - // Create the tables we need to store galleries and files - db.schema.createTableIfNotExists('albums', function (table) { - table.increments() - table.integer('userid') - table.string('name') - table.string('identifier') - table.integer('enabled') - table.integer('timestamp') - }).then(() => {}) + db.schema.createTableIfNotExists('files', function (table) { + table.increments() + table.integer('userid') + table.string('name') + table.string('original') + table.string('type') + table.string('size') + table.string('hash') + table.string('ip') + table.integer('albumid') + table.integer('timestamp') + }).then(() => {}) - db.schema.createTableIfNotExists('files', function (table) { - table.increments() - table.integer('userid') - table.string('name') - table.string('original') - table.string('type') - table.string('size') - table.string('hash') - table.string('ip') - table.integer('albumid') - table.integer('timestamp') - }).then(() => {}) + db.schema.createTableIfNotExists('users', function (table) { + table.increments() + table.string('username') + table.string('password') + table.string('token') + table.integer('timestamp') + }).then(() => { + db.table('users').where({username: 'root'}).then((user) => { + if (user.length > 0) return - db.schema.createTableIfNotExists('users', function (table) { - table.increments() - table.string('username') - table.string('password') - table.string('token') - table.integer('timestamp') - }).then(() => { - db.table('users').where({username: 'root'}).then((user) => { - if(user.length > 0) return + require('bcrypt').hash('root', 10, function (err, hash) { + if (err) console.error('Error generating password hash for root') - require('bcrypt').hash('root', 10, function(err, hash) { - if(err) console.error('Error generating password hash for root') - - db.table('users').insert({ - username: 'root', - password: hash, - token: require('randomstring').generate(64), - timestamp: Math.floor(Date.now() / 1000) - }).then(() => {}) - }) - }) - }) + db.table('users').insert({ + username: 'root', + password: hash, + token: require('randomstring').generate(64), + timestamp: Math.floor(Date.now() / 1000) + }).then(() => {}) + }) + }) + }) } -module.exports = init \ No newline at end of file +module.exports = init diff --git a/database/migration.js b/database/migration.js index fb30779..4b2febf 100644 --- a/database/migration.js +++ b/database/migration.js @@ -1,13 +1,13 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); +const config = require('../config.js') +const db = require('knex')(config.database) -const migration = {}; +const migration = {} migration.start = async () => { - await db.schema.table('albums', table => { - table.dateTime('editedAt'); - table.dateTime('zipGeneratedAt'); - }); - console.log('Migration finished! Now start lolisafe normally'); -}; + await db.schema.table('albums', table => { + table.dateTime('editedAt') + table.dateTime('zipGeneratedAt') + }) + console.log('Migration finished! Now start lolisafe normally') +} -migration.start(); +migration.start() diff --git a/lolisafe.js b/lolisafe.js index f4c3315..c6e2352 100644 --- a/lolisafe.js +++ b/lolisafe.js @@ -1,58 +1,66 @@ -const config = require('./config.js'); -const api = require('./routes/api.js'); -const album = require('./routes/album.js'); -const express = require('express'); -const helmet = require('helmet'); -const bodyParser = require('body-parser'); -const RateLimit = require('express-rate-limit'); -const db = require('knex')(config.database); -const fs = require('fs'); -const exphbs = require('express-handlebars'); -const safe = express(); +const config = require('./config.js') +const api = require('./routes/api.js') +const album = require('./routes/album.js') +const express = require('express') +const helmet = require('helmet') +const bodyParser = require('body-parser') +const RateLimit = require('express-rate-limit') +const db = require('knex')(config.database) +const fs = require('fs') +const exphbs = require('express-handlebars') +const safe = express() -require('./database/db.js')(db); +require('./database/db.js')(db) -fs.existsSync('./pages/custom') || fs.mkdirSync('./pages/custom'); -fs.existsSync(`./${config.logsFolder}`) || fs.mkdirSync(`./${ config.logsFolder}`); -fs.existsSync(`./${config.uploads.folder}`) || fs.mkdirSync(`./${config.uploads.folder}`); -fs.existsSync(`./${config.uploads.folder}/thumbs`) || fs.mkdirSync(`./${config.uploads.folder }/thumbs`); -fs.existsSync(`./${config.uploads.folder }/zips`) || fs.mkdirSync(`./${config.uploads.folder}/zips`); +fs.existsSync('./pages/custom') || fs.mkdirSync('./pages/custom') +fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder) +fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder) +fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs') +fs.existsSync('./' + config.uploads.folder + '/zips') || fs.mkdirSync('./' + config.uploads.folder + '/zips') -safe.use(helmet()); -safe.set('trust proxy', 1); +safe.use(helmet()) +safe.set('trust proxy', 1) -safe.engine('handlebars', exphbs({ defaultLayout: 'main' })); -safe.set('view engine', 'handlebars'); -safe.enable('view cache'); +safe.engine('handlebars', exphbs({ defaultLayout: 'main' })) +safe.set('view engine', 'handlebars') +safe.enable('view cache') -let limiter = new RateLimit({ windowMs: 5000, max: 2 }); -safe.use('/api/login/', limiter); -safe.use('/api/register/', limiter); +let limiter = new RateLimit({ windowMs: 5000, max: 2 }) +safe.use('/api/login/', limiter) +safe.use('/api/register/', limiter) -safe.use(bodyParser.urlencoded({ extended: true })); -safe.use(bodyParser.json()); +safe.use(bodyParser.urlencoded({ extended: true })) +safe.use(bodyParser.json()) if (config.serveFilesWithNode) { - safe.use('/', express.static(config.uploads.folder)); + safe.use('/', express.static(config.uploads.folder)) } -safe.use('/', express.static('./public')); -safe.use('/', album); -safe.use('/api', api); +safe.use('/', express.static('./public')) +safe.use('/', album) +safe.use('/api', api) for (let page of config.pages) { - let root = './pages/'; - if (fs.existsSync(`./pages/custom/${page}.html`)) { - root = './pages/custom/'; - } - if (page === 'home') { - safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root })); - } else { - safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root })); - } + let root = './pages/' + if (fs.existsSync(`./pages/custom/${page}.html`)) { + root = './pages/custom/' + } + if (page === 'home') { + safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root })) + } else { + safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root })) + } } -safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' })); -safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' })); +safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' })) +safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' })) -safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`)); +safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`)) + +process.on('uncaughtException', err => { + console.error(`Uncaught Exception:\n${err.stack}`) +}) + +process.on('unhandledRejection', err => { + console.error(`Unhandled Rejection (Promise):\n${err.stack}`) +}) diff --git a/package.json b/package.json index e9a2396..555bef2 100644 --- a/package.json +++ b/package.json @@ -30,19 +30,9 @@ "sqlite3": "^3.1.13" }, "devDependencies": { - "eslint": "^4.4.1", - "eslint-config-aqua": "^1.5.0" + "standard": "^10.0.3" }, - "eslintConfig": { - "extends": [ - "aqua" - ], - "env": { - "browser": true, - "node": true - }, - "rules": { - "func-names": 0 - } + "standard": { + "envs": ["browser", "node"] } } diff --git a/pages/auth.html b/pages/auth.html index 85a38f4..587c49a 100644 --- a/pages/auth.html +++ b/pages/auth.html @@ -39,6 +39,7 @@ + + diff --git a/pages/dashboard.html b/pages/dashboard.html index 9739dee..5ba6561 100644 --- a/pages/dashboard.html +++ b/pages/dashboard.html @@ -39,6 +39,7 @@ + + diff --git a/public/images/icons/browserconfig.xml b/public/images/icons/browserconfig.xml index 1c28f50..3c5d9d1 100644 --- a/public/images/icons/browserconfig.xml +++ b/public/images/icons/browserconfig.xml @@ -1,9 +1,9 @@ - - - - #232629 - - + + + + #232629 + + diff --git a/public/images/icons/manifest.json b/public/images/icons/manifest.json index dbbbe65..72b32fe 100644 --- a/public/images/icons/manifest.json +++ b/public/images/icons/manifest.json @@ -1,18 +1,18 @@ { - "name": "safe.fiery.me", - "icons": [ - { - "src": "/images/icons/android-chrome-192x192.png?v=ZqYs7M3fG4", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/images/icons/android-chrome-384x384.png?v=ZqYs7M3fG4", - "sizes": "384x384", - "type": "image/png" - } - ], - "theme_color": "#232629", - "background_color": "#232629", - "display": "standalone" + "name": "safe.fiery.me", + "icons": [ + { + "src": "/images/icons/android-chrome-192x192.png?v=ZqYs7M3fG4", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/icons/android-chrome-384x384.png?v=ZqYs7M3fG4", + "sizes": "384x384", + "type": "image/png" + } + ], + "theme_color": "#232629", + "background_color": "#232629", + "display": "standalone" } diff --git a/public/js/auth.js b/public/js/auth.js index 56eea32..e881dd3 100644 --- a/public/js/auth.js +++ b/public/js/auth.js @@ -1,44 +1,56 @@ -var page = {}; +/* global swal, axios */ -page.do = function(dest) { - var user = document.getElementById('user').value; - var pass = document.getElementById('pass').value; +var page = {} - if (user === undefined || user === null || user === '') { return swal('Error', 'You need to specify a username', 'error'); } - if (pass === undefined || pass === null || pass === '') { return swal('Error', 'You need to specify a username', 'error'); } +page.do = function (dest) { + var user = document.getElementById('user').value + var pass = document.getElementById('pass').value - axios.post(`/api/${dest}`, { - username: user, - password: pass - }) - .then(response => { - if (response.data.success === false) { return swal('Error', response.data.description, 'error'); } + if (user === undefined || user === null || user === '') { + return swal('Error', 'You need to specify a username', 'error') + } + if (pass === undefined || pass === null || pass === '') { + return swal('Error', 'You need to specify a username', 'error') + } - localStorage.token = response.data.token; - window.location = '/dashboard'; - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; + axios.post('/api/' + dest, { + username: user, + password: pass + }) + .then(function (response) { + if (response.data.success === false) { + return swal('Error', response.data.description, 'error') + } -page.verify = function() { - page.token = localStorage.token; - if (page.token === undefined) return; + localStorage.token = response.data.token + window.location = '/dashboard' + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} - axios.post('/api/tokens/verify', { token: page.token }) - .then(response => { - if (response.data.success === false) { return swal('Error', response.data.description, 'error'); } +page.verify = function () { + page.token = localStorage.token + if (page.token === undefined) return - window.location = '/dashboard'; - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; + axios.post('/api/tokens/verify', { + token: page.token + }) + .then(function (response) { + if (response.data.success === false) { + return swal('Error', response.data.description, 'error') + } -window.onload = function() { - page.verify(); -}; + window.location = '/dashboard' + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +window.onload = function () { + page.verify() +} diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 3238f7b..c20865a 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -1,553 +1,576 @@ -let panel = {}; - -panel.page; -panel.username; -panel.token = localStorage.token; -panel.filesView = localStorage.filesView; - -panel.preparePage = function() { - if (!panel.token) return window.location = '/auth'; - panel.verifyToken(panel.token, true); -}; - -panel.verifyToken = function(token, reloadOnError) { - if (reloadOnError === undefined) { reloadOnError = false; } - - axios.post('/api/tokens/verify', { token: token }) - .then(response => { - if (response.data.success === false) { - swal({ - title: 'An error ocurred', - text: response.data.description, - type: 'error' - }, () => { - if (reloadOnError) { - localStorage.removeItem('token'); - location.location = '/auth'; - } - }); - return; - } - - axios.defaults.headers.common.token = token; - localStorage.token = token; - panel.token = token; - panel.username = response.data.username; - return panel.prepareDashboard(); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.prepareDashboard = function() { - panel.page = document.getElementById('page'); - document.getElementById('auth').style.display = 'none'; - document.getElementById('dashboard').style.display = 'block'; - - document.getElementById('itemUploads').addEventListener('click', function() { - panel.setActiveMenu(this); - }); - - document.getElementById('itemManageGallery').addEventListener('click', function() { - panel.setActiveMenu(this); - }); - - document.getElementById('itemTokens').addEventListener('click', function() { - panel.setActiveMenu(this); - }); - - document.getElementById('itemPassword').addEventListener('click', function() { - panel.setActiveMenu(this); - }); - - document.getElementById('itemLogout').innerHTML = `Logout ( ${panel.username} )`; - - panel.getAlbumsSidebar(); -}; - -panel.logout = function() { - localStorage.removeItem('token'); - location.reload('/'); -}; - -panel.getUploads = function(album = undefined, page = undefined) { - if (page === undefined) page = 0; - - let url = `/api/uploads/${page}`; - if (album !== undefined) { url = `/api/album/${album}/${page}`; } - - axios.get(url).then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - var prevPage = 0; - var nextPage = page + 1; - - if (response.data.files.length < 25) { nextPage = page; } - - if (page > 0) prevPage = page - 1; - - panel.page.innerHTML = ''; - var container = document.createElement('div'); - var pagination = ``; - var listType = ` -
-
- - - - - - - - - - -
-
`; - - if (panel.filesView === 'thumbs') { - container.innerHTML = ` - ${pagination} -
- ${listType} -
- -
- ${pagination} - `; - - panel.page.appendChild(container); - var table = document.getElementById('table'); - - for (var item of response.data.files) { - var div = document.createElement('div'); - div.className = 'column is-2'; - if (item.thumb !== undefined) { div.innerHTML = ``; } else { div.innerHTML = `

.${item.file.split('.').pop()}

`; } - table.appendChild(div); - } - } else { - var albumOrUser = 'Album'; - if (panel.username === 'root') { albumOrUser = 'User'; } - - container.innerHTML = ` - ${pagination} -
- ${listType} - - - - - - - - - - - -
File${albumOrUser}Date
-
- ${pagination} - `; - - panel.page.appendChild(container); - var table = document.getElementById('table'); - - for (var item of response.data.files) { - var tr = document.createElement('tr'); - - var displayAlbumOrUser = item.album; - if (panel.username === 'root') { - displayAlbumOrUser = ''; - if (item.username !== undefined) { displayAlbumOrUser = item.username; } - } - - tr.innerHTML = ` - - ${item.file} - ${displayAlbumOrUser} - ${item.date} - - - - - - - - - `; - - table.appendChild(tr); - } - } - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.setFilesView = function(view, album, page) { - localStorage.filesView = view; - panel.filesView = view; - panel.getUploads(album, page); -}; - -panel.deleteFile = function(id) { - swal({ - title: 'Are you sure?', - text: 'You wont be able to recover the file!', - type: 'warning', - showCancelButton: true, - confirmButtonColor: '#ff3860', - confirmButtonText: 'Yes, delete it!', - closeOnConfirm: false - }, - () => { - axios.post('/api/upload/delete', { id: id }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal('Deleted!', 'The file has been deleted.', 'success'); - panel.getUploads(); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); - } - ); -}; - -panel.getAlbums = function() { - axios.get('/api/albums').then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - panel.page.innerHTML = ''; - var container = document.createElement('div'); - container.className = 'container'; - container.innerHTML = ` -

Create new album

- -

- - Submit -

- -

List of albums

- - - - - - - - - - - - - -
NameFilesCreated AtPublic link
`; - - panel.page.appendChild(container); - var table = document.getElementById('table'); - - for (var item of response.data.albums) { - var tr = document.createElement('tr'); - tr.innerHTML = ` - - ${item.name} - ${item.files} - ${item.date} - Album link - - - - - - - - - - - - - - `; - - table.appendChild(tr); - } - - document.getElementById('submitAlbum').addEventListener('click', () => { - panel.submitAlbum(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.renameAlbum = function(id) { - swal({ - title: 'Rename album', - text: 'New name you want to give the album:', - type: 'input', - showCancelButton: true, - closeOnConfirm: false, - animation: 'slide-from-top', - inputPlaceholder: 'My super album' - }, inputValue => { - if (inputValue === false) return false; - if (inputValue === '') { - swal.showInputError('You need to write something!'); - return false; - } - - axios.post('/api/albums/rename', { - id: id, - name: inputValue - }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else if (response.data.description === 'Name already in use') swal.showInputError('That name is already in use!'); - else swal('An error ocurred', response.data.description, 'error'); - return; - } - - swal('Success!', `Your album was renamed to: ${inputValue}`, 'success'); - panel.getAlbumsSidebar(); - panel.getAlbums(); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); - }); -}; - -panel.deleteAlbum = function(id) { - swal({ - title: 'Are you sure?', - text: "This won't delete your files, only the album!", - type: 'warning', - showCancelButton: true, - confirmButtonColor: '#ff3860', - confirmButtonText: 'Yes, delete it!', - closeOnConfirm: false - }, - () => { - axios.post('/api/albums/delete', { id: id }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal('Deleted!', 'Your album has been deleted.', 'success'); - panel.getAlbumsSidebar(); - panel.getAlbums(); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); - } - ); -}; - -panel.submitAlbum = function() { - axios.post('/api/albums', { name: document.getElementById('albumName').value }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal('Woohoo!', 'Album was added successfully', 'success'); - panel.getAlbumsSidebar(); - panel.getAlbums(); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.getAlbumsSidebar = function() { - axios.get('/api/albums/sidebar') - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - var albumsContainer = document.getElementById('albumsContainer'); - albumsContainer.innerHTML = ''; - - if (response.data.albums === undefined) return; - - for (var album of response.data.albums) { - li = document.createElement('li'); - a = document.createElement('a'); - a.id = album.id; - a.innerHTML = album.name; - - a.addEventListener('click', function() { - panel.getAlbum(this); - }); - - li.appendChild(a); - albumsContainer.appendChild(li); - } - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.getAlbum = function(item) { - panel.setActiveMenu(item); - panel.getUploads(item.id); -}; - -panel.changeToken = function() { - axios.get('/api/tokens') - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - panel.page.innerHTML = ''; - var container = document.createElement('div'); - container.className = 'container'; - container.innerHTML = ` -

Manage your token

- - -

- - Request new token -

- `; - - panel.page.appendChild(container); - - document.getElementById('getNewToken').addEventListener('click', () => { - panel.getNewToken(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.getNewToken = function() { - axios.post('/api/tokens/change') - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal({ - title: 'Woohoo!', - text: 'Your token was changed successfully.', - type: 'success' - }, () => { - localStorage.token = response.data.token; - location.reload(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.changePassword = function() { - panel.page.innerHTML = ''; - var container = document.createElement('div'); - container.className = 'container'; - container.innerHTML = ` -

Change your password

- - -

- -

- -

- - Set new password -

- `; - - panel.page.appendChild(container); - - document.getElementById('sendChangePassword').addEventListener('click', () => { - if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) { - panel.sendNewPassword(document.getElementById('password').value); - } else { - swal({ - title: 'Password mismatch!', - text: 'Your passwords do not match, please try again.', - type: 'error' - }, () => { - panel.changePassword(); - }); - } - }); -}; - -panel.sendNewPassword = function(pass) { - axios.post('/api/password/change', { password: pass }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal({ - title: 'Woohoo!', - text: 'Your password was changed successfully.', - type: 'success' - }, () => { - location.reload(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.setActiveMenu = function(item) { - var menu = document.getElementById('menu'); - var items = menu.getElementsByTagName('a'); - for (var i = 0; i < items.length; i++) { items[i].className = ''; } - - item.className = 'is-active'; -}; - -window.onload = function() { - panel.preparePage(); -}; +/* eslint-disable no-unused-expressions */ +/* global swal, axios */ + +let panel = {} + +panel.page +panel.username +panel.token = localStorage.token +panel.filesView = localStorage.filesView + +panel.preparePage = function () { + if (!panel.token) { + window.location = '/auth' + return '/auth' + } + panel.verifyToken(panel.token, true) +} + +panel.verifyToken = function (token, reloadOnError) { + if (reloadOnError === undefined) { + reloadOnError = false + } + + axios.post('/api/tokens/verify', { + token: token + }) + .then(function (response) { + if (response.data.success === false) { + swal({ + title: 'An error ocurred', + text: response.data.description, + type: 'error' + }, function () { + if (reloadOnError) { + localStorage.removeItem('token') + location.location = '/auth' + } + }) + return + } + + axios.defaults.headers.common.token = token + localStorage.token = token + panel.token = token + panel.username = response.data.username + return panel.prepareDashboard() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.prepareDashboard = function () { + panel.page = document.getElementById('page') + document.getElementById('auth').style.display = 'none' + document.getElementById('dashboard').style.display = 'block' + + document.getElementById('itemUploads').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemManageGallery').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemTokens').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemPassword').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemLogout').innerHTML = `Logout ( ${panel.username} )` + + panel.getAlbumsSidebar() +} + +panel.logout = function () { + localStorage.removeItem('token') + location.reload('/') +} + +panel.getUploads = function (album = undefined, page = undefined) { + if (page === undefined) page = 0 + + let url = '/api/uploads/' + page + if (album !== undefined) { url = '/api/album/' + album + '/' + page } + + axios.get(url).then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + var prevPage = 0 + var nextPage = page + 1 + + if (response.data.files.length < 25) { nextPage = page } + + if (page > 0) prevPage = page - 1 + + panel.page.innerHTML = '' + var container = document.createElement('div') + var pagination = `` + var listType = ` +
+
+ + + + + + + + + + +
+
` + + var table, item + if (panel.filesView === 'thumbs') { + container.innerHTML = ` + ${pagination} +
+ ${listType} +
+ +
+ ${pagination} + ` + + panel.page.appendChild(container) + table = document.getElementById('table') + + for (item of response.data.files) { + var div = document.createElement('div') + div.className = 'column is-2' + if (item.thumb !== undefined) { + div.innerHTML = `` + } else { + div.innerHTML = `

.${item.file.split('.').pop()}

` + } + table.appendChild(div) + } + } else { + var albumOrUser = 'Album' + if (panel.username === 'root') { albumOrUser = 'User' } + + container.innerHTML = ` + ${pagination} +
+ ${listType} + + + + + + + + + + + +
File${albumOrUser}Date
+
+ ${pagination} + ` + + panel.page.appendChild(container) + table = document.getElementById('table') + + for (item of response.data.files) { + var tr = document.createElement('tr') + + var displayAlbumOrUser = item.album + console.log(item) + if (panel.username === 'root') { + displayAlbumOrUser = '' + if (item.username !== undefined) { displayAlbumOrUser = item.username } + } + + tr.innerHTML = ` + + ${item.file} + ${displayAlbumOrUser} + ${item.date} + + + + + + + + + ` + + table.appendChild(tr) + } + } + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.setFilesView = function (view, album, page) { + localStorage.filesView = view + panel.filesView = view + panel.getUploads(album, page) +} + +panel.deleteFile = function (id) { + swal({ + title: 'Are you sure?', + text: 'You wont be able to recover the file!', + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#ff3860', + confirmButtonText: 'Yes, delete it!', + closeOnConfirm: false + }, + function () { + axios.post('/api/upload/delete', { + id: id + }) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal('Deleted!', 'The file has been deleted.', 'success') + panel.getUploads() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) + } + ) +} + +panel.getAlbums = function () { + axios.get('/api/albums').then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + panel.page.innerHTML = '' + var container = document.createElement('div') + container.className = 'container' + container.innerHTML = ` +

Create new album

+ +

+ + Submit +

+ +

List of albums

+ + + + + + + + + + + + + +
NameFilesCreated AtPublic link
` + + panel.page.appendChild(container) + var table = document.getElementById('table') + + for (var item of response.data.albums) { + var tr = document.createElement('tr') + tr.innerHTML = ` + + ${item.name} + ${item.files} + ${item.date} + Album link + + + + + + + + + + + + + + ` + + table.appendChild(tr) + } + + document.getElementById('submitAlbum').addEventListener('click', function () { + panel.submitAlbum() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.renameAlbum = function (id) { + swal({ + title: 'Rename album', + text: 'New name you want to give the album:', + type: 'input', + showCancelButton: true, + closeOnConfirm: false, + animation: 'slide-from-top', + inputPlaceholder: 'My super album' + }, function (inputValue) { + if (inputValue === false) return false + if (inputValue === '') { + swal.showInputError('You need to write something!') + return false + } + + axios.post('/api/albums/rename', { + id: id, + name: inputValue + }) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else if (response.data.description === 'Name already in use') swal.showInputError('That name is already in use!') + else swal('An error ocurred', response.data.description, 'error') + return + } + + swal('Success!', 'Your album was renamed to: ' + inputValue, 'success') + panel.getAlbumsSidebar() + panel.getAlbums() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) + }) +} + +panel.deleteAlbum = function (id) { + swal({ + title: 'Are you sure?', + text: "This won't delete your files, only the album!", + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#ff3860', + confirmButtonText: 'Yes, delete it!', + closeOnConfirm: false + }, + function () { + axios.post('/api/albums/delete', { + id: id + }) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal('Deleted!', 'Your album has been deleted.', 'success') + panel.getAlbumsSidebar() + panel.getAlbums() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) + } + ) +} + +panel.submitAlbum = function () { + axios.post('/api/albums', { + name: document.getElementById('albumName').value + }) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal('Woohoo!', 'Album was added successfully', 'success') + panel.getAlbumsSidebar() + panel.getAlbums() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.getAlbumsSidebar = function () { + axios.get('/api/albums/sidebar') + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + var albumsContainer = document.getElementById('albumsContainer') + albumsContainer.innerHTML = '' + + if (response.data.albums === undefined) return + + var li, a + for (var album of response.data.albums) { + li = document.createElement('li') + a = document.createElement('a') + a.id = album.id + a.innerHTML = album.name + + a.addEventListener('click', function () { + panel.getAlbum(this) + }) + + li.appendChild(a) + albumsContainer.appendChild(li) + } + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.getAlbum = function (item) { + panel.setActiveMenu(item) + panel.getUploads(item.id) +} + +panel.changeToken = function () { + axios.get('/api/tokens') + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + panel.page.innerHTML = '' + var container = document.createElement('div') + container.className = 'container' + container.innerHTML = ` +

Manage your token

+ + +

+ + Request new token +

+ ` + + panel.page.appendChild(container) + + document.getElementById('getNewToken').addEventListener('click', function () { + panel.getNewToken() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.getNewToken = function () { + axios.post('/api/tokens/change') + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal({ + title: 'Woohoo!', + text: 'Your token was changed successfully.', + type: 'success' + }, function () { + localStorage.token = response.data.token + location.reload() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.changePassword = function () { + panel.page.innerHTML = '' + var container = document.createElement('div') + container.className = 'container' + container.innerHTML = ` +

Change your password

+ + +

+ +

+ +

+ + Set new password +

+ ` + + panel.page.appendChild(container) + + document.getElementById('sendChangePassword').addEventListener('click', function () { + if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) { + panel.sendNewPassword(document.getElementById('password').value) + } else { + swal({ + title: 'Password mismatch!', + text: 'Your passwords do not match, please try again.', + type: 'error' + }, function () { + panel.changePassword() + }) + } + }) +} + +panel.sendNewPassword = function (pass) { + axios.post('/api/password/change', {password: pass}) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal({ + title: 'Woohoo!', + text: 'Your password was changed successfully.', + type: 'success' + }, function () { + location.reload() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.setActiveMenu = function (item) { + var menu = document.getElementById('menu') + var items = menu.getElementsByTagName('a') + for (var i = 0; i < items.length; i++) { items[i].className = '' } + + item.className = 'is-active' +} + +window.onload = function () { + panel.preparePage() +} diff --git a/public/js/home.js b/public/js/home.js index d932aec..4c6e876 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1,172 +1,178 @@ -var upload = {}; +/* eslint-disable no-unused-expressions */ +/* global swal, axios, Dropzone */ -upload.isPrivate = true; -upload.token = localStorage.token; -upload.maxFileSize; +var upload = {} + +upload.isPrivate = true +upload.token = localStorage.token +upload.maxFileSize // Add the album var to the upload so we can store the album id in there -upload.album; -upload.myDropzone; +upload.album +upload.myDropzone -upload.checkIfPublic = function() { - axios.get('/api/check') - .then(response => { - upload.isPrivate = response.data.private; - upload.maxFileSize = response.data.maxFileSize; - upload.preparePage(); - }) - .catch(error => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(error); - }); -}; +upload.checkIfPublic = function () { + axios.get('/api/check') + .then(response => { + upload.isPrivate = response.data.private + upload.maxFileSize = response.data.maxFileSize + upload.preparePage() + }) + .catch(error => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(error) + }) +} -upload.preparePage = function() { - if (!upload.isPrivate) return upload.prepareUpload(); - if (!upload.token) return document.getElementById('loginToUpload').style.display = 'inline-flex'; - upload.verifyToken(upload.token, true); -}; +upload.preparePage = function () { + if (!upload.isPrivate) return upload.prepareUpload() + if (!upload.token) { + document.getElementById('loginToUpload').style.display = 'inline-flex' + return 'inline-flex' + } + upload.verifyToken(upload.token, true) +} -upload.verifyToken = function(token, reloadOnError) { - if (reloadOnError === undefined) { reloadOnError = false; } +upload.verifyToken = function (token, reloadOnError) { + if (reloadOnError === undefined) { reloadOnError = false } - axios.post('/api/tokens/verify', { token: token }) - .then(response => { - if (response.data.success === false) { - swal({ - title: 'An error ocurred', - text: response.data.description, - type: 'error' - }, () => { - if (reloadOnError) { - localStorage.removeItem('token'); - location.reload(); - } - }); - return; - } + axios.post('/api/tokens/verify', { token: token }) + .then(response => { + if (response.data.success === false) { + swal({ + title: 'An error ocurred', + text: response.data.description, + type: 'error' + }, () => { + if (reloadOnError) { + localStorage.removeItem('token') + location.reload() + } + }) + return + } - localStorage.token = token; - upload.token = token; - return upload.prepareUpload(); - }) - .catch(error => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(error); - }); -}; + localStorage.token = token + upload.token = token + return upload.prepareUpload() + }) + .catch(error => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(error) + }) +} -upload.prepareUpload = function() { - // I think this fits best here because we need to check for a valid token before we can get the albums - if (upload.token) { - var select = document.getElementById('albumSelect'); +upload.prepareUpload = function () { + // I think this fits best here because we need to check for a valid token before we can get the albums + if (upload.token) { + var select = document.getElementById('albumSelect') - select.addEventListener('change', () => { - upload.album = select.value; - }); + select.addEventListener('change', () => { + upload.album = select.value + }) - axios.get('/api/albums', { headers: { token: upload.token } }) - .then(res => { - var albums = res.data.albums; + axios.get('/api/albums', { headers: { token: upload.token } }) + .then(res => { + var albums = res.data.albums - // If the user doesn't have any albums we don't really need to display - // an album selection - if (albums.length === 0) return; + // If the user doesn't have any albums we don't really need to display + // an album selection + if (albums.length === 0) return - // Loop through the albums and create an option for each album - for (var i = 0; i < albums.length; i++) { - var opt = document.createElement('option'); - opt.value = albums[i].id; - opt.innerHTML = albums[i].name; - select.appendChild(opt); - } - // Display the album selection - document.getElementById('albumDiv').style.display = 'block'; - }) - .catch(e => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(e); - }); - } + // Loop through the albums and create an option for each album + for (var i = 0; i < albums.length; i++) { + var opt = document.createElement('option') + opt.value = albums[i].id + opt.innerHTML = albums[i].name + select.appendChild(opt) + } + // Display the album selection + document.getElementById('albumDiv').style.display = 'block' + }) + .catch(e => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(e) + }) + } - div = document.createElement('div'); - div.id = 'dropzone'; - div.innerHTML = 'Click here or drag and drop files'; - div.style.display = 'flex'; + var div = document.createElement('div') + div.id = 'dropzone' + div.innerHTML = 'Click here or drag and drop files' + div.style.display = 'flex' - document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}`; - document.getElementById('loginToUpload').style.display = 'none'; + document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}` + document.getElementById('loginToUpload').style.display = 'none' - if (upload.token === undefined) { document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads'; } + if (upload.token === undefined) { document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads' } - document.getElementById('uploadContainer').appendChild(div); + document.getElementById('uploadContainer').appendChild(div) - upload.prepareDropzone(); -}; + upload.prepareDropzone() +} -upload.prepareDropzone = function() { - var previewNode = document.querySelector('#template'); - previewNode.id = ''; - var previewTemplate = previewNode.parentNode.innerHTML; - previewNode.parentNode.removeChild(previewNode); +upload.prepareDropzone = function () { + var previewNode = document.querySelector('#template') + previewNode.id = '' + var previewTemplate = previewNode.parentNode.innerHTML + previewNode.parentNode.removeChild(previewNode) - var dropzone = new Dropzone('div#dropzone', { - url: '/api/upload', - paramName: 'files[]', - maxFilesize: upload.maxFileSize.slice(0, -2), - parallelUploads: 2, - uploadMultiple: false, - previewsContainer: 'div#uploads', - previewTemplate: previewTemplate, - createImageThumbnails: false, - maxFiles: 1000, - autoProcessQueue: true, - headers: { token: upload.token }, - init: function() { - upload.myDropzone = this; - this.on('addedfile', file => { - document.getElementById('uploads').style.display = 'block'; - }); - // Add the selected albumid, if an album is selected, as a header - this.on('sending', (file, xhr) => { - if (upload.album) { - xhr.setRequestHeader('albumid', upload.album); - } - }); - } - }); + var dropzone = new Dropzone('div#dropzone', { + url: '/api/upload', + paramName: 'files[]', + maxFilesize: upload.maxFileSize.slice(0, -2), + parallelUploads: 2, + uploadMultiple: false, + previewsContainer: 'div#uploads', + previewTemplate: previewTemplate, + createImageThumbnails: false, + maxFiles: 1000, + autoProcessQueue: true, + headers: { token: upload.token }, + init: function () { + upload.myDropzone = this + this.on('addedfile', file => { + document.getElementById('uploads').style.display = 'block' + }) + // Add the selected albumid, if an album is selected, as a header + this.on('sending', (file, xhr) => { + if (upload.album) { + xhr.setRequestHeader('albumid', upload.album) + } + }) + } + }) - // Update the total progress bar - dropzone.on('uploadprogress', (file, progress) => { - file.previewElement.querySelector('.progress').setAttribute('value', progress); - file.previewElement.querySelector('.progress').innerHTML = `${progress}%`; - }); + // Update the total progress bar + dropzone.on('uploadprogress', (file, progress) => { + file.previewElement.querySelector('.progress').setAttribute('value', progress) + file.previewElement.querySelector('.progress').innerHTML = `${progress}%` + }) - dropzone.on('success', (file, response) => { - // Handle the responseText here. For example, add the text to the preview element: + dropzone.on('success', (file, response) => { + // Handle the responseText here. For example, add the text to the preview element: - if (response.success === false) { - var span = document.createElement('span'); - span.innerHTML = response.description; - file.previewTemplate.querySelector('.link').appendChild(span); - return; - } + if (response.success === false) { + var span = document.createElement('span') + span.innerHTML = response.description + file.previewTemplate.querySelector('.link').appendChild(span) + return + } - a = document.createElement('a'); - a.href = response.files[0].url; - a.target = '_blank'; - a.innerHTML = response.files[0].url; - file.previewTemplate.querySelector('.link').appendChild(a); + var a = document.createElement('a') + a.href = response.files[0].url + a.target = '_blank' + a.innerHTML = response.files[0].url + file.previewTemplate.querySelector('.link').appendChild(a) - file.previewTemplate.querySelector('.progress').style.display = 'none'; - }); + file.previewTemplate.querySelector('.progress').style.display = 'none' + }) - upload.prepareShareX(); -}; + upload.prepareShareX() +} -upload.prepareShareX = function() { - if (upload.token) { - var sharex_element = document.getElementById('ShareX'); - var sharex_file = `{\r\n\ +upload.prepareShareX = function () { + if (upload.token) { + var sharexElement = document.getElementById('ShareX') + var sharexFile = `{\r\n\ "Name": "${location.hostname}",\r\n\ "DestinationType": "ImageUploader, FileUploader",\r\n\ "RequestType": "POST",\r\n\ @@ -178,30 +184,29 @@ upload.prepareShareX = function() { "ResponseType": "Text",\r\n\ "URL": "$json:files[0].url$",\r\n\ "ThumbnailURL": "$json:files[0].url$"\r\n\ -}`; - var sharex_blob = new Blob([sharex_file], { type: 'application/octet-binary' }); - sharex_element.setAttribute('href', URL.createObjectURL(sharex_blob)); - sharex_element.setAttribute('download', `${location.hostname}.sxcu`); - } -}; +}` + var sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' }) + sharexElement.setAttribute('href', URL.createObjectURL(sharexBlob)) + sharexElement.setAttribute('download', `${location.hostname}.sxcu`) + } +} // Handle image paste event window.addEventListener('paste', event => { - var items = (event.clipboardData || event.originalEvent.clipboardData).items; - for (index in items) { - var item = items[index]; - if (item.kind === 'file') { - var blob = item.getAsFile(); - console.log(blob.type); - var file = new File([blob], `pasted-image.${blob.type.match(/(?:[^\/]*\/)([^;]*)/)[1]}`); - file.type = blob.type; - console.log(file); - upload.myDropzone.addFile(file); - } - } -}); - -window.onload = function() { - upload.checkIfPublic(); -}; + var items = (event.clipboardData || event.originalEvent.clipboardData).items + for (var index in items) { + var item = items[index] + if (item.kind === 'file') { + var blob = item.getAsFile() + console.log(blob.type) + var file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`) + file.type = blob.type + console.log(file) + upload.myDropzone.addFile(file) + } + } +}) +window.onload = function () { + upload.checkIfPublic() +} diff --git a/routes/album.js b/routes/album.js index 46f00d3..56d2211 100644 --- a/routes/album.js +++ b/routes/album.js @@ -1,56 +1,55 @@ -const config = require('../config.js'); -const routes = require('express').Router(); -const db = require('knex')(config.database); -const path = require('path'); -const utils = require('../controllers/utilsController.js'); +const config = require('../config.js') +const routes = require('express').Router() +const db = require('knex')(config.database) +const path = require('path') +const utils = require('../controllers/utilsController.js') routes.get('/a/:identifier', async (req, res, next) => { - let identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); + let identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.status(404).sendFile('404.html', { root: './pages/error/' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.status(404).sendFile('404.html', { root: './pages/error/' }) - const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC'); - let thumb = ''; - const basedomain = config.domain; + const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC') + let thumb = '' + const basedomain = config.domain - for (let file of files) { - file.file = `${basedomain}/${file.name}`; + for (let file of files) { + file.file = `${basedomain}/${file.name}` - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` - /* - If thumbnail for album is still not set, do it. - A potential improvement would be to let the user upload a specific image as an album cover - since embedding the first image could potentially result in nsfw content when pasting links. - */ + /* + If thumbnail for album is still not set, do it. + A potential improvement would be to let the user upload a specific image as an album cover + since embedding the first image could potentially result in nsfw content when pasting links. + */ - if (thumb === '') { - thumb = file.thumb; - } + if (thumb === '') { + thumb = file.thumb + } - file.thumb = ``; - } else { - file.thumb = `

.${ext}

`; - } - } + file.thumb = `` + } else { + file.thumb = `

.${ext}

` + } + } + let enableDownload = false + if (config.uploads.generateZips) enableDownload = true - let enableDownload = false; - if (config.uploads.generateZips) enableDownload = true; + return res.render('album', { + layout: false, + title: album.name, + count: files.length, + thumb, + files, + identifier, + enableDownload + }) +}) - return res.render('album', { - layout: false, - title: album.name, - count: files.length, - thumb, - files, - identifier, - enableDownload - }); -}); - -module.exports = routes; +module.exports = routes diff --git a/routes/api.js b/routes/api.js index 5a4d355..ae93e98 100644 --- a/routes/api.js +++ b/routes/api.js @@ -1,37 +1,37 @@ -const config = require('../config.js'); -const routes = require('express').Router(); -const uploadController = require('../controllers/uploadController'); -const albumsController = require('../controllers/albumsController'); -const tokenController = require('../controllers/tokenController'); -const authController = require('../controllers/authController'); +const config = require('../config.js') +const routes = require('express').Router() +const uploadController = require('../controllers/uploadController') +const albumsController = require('../controllers/albumsController') +const tokenController = require('../controllers/tokenController') +const authController = require('../controllers/authController') routes.get('/check', (req, res, next) => { - return res.json({ - private: config.private, - maxFileSize: config.uploads.maxSize - }); -}); + return res.json({ + private: config.private, + maxFileSize: config.uploads.maxSize + }) +}) -routes.post('/login', (req, res, next) => authController.verify(req, res, next)); -routes.post('/register', (req, res, next) => authController.register(req, res, next)); -routes.post('/password/change', (req, res, next) => authController.changePassword(req, res, next)); -routes.get('/uploads', (req, res, next) => uploadController.list(req, res, next)); -routes.get('/uploads/:page', (req, res, next) => uploadController.list(req, res, next)); -routes.post('/upload', (req, res, next) => uploadController.upload(req, res, next)); -routes.post('/upload/delete', (req, res, next) => uploadController.delete(req, res, next)); -routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next)); -routes.get('/album/get/:identifier', (req, res, next) => albumsController.get(req, res, next)); -routes.get('/album/zip/:identifier', (req, res, next) => albumsController.generateZip(req, res, next)); -routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next)); -routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next)); -routes.get('/albums', (req, res, next) => albumsController.list(req, res, next)); -routes.get('/albums/:sidebar', (req, res, next) => albumsController.list(req, res, next)); -routes.post('/albums', (req, res, next) => albumsController.create(req, res, next)); -routes.post('/albums/delete', (req, res, next) => albumsController.delete(req, res, next)); -routes.post('/albums/rename', (req, res, next) => albumsController.rename(req, res, next)); -routes.get('/albums/test', (req, res, next) => albumsController.test(req, res, next)); -routes.get('/tokens', (req, res, next) => tokenController.list(req, res, next)); -routes.post('/tokens/verify', (req, res, next) => tokenController.verify(req, res, next)); -routes.post('/tokens/change', (req, res, next) => tokenController.change(req, res, next)); +routes.post('/login', (req, res, next) => authController.verify(req, res, next)) +routes.post('/register', (req, res, next) => authController.register(req, res, next)) +routes.post('/password/change', (req, res, next) => authController.changePassword(req, res, next)) +routes.get('/uploads', (req, res, next) => uploadController.list(req, res, next)) +routes.get('/uploads/:page', (req, res, next) => uploadController.list(req, res, next)) +routes.post('/upload', (req, res, next) => uploadController.upload(req, res, next)) +routes.post('/upload/delete', (req, res, next) => uploadController.delete(req, res, next)) +routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next)) +routes.get('/album/get/:identifier', (req, res, next) => albumsController.get(req, res, next)) +routes.get('/album/zip/:identifier', (req, res, next) => albumsController.generateZip(req, res, next)) +routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next)) +routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next)) +routes.get('/albums', (req, res, next) => albumsController.list(req, res, next)) +routes.get('/albums/:sidebar', (req, res, next) => albumsController.list(req, res, next)) +routes.post('/albums', (req, res, next) => albumsController.create(req, res, next)) +routes.post('/albums/delete', (req, res, next) => albumsController.delete(req, res, next)) +routes.post('/albums/rename', (req, res, next) => albumsController.rename(req, res, next)) +routes.get('/albums/test', (req, res, next) => albumsController.test(req, res, next)) +routes.get('/tokens', (req, res, next) => tokenController.list(req, res, next)) +routes.post('/tokens/verify', (req, res, next) => tokenController.verify(req, res, next)) +routes.post('/tokens/change', (req, res, next) => tokenController.change(req, res, next)) -module.exports = routes; +module.exports = routes diff --git a/yarn.lock b/yarn.lock index de5490d..470535e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,11 +27,11 @@ acorn@^5.2.1: version "5.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" -ajv-keywords@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" -ajv@^4.9.1: +ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: @@ -47,15 +47,6 @@ ajv@^5.1.0: json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" -ajv@^5.2.3, ajv@^5.3.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -68,9 +59,9 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" ansi-regex@^2.0.0: version "2.1.1" @@ -159,6 +150,13 @@ array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" +array.prototype.find@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + arrify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -205,7 +203,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.22.0: +babel-code-frame@^6.16.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -295,6 +293,10 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + busboy@^0.2.11: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" @@ -335,7 +337,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@2.3.0, chalk@^2.0.0, chalk@^2.1.0: +chalk@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" dependencies: @@ -343,7 +345,7 @@ chalk@2.3.0, chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -353,19 +355,15 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" +cli-cursor@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" dependencies: - restore-cursor "^2.0.0" + restore-cursor "^1.0.1" cli-width@^2.0.0: version "2.2.0" @@ -415,7 +413,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -427,6 +425,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -466,14 +468,6 @@ cross-spawn@^4.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -486,6 +480,12 @@ cryptiles@3.x.x: dependencies: boom "5.x.x" +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -496,7 +496,11 @@ dasherize@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" -debug@2.6.9: +debug-log@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" + +debug@2.6.9, debug@^2.1.1, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -539,6 +543,17 @@ define-properties@^1.1.2: foreach "^2.0.5" object-keys "^1.0.8" +deglob@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/deglob/-/deglob-2.1.0.tgz#4d44abe16ef32c779b4972bd141a80325029a14a" + dependencies: + find-root "^1.0.0" + glob "^7.0.5" + ignore "^3.0.9" + pkg-config "^1.1.0" + run-parallel "^1.1.2" + uniq "^1.0.1" + del@^2.0.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" @@ -588,7 +603,14 @@ dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" -doctrine@^2.1.0: +doctrine@1.5.0, doctrine@^1.2.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" dependencies: @@ -612,10 +634,86 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" +error-ex@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.7.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.38" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.38.tgz#fa7d40d65bbc9bb8a67e1d3f9cc656a00530eed3" + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + +es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + es6-promise@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -624,64 +722,122 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -eslint-config-aqua@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-aqua/-/eslint-config-aqua-1.5.0.tgz#105ab60d5e932c4e63127cef2bed60393d83af00" - -eslint-scope@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-visitor-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" +eslint-config-standard-jsx@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-4.0.2.tgz#009e53c4ddb1e9ee70b4650ffe63a7f39f8836e1" -eslint@^4.4.1: - version "4.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.16.0.tgz#934ada9e98715e1d7bbfd6f6f0519ed2fab35cc1" +eslint-config-standard@10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz#c061e4d066f379dc17cd562c64e819b4dd454591" + +eslint-import-resolver-node@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c" dependencies: - ajv "^5.3.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^3.1.0" - doctrine "^2.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.2" + debug "^2.2.0" + object-assign "^4.0.1" + resolve "^1.1.6" + +eslint-module-utils@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.2.0" + doctrine "1.5.0" + eslint-import-resolver-node "^0.2.0" + eslint-module-utils "^2.0.0" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + pkg-up "^1.0.0" + +eslint-plugin-node@~4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.3.tgz#c04390ab8dbcbb6887174023d6f3a72769e63b97" + dependencies: + ignore "^3.0.11" + minimatch "^3.0.2" + object-assign "^4.0.1" + resolve "^1.1.7" + semver "5.3.0" + +eslint-plugin-promise@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca" + +eslint-plugin-react@~6.10.0: + version "6.10.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78" + dependencies: + array.prototype.find "^2.0.1" + doctrine "^1.2.2" + has "^1.0.1" + jsx-ast-utils "^1.3.4" + object.assign "^4.0.4" + +eslint-plugin-standard@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz#34d0c915b45edc6f010393c7eef3823b08565cf2" + +eslint@~3.19.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" + dependencies: + babel-code-frame "^6.16.0" + chalk "^1.1.3" + concat-stream "^1.5.2" + debug "^2.1.1" + doctrine "^2.0.0" + escope "^3.6.0" + espree "^3.4.0" esquery "^1.0.0" + estraverse "^4.2.0" esutils "^2.0.2" file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.0.1" - ignore "^3.3.3" + glob "^7.0.3" + globals "^9.14.0" + ignore "^3.2.0" imurmurhash "^0.1.4" - inquirer "^3.0.6" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" is-resolvable "^1.0.0" - js-yaml "^3.9.1" - json-stable-stringify-without-jsonify "^1.0.1" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" + lodash "^4.0.0" + mkdirp "^0.5.0" natural-compare "^1.4.0" optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" - progress "^2.0.0" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.7.5" + strip-bom "^3.0.0" strip-json-comments "~2.0.1" - table "^4.0.1" + table "^3.7.8" text-table "~0.2.0" + user-home "^2.0.0" -espree@^3.5.2: +espree@^3.4.0: version "3.5.2" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" dependencies: @@ -705,7 +861,7 @@ esrecurse@^4.1.0: estraverse "^4.1.0" object-assign "^4.0.1" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -717,6 +873,17 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -800,14 +967,6 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -822,19 +981,16 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" dependencies: escape-string-regexp "^1.0.5" + object-assign "^4.1.0" file-entry-cache@^2.0.0: version "2.0.0" @@ -869,6 +1025,23 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" +find-root@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + findup-sync@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" @@ -985,14 +1158,10 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.1.0: +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -1006,10 +1175,24 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + generic-pool@^3.1.7: version "3.4.0" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.4.0.tgz#6b76fc201bb49a0ff381450f585352378fb1c08f" +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1039,7 +1222,7 @@ glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -1066,9 +1249,9 @@ global-prefix@^0.1.4: is-windows "^0.2.0" which "^1.2.12" -globals@^11.0.1: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4" +globals@^9.14.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" globby@^5.0.0: version "5.0.0" @@ -1136,10 +1319,20 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -1236,7 +1429,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.19, iconv-lite@^0.4.17: +iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -1244,7 +1437,7 @@ ienoopen@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b" -ignore@^3.3.3: +ignore@^3.0.11, ignore@^3.0.9, ignore@^3.2.0: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -1271,26 +1464,25 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" -inquirer@^3.0.6: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" + figures "^1.3.5" lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" through "^2.3.6" -interpret@^1.0.4: +interpret@^1.0.0, interpret@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -1305,10 +1497,22 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + is-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -1343,6 +1547,15 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" +is-my-json-valid@^2.10.0: + version "2.17.1" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -1385,9 +1598,15 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" is-relative@^1.0.0: version "1.0.0" @@ -1399,6 +1618,10 @@ is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -1421,7 +1644,7 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1447,7 +1670,7 @@ js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.9.1: +js-yaml@^3.5.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -1458,6 +1681,10 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +json-parse-better-errors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -1466,11 +1693,7 @@ json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - -json-stable-stringify@^1.0.1: +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" dependencies: @@ -1484,6 +1707,10 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -1493,6 +1720,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsx-ast-utils@^1.3.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" + jszip@^3.1.4: version "3.1.5" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37" @@ -1569,6 +1800,26 @@ liftoff@2.3.0: rechoir "^0.6.2" resolve "^1.1.7" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + lodash.isplainobject@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -1585,7 +1836,7 @@ lodash.reduce@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" -lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.0: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.3.0, lodash@^4.6.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -1648,11 +1899,7 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" - -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -1662,7 +1909,7 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@1.2.0, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -1693,9 +1940,9 @@ multer@^1.2.1: type-is "^1.6.4" xtend "^4.0.0" -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" nan@2.6.2: version "2.6.2" @@ -1785,7 +2032,7 @@ object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-keys@^1.0.10, object-keys@^1.0.8: +object-keys@^1.0.10, object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" @@ -1797,6 +2044,15 @@ object.assign@^4.0.3: function-bind "^1.1.0" object-keys "^1.0.10" +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -1831,11 +2087,9 @@ once@^1.3.0, once@^1.3.3: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - dependencies: - mimic-fn "^1.0.0" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" optimist@^0.6.1: version "0.6.1" @@ -1859,7 +2113,7 @@ os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -1870,6 +2124,22 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-limit@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + pako@~1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -1891,6 +2161,13 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -1899,11 +2176,21 @@ parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" @@ -1941,6 +2228,10 @@ pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -1951,13 +2242,40 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pkg-conf@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + +pkg-config@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pkg-config/-/pkg-config-1.1.1.tgz#557ef22d73da3c8837107766c52eadabde298fe4" + dependencies: + debug-log "^1.0.0" + find-root "^1.0.0" + xtend "^4.0.1" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-up@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26" + dependencies: + find-up "^1.0.0" + platform@1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" prelude-ls@~1.1.2: version "1.1.2" @@ -1971,9 +2289,9 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" +progress@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" promise@^7.0.0: version "7.3.1" @@ -2071,6 +2389,14 @@ readable-stream@~2.0.6: string_decoder "~0.10.x" util-deprecate "~1.0.1" +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -2157,7 +2483,7 @@ request@^2.81.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -require-uncached@^1.0.3: +require-uncached@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" dependencies: @@ -2181,12 +2507,12 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" + exit-hook "^1.0.0" + onetime "^1.0.0" right-align@^0.1.1: version "0.1.3" @@ -2200,26 +2526,28 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: dependencies: glob "^7.0.5" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" dependencies: - is-promise "^2.1.0" + once "^1.3.0" -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - dependencies: - rx-lite "*" +run-parallel@^1.1.2: + version "1.1.6" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.6.tgz#29003c9a2163e01e2d2dfc90575f2c6c1d61a039" -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +semver@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -2263,25 +2591,21 @@ setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" +shelljs@^0.7.5: + version "0.7.8" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" dependencies: - shebang-regex "^1.0.0" + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - dependencies: - is-fullwidth-code-point "^2.0.0" +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" sntp@1.x.x: version "1.0.9" @@ -2330,6 +2654,29 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" +standard-engine@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-7.0.0.tgz#ebb77b9c8fc2c8165ffa353bd91ba0dff41af690" + dependencies: + deglob "^2.1.0" + get-stdin "^5.0.1" + minimist "^1.1.0" + pkg-conf "^2.0.0" + +standard@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/standard/-/standard-10.0.3.tgz#7869bcbf422bdeeaab689a1ffb1fea9677dd50ea" + dependencies: + eslint "~3.19.0" + eslint-config-standard "10.2.1" + eslint-config-standard-jsx "4.0.2" + eslint-plugin-import "~2.2.0" + eslint-plugin-node "~4.2.2" + eslint-plugin-promise "~3.5.0" + eslint-plugin-react "~6.10.0" + eslint-plugin-standard "~3.0.1" + standard-engine "~7.0.0" + "statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -2346,7 +2693,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.0, string-width@^2.1.1: +string-width@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: @@ -2379,6 +2726,10 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -2393,16 +2744,16 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -table@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" - string-width "^2.1.1" + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" tar-pack@^3.4.0: version "3.4.0" @@ -2439,12 +2790,6 @@ tildify@1.2.0: dependencies: os-homedir "^1.0.0" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - dependencies: - os-tmpdir "~1.0.2" - tough-cookie@~2.3.0: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" @@ -2505,10 +2850,20 @@ unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + dependencies: + os-homedir "^1.0.0" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -2581,7 +2936,7 @@ x-xss-protection@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.0.0.tgz#898afb93869b24661cf9c52f9ee8db8ed0764dd9" -xtend@^4.0.0: +xtend@^4.0.0, xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"