mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Updates
* Switched standard to eslint with eslint-config-standard (and 4 more eslint plugins needed by standard). * Added "curly" eslint rule with "all" option. I like it. * Refactored all JS files to apply the new "curly" eslint rule. * Renewed axios.min.js, dropzone.min.js and sweetalert.min.js. Re-minified and added a small comment stating their version and copyright statement. * Some buttons in dashboard will now show loading icon whenever they're waiting for response from the server. * Updated README.md and .gitignore.
This commit is contained in:
parent
e81e706914
commit
2dd724f88f
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,5 +10,6 @@ npm-debug.log
|
||||
pages/custom/**
|
||||
migrate.js
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
.vscode/
|
||||
|
12
README.md
12
README.md
@ -1,18 +1,14 @@
|
||||
![loli-safe](https://s.fiery.me/rlpHkNJejaYKuVesQN1Gxtm43NokI8WQ.png)
|
||||
![loli-safe](https://s.fiery.me/cFUN.png)
|
||||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE)
|
||||
[![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn)
|
||||
|
||||
# lolisafe, a small safe worth protecting.
|
||||
|
||||
## What is `safe.fiery.me` branch?
|
||||
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
|
||||
This branch is the one being used at https://safe.fiery.me. If you are looking for the original, head to `master` branch, or even better to [WeebDev/lolisafe](https://github.com/WeebDev/lolisafe).
|
||||
|
||||
## What's new in v3.0.0
|
||||
- Backend rewrite to make it faster, better and easier to extend
|
||||
- Album downloads (Thanks to [PascalTemel](https://github.com/PascalTemel))
|
||||
- See releases for changelog
|
||||
|
||||
If you're upgrading from a version prior to v3.0.0 make sure to run **ONCE** `node database/migration.js` to create the missing columns on the database.
|
||||
If you want to use an existing lolisafe database with this branch, make sure to run `node database/migration.js` at least once to create some new columns introduced in this branch. You can ignore any errors about duplicate columns.
|
||||
Configuration file of lolisafe, `config.js`, is not 100% compatible with this branch. There are some options that had been renamed and/or restructured. Please make sure your config matches the sample in `config.sample.js` before starting.
|
||||
|
||||
## Running
|
||||
1. Ensure you have at least version 7.6.0 of node installed
|
||||
|
@ -10,7 +10,7 @@ const albumsController = {}
|
||||
albumsController.list = async (req, res, next) => {
|
||||
const albumDomain = config.albumDomain || config.domain
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
const fields = ['id', 'name']
|
||||
if (req.params.sidebar === undefined) {
|
||||
@ -35,16 +35,16 @@ albumsController.list = async (req, res, next) => {
|
||||
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 })
|
||||
}
|
||||
|
||||
albumsController.create = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
const name = req.body.name
|
||||
if (name === undefined || name === '') {
|
||||
@ -76,7 +76,7 @@ albumsController.create = async (req, res, next) => {
|
||||
|
||||
albumsController.delete = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
const id = req.body.id
|
||||
if (id === undefined || id === '') {
|
||||
@ -89,7 +89,7 @@ albumsController.delete = async (req, res, next) => {
|
||||
|
||||
albumsController.rename = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
const id = req.body.id
|
||||
if (id === undefined || id === '') {
|
||||
@ -112,10 +112,10 @@ albumsController.rename = async (req, res, next) => {
|
||||
|
||||
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.' })
|
||||
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.' })
|
||||
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')
|
||||
@ -139,11 +139,11 @@ albumsController.get = async (req, res, next) => {
|
||||
|
||||
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.' })
|
||||
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.' })
|
||||
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`)
|
||||
@ -152,7 +152,7 @@ albumsController.generateZip = async (req, res, next) => {
|
||||
} 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 (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()
|
||||
|
@ -10,11 +10,11 @@ authController.verify = async (req, res, next) => {
|
||||
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.' })
|
||||
if (!user) { return res.json({ success: false, description: 'Username doesn\'t exist.' }) }
|
||||
if (user.enabled === false || user.enabled === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
@ -27,7 +27,7 @@ authController.verify = async (req, res, next) => {
|
||||
console.log(err)
|
||||
return res.json({ success: false, description: 'There was an error.' })
|
||||
}
|
||||
if (result === false) return res.json({ success: false, description: 'Wrong password.' })
|
||||
if (result === false) { return res.json({ success: false, description: 'Wrong password.' }) }
|
||||
return res.json({ success: true, token: user.token })
|
||||
})
|
||||
}
|
||||
@ -40,8 +40,8 @@ authController.register = async (req, res, next) => {
|
||||
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.' })
|
||||
@ -51,7 +51,7 @@ authController.register = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const user = await db.table('users').where('username', username).first()
|
||||
if (user) return res.json({ success: false, description: 'Username already exists.' })
|
||||
if (user) { return res.json({ success: false, description: 'Username already exists.' }) }
|
||||
|
||||
bcrypt.hash(password, 10, async (err, hash) => {
|
||||
if (err) {
|
||||
@ -71,10 +71,10 @@ authController.register = async (req, res, next) => {
|
||||
|
||||
authController.changePassword = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
let password = req.body.password
|
||||
if (password === undefined) return res.json({ success: false, description: 'No password provided.' })
|
||||
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.' })
|
||||
@ -93,7 +93,7 @@ authController.changePassword = async (req, res, next) => {
|
||||
|
||||
authController.getFileLengthConfig = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
return res.json({ success: true, fileLength: user.fileLength, config: config.uploads.fileLength })
|
||||
}
|
||||
|
||||
@ -103,11 +103,11 @@ authController.changeFileLength = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
let fileLength = parseInt(req.body.fileLength)
|
||||
if (fileLength === undefined) return res.json({ success: false, description: 'No file name length provided.' })
|
||||
if (isNaN(fileLength)) return res.json({ success: false, description: 'File name length is not a valid number.' })
|
||||
if (fileLength === undefined) { return res.json({ success: false, description: 'No file name length provided.' }) }
|
||||
if (isNaN(fileLength)) { return res.json({ success: false, description: 'File name length is not a valid number.' }) }
|
||||
|
||||
if (fileLength < config.uploads.fileLength.min || fileLength > config.uploads.fileLength.max) {
|
||||
return res.json({ success: false, description: `File name length must be ${config.uploads.fileLength.min} to ${config.uploads.fileLength.max} characters.` })
|
||||
|
@ -7,22 +7,22 @@ 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.' })
|
||||
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.' })
|
||||
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)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
return res.json({ success: true, token: user.token })
|
||||
}
|
||||
|
||||
tokenController.change = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
const newtoken = randomstring.generate(64)
|
||||
await db.table('users').where('token', user.token).update({
|
||||
|
@ -28,11 +28,11 @@ const storage = multer.diskStorage({
|
||||
const uuidDir = path.join(chunksDir, req.body.uuid)
|
||||
fs.access(uuidDir, err => {
|
||||
// If it exists, callback
|
||||
if (!err) return cb(null, uuidDir)
|
||||
if (!err) { return cb(null, uuidDir) }
|
||||
// It it doesn't, then make it first
|
||||
fs.mkdir(uuidDir, err => {
|
||||
// If there was no error, callback
|
||||
if (!err) return cb(null, uuidDir)
|
||||
if (!err) { return cb(null, uuidDir) }
|
||||
// Otherwise, log it
|
||||
console.log(err)
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
@ -81,7 +81,7 @@ const upload = multer({
|
||||
const keys = Object.keys(req.body)
|
||||
if (keys.length) {
|
||||
for (const key of keys) {
|
||||
if (!/^dz/.test(key)) continue
|
||||
if (!/^dz/.test(key)) { continue }
|
||||
req.body[key.replace(/^dz/, '')] = req.body[key]
|
||||
delete req.body[key]
|
||||
}
|
||||
@ -114,11 +114,11 @@ uploadsController.getUniqueRandomName = (length, extension, cb) => {
|
||||
const name = randomstring.generate(length) + extension
|
||||
fs.access(path.join(uploadDir, name), err => {
|
||||
// If a file with the same name does not exist
|
||||
if (err) return cb(null, name)
|
||||
if (err) { return cb(null, name) }
|
||||
// If a file with the same name already exists, log to console
|
||||
console.log(`A file named ${name} already exists (${++i}/${maxTries}).`)
|
||||
// If it still haven't reached allowed maximum tries, then try again
|
||||
if (i < maxTries) return access(i)
|
||||
if (i < maxTries) { return access(i) }
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return cb('Could not allocate a unique random name. Try again?')
|
||||
})
|
||||
@ -131,7 +131,7 @@ uploadsController.upload = async (req, res, next) => {
|
||||
let user
|
||||
if (config.private === true) {
|
||||
user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
} else if (req.headers.token) {
|
||||
user = await db.table('users').where('token', req.headers.token).first()
|
||||
}
|
||||
@ -172,12 +172,12 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
|
||||
}
|
||||
|
||||
upload(req, res, async err => {
|
||||
if (err) return erred(err)
|
||||
if (err) { return erred(err) }
|
||||
|
||||
if (req.files.length === 0) return erred(new Error('No files.'))
|
||||
if (req.files.length === 0) { return erred(new Error('No files.')) }
|
||||
|
||||
// If chunked uploads is enabeld and the uploaded file is a chunk, then just say that it was a success
|
||||
if (chunkedUploads && req.body.uuid) return res.json({ success: true })
|
||||
if (chunkedUploads && req.body.uuid) { return res.json({ success: true }) }
|
||||
|
||||
const infoMap = req.files.map(file => {
|
||||
return {
|
||||
@ -206,7 +206,7 @@ uploadsController.finishChunks = async (req, res, next) => {
|
||||
let user
|
||||
if (config.private === true) {
|
||||
user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
} else if (req.headers.token) {
|
||||
user = await db.table('users').where('token', req.headers.token).first()
|
||||
}
|
||||
@ -247,25 +247,25 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
||||
}
|
||||
|
||||
const files = req.body.files
|
||||
if (!files) return erred(new Error('Missing files array.'))
|
||||
if (!files) { return erred(new Error('Missing files array.')) }
|
||||
|
||||
let iteration = 0
|
||||
const infoMap = []
|
||||
files.forEach(file => {
|
||||
const { uuid, count } = file
|
||||
if (!uuid || !count) return erred(new Error('Missing UUID and/or chunks count.'))
|
||||
if (!uuid || !count) { return erred(new Error('Missing UUID and/or chunks count.')) }
|
||||
|
||||
const chunksDirUuid = path.join(chunksDir, uuid)
|
||||
|
||||
fs.readdir(chunksDirUuid, async (err, chunks) => {
|
||||
if (err) return erred(err)
|
||||
if (count < chunks.length) return erred(new Error('Chunks count mismatch.'))
|
||||
if (err) { return erred(err) }
|
||||
if (count < chunks.length) { return erred(new Error('Chunks count mismatch.')) }
|
||||
|
||||
const extension = path.extname(chunks[0])
|
||||
const length = uploadsController.getFileNameLength(req)
|
||||
|
||||
uploadsController.getUniqueRandomName(length, extension, async (err, name) => {
|
||||
if (err) return erred(err)
|
||||
if (err) { return erred(err) }
|
||||
|
||||
const destination = path.join(uploadDir, name)
|
||||
const destFileStream = fs.createWriteStream(destination, { flags: 'a' })
|
||||
@ -280,7 +280,7 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
||||
}
|
||||
})
|
||||
|
||||
if (!appended) return
|
||||
if (!appended) { return }
|
||||
|
||||
infoMap.push({
|
||||
path: destination,
|
||||
@ -348,8 +348,11 @@ uploadsController.writeFilesToDb = async (req, res, user, albumid, infoMap) => {
|
||||
const fileHash = hash.digest('hex')
|
||||
const dbFile = await db.table('files')
|
||||
.where(function () {
|
||||
if (user === undefined) this.whereNull('userid')
|
||||
else this.where('userid', user.id)
|
||||
if (user === undefined) {
|
||||
this.whereNull('userid')
|
||||
} else {
|
||||
this.where('userid', user.id)
|
||||
}
|
||||
})
|
||||
.where({
|
||||
hash: fileHash,
|
||||
@ -433,7 +436,7 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
|
||||
|
||||
uploadsController.delete = async (req, res) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
const id = req.body.id
|
||||
if (id === undefined || id === '') {
|
||||
return res.json({ success: false, description: 'No file specified.' })
|
||||
@ -451,7 +454,7 @@ uploadsController.delete = async (req, res) => {
|
||||
try {
|
||||
await uploadsController.deleteFile(file.name).catch(err => {
|
||||
// ENOENT is missing file, for whatever reason, then just delete from db
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
if (err.code !== 'ENOENT') { throw err }
|
||||
})
|
||||
await db.table('files').where('id', id).del()
|
||||
if (file.albumid) {
|
||||
@ -494,18 +497,21 @@ uploadsController.deleteFile = function (file) {
|
||||
|
||||
uploadsController.list = async (req, res) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
if (!user) { return }
|
||||
|
||||
let offset = req.params.page
|
||||
if (offset === undefined) offset = 0
|
||||
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)
|
||||
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)
|
||||
if (user.username !== 'root') { this.where('userid', user.id) }
|
||||
})
|
||||
.orderBy('id', 'DESC')
|
||||
.limit(25)
|
||||
@ -546,10 +552,10 @@ uploadsController.list = async (req, res) => {
|
||||
}
|
||||
|
||||
// If we are a normal user, send response
|
||||
if (user.username !== 'root') return res.json({ success: true, files })
|
||||
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 (userids.length === 0) { return res.json({ success: true, files }) }
|
||||
|
||||
const users = await db.table('users').whereIn('id', userids)
|
||||
for (let dbUser of users) {
|
||||
|
@ -26,11 +26,11 @@ utilsController.getPrettyDate = function (date) {
|
||||
utilsController.getPrettyBytes = function (num) {
|
||||
// MIT License
|
||||
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
if (!Number.isFinite(num)) return num
|
||||
if (!Number.isFinite(num)) { return num }
|
||||
|
||||
const neg = num < 0
|
||||
if (neg) num = -num
|
||||
if (num < 1) return (neg ? '-' : '') + num + ' B'
|
||||
if (neg) { num = -num }
|
||||
if (num < 1) { return (neg ? '-' : '') + num + ' B' }
|
||||
|
||||
const exponent = Math.min(Math.floor(Math.log10(num) / 3), units.length - 1)
|
||||
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3))
|
||||
@ -47,7 +47,7 @@ utilsController.authorize = async (req, res) => {
|
||||
}
|
||||
|
||||
const user = await db.table('users').where('token', token).first()
|
||||
if (user) return user
|
||||
if (user) { return user }
|
||||
res.status(401).json({ success: false, description: 'Invalid token.' })
|
||||
}
|
||||
|
||||
@ -56,9 +56,9 @@ utilsController.generateThumbs = function (file, basedomain) {
|
||||
const isVideoExt = utilsController.videoExtensions.includes(ext)
|
||||
const isImageExt = utilsController.imageExtensions.includes(ext)
|
||||
|
||||
if (!isVideoExt && !isImageExt) return
|
||||
if (isVideoExt && config.uploads.generateThumbnails.video !== true) return
|
||||
if (isImageExt && config.uploads.generateThumbnails.image !== true) return
|
||||
if (!isVideoExt && !isImageExt) { return }
|
||||
if (isVideoExt && config.uploads.generateThumbnails.video !== true) { return }
|
||||
if (isImageExt && config.uploads.generateThumbnails.image !== true) { return }
|
||||
|
||||
let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png')
|
||||
fs.access(thumbname, err => {
|
||||
@ -83,7 +83,7 @@ utilsController.generateThumbs = function (file, basedomain) {
|
||||
.extent(size.width, size.height)
|
||||
.background('transparent')
|
||||
.write(thumbname, error => {
|
||||
if (error) console.log('Error - ', error)
|
||||
if (error) { console.log('Error - ', error) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -43,10 +43,10 @@ let init = function (db) {
|
||||
table.integer('timestamp')
|
||||
}).then(() => {
|
||||
db.table('users').where({username: 'root'}).then((user) => {
|
||||
if (user.length > 0) return
|
||||
if (user.length > 0) { return }
|
||||
|
||||
require('bcrypt').hash('root', 10, function (err, hash) {
|
||||
if (err) console.error('Error generating password hash for root')
|
||||
if (err) { console.error('Error generating password hash for root') }
|
||||
|
||||
db.table('users').insert({
|
||||
username: 'root',
|
||||
|
27
package.json
27
package.json
@ -35,12 +35,29 @@
|
||||
"sqlite3": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^11.0.1"
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"eslint-plugin-promise": "^3.7.0",
|
||||
"eslint-plugin-standard": "^3.0.1"
|
||||
},
|
||||
"standard": {
|
||||
"envs": [
|
||||
"browser",
|
||||
"node"
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"standard"
|
||||
],
|
||||
"rules": {
|
||||
"curly": [
|
||||
"error",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"**/*.min.js"
|
||||
]
|
||||
}
|
||||
|
@ -12,8 +12,8 @@
|
||||
<!-- Stylesheets and scripts -->
|
||||
<link rel="stylesheet" type="text/css" href="libs/bulma/bulma.min.css?v=K6t86DbYuR">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css?v=XcTZuW9fFV">
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=V2RnA3Mwhh"></script>
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="js/album.js?v=V2RnA3Mwhh"></script>
|
||||
|
||||
<!-- Open Graph tags -->
|
||||
|
@ -13,9 +13,9 @@
|
||||
<link rel="stylesheet" type="text/css" href="libs/bulma/bulma.min.css?v=K6t86DbYuR">
|
||||
<link rel="stylesheet" type="text/css" href="libs/fontello/fontello.css?v=V2RnA3Mwhh">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css?v=XcTZuW9fFV">
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=V2RnA3Mwhh"></script>
|
||||
<script type="text/javascript" src="js/auth.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="js/auth.js?v=8FbubjpYRC"></script>
|
||||
|
||||
<!-- Open Graph tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
|
@ -14,9 +14,9 @@
|
||||
<link rel="stylesheet" type="text/css" href="libs/fontello/fontello.css?v=V2RnA3Mwhh">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css?v=XcTZuW9fFV">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard.css?v=GBG98Tjuxh">
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=V2RnA3Mwhh"></script>
|
||||
<script type="text/javascript" src="js/dashboard.js?v=GBG98Tjuxh"></script>
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="js/dashboard.js?v=8FbubjpYRC"></script>
|
||||
|
||||
<!-- Open Graph tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
|
@ -12,10 +12,10 @@
|
||||
<!-- Stylesheets and scripts -->
|
||||
<link rel="stylesheet" type="text/css" href="libs/bulma/bulma.min.css?v=K6t86DbYuR">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css?v=XcTZuW9fFV">
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/dropzone/dropzone.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=V2RnA3Mwhh"></script>
|
||||
<script type="text/javascript" src="js/home.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="libs/dropzone/dropzone.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="js/home.js?v=8FbubjpYRC"></script>
|
||||
|
||||
<!-- Open Graph tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
|
@ -33,7 +33,7 @@ page.do = dest => {
|
||||
|
||||
page.onkeypress = function (event, element) {
|
||||
event = event || window.event
|
||||
if (!event) return
|
||||
if (!event) { return }
|
||||
if (event.keyCode === 13 || event.which === 13) {
|
||||
return this.do('login')
|
||||
}
|
||||
@ -41,7 +41,7 @@ page.onkeypress = function (event, element) {
|
||||
|
||||
page.verify = () => {
|
||||
page.token = localStorage.token
|
||||
if (page.token === undefined) return
|
||||
if (page.token === undefined) { return }
|
||||
|
||||
axios.post('api/tokens/verify', {
|
||||
token: page.token
|
||||
|
@ -89,16 +89,29 @@ panel.closeModal = () => {
|
||||
document.getElementById('modal').className = 'modal'
|
||||
}
|
||||
|
||||
panel.getUploads = (album, page) => {
|
||||
if (page === undefined) page = 0
|
||||
panel.isLoading = (element, state) => {
|
||||
if (!element) { return }
|
||||
if (state && !element.className.includes(' is-loading')) {
|
||||
element.className += ' is-loading'
|
||||
} else if (!state && element.className.includes(' is-loading')) {
|
||||
element.className = element.className.replace(' is-loading', '')
|
||||
}
|
||||
}
|
||||
|
||||
panel.getUploads = (album, page, element) => {
|
||||
if (element) { panel.isLoading(element, true) }
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
let prevPage = 0
|
||||
@ -106,23 +119,23 @@ panel.getUploads = (album, page) => {
|
||||
|
||||
if (response.data.files.length < 25) { nextPage = page }
|
||||
|
||||
if (page > 0) prevPage = page - 1
|
||||
if (page > 0) { prevPage = page - 1 }
|
||||
|
||||
const pagination = `
|
||||
<nav class="pagination is-centered">
|
||||
<a class="pagination-previous" onclick="panel.getUploads(${album}, ${prevPage})">Previous</a>
|
||||
<a class="pagination-next" onclick="panel.getUploads(${album}, ${nextPage})">Next page</a>
|
||||
<a class="button pagination-previous" onclick="panel.getUploads(${album}, ${prevPage}, this)">Previous</a>
|
||||
<a class="button pagination-next" onclick="panel.getUploads(${album}, ${nextPage}, this)">Next page</a>
|
||||
</nav>
|
||||
`
|
||||
const listType = `
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<a class="button is-small is-outlined is-danger" title="List view" onclick="panel.setFilesView('list', ${album}, ${page})">
|
||||
<a class="button is-small is-outlined is-danger" title="List view" onclick="panel.setFilesView('list', ${album}, ${page}, this)">
|
||||
<span class="icon is-small">
|
||||
<i class="fa icon-list-bullet"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-outlined is-danger" title="Thumbs view" onclick="panel.setFilesView('thumbs', ${album}, ${page})">
|
||||
<a class="button is-small is-outlined is-danger" title="Thumbs view" onclick="panel.setFilesView('thumbs', ${album}, ${page}, this)">
|
||||
<span class="icon is-small">
|
||||
<i class="fa icon-th-large"></i>
|
||||
</span>
|
||||
@ -242,10 +255,10 @@ panel.getUploads = (album, page) => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.setFilesView = (view, album, page) => {
|
||||
panel.setFilesView = (view, album, page, element) => {
|
||||
localStorage.filesView = view
|
||||
panel.filesView = view
|
||||
panel.getUploads(album, page)
|
||||
panel.getUploads(album, page, element)
|
||||
}
|
||||
|
||||
panel.deleteFile = (id, album, page) => {
|
||||
@ -262,14 +275,17 @@ panel.deleteFile = (id, album, page) => {
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (!value) return
|
||||
if (!value) { return }
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Deleted!', 'The file has been deleted.', 'success')
|
||||
@ -285,8 +301,11 @@ panel.deleteFile = (id, album, page) => {
|
||||
panel.getAlbums = () => {
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
panel.page.innerHTML = `
|
||||
@ -349,7 +368,7 @@ panel.getAlbums = () => {
|
||||
}
|
||||
|
||||
document.getElementById('submitAlbum').addEventListener('click', function () {
|
||||
panel.submitAlbum()
|
||||
panel.submitAlbum(this)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
@ -376,16 +395,14 @@ panel.renameAlbum = id => {
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (!value) return swal.close()
|
||||
if (!value) { return swal.close() }
|
||||
axios.post('api/albums/rename', {
|
||||
id: id,
|
||||
name: value
|
||||
})
|
||||
.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 occurred', response.data.description, 'error')
|
||||
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 occurred', response.data.description, 'error') }
|
||||
return
|
||||
}
|
||||
|
||||
@ -414,14 +431,17 @@ panel.deleteAlbum = id => {
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (!value) return
|
||||
if (!value) { return }
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success')
|
||||
@ -435,14 +455,19 @@ panel.deleteAlbum = id => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.submitAlbum = () => {
|
||||
panel.submitAlbum = element => {
|
||||
panel.isLoading(element, true)
|
||||
axios.post('api/albums', {
|
||||
name: document.getElementById('albumName').value
|
||||
})
|
||||
.then(response => {
|
||||
.then(async response => {
|
||||
panel.setLoading(element, false)
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') return panel.verifyToken(panel.token)
|
||||
else return swal('An error occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Woohoo!', 'Album was added successfully', 'success')
|
||||
@ -451,6 +476,7 @@ panel.submitAlbum = () => {
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
panel.setLoading(element, false)
|
||||
return swal('An error occurred', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
@ -459,14 +485,17 @@ panel.getAlbumsSidebar = () => {
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const albumsContainer = document.getElementById('albumsContainer')
|
||||
albumsContainer.innerHTML = ''
|
||||
|
||||
if (response.data.albums === undefined) return
|
||||
if (response.data.albums === undefined) { return }
|
||||
|
||||
for (const album of response.data.albums) {
|
||||
const li = document.createElement('li')
|
||||
@ -497,8 +526,11 @@ panel.changeFileLength = () => {
|
||||
axios.get('api/filelength/config')
|
||||
.then(response => {
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') return panel.verifyToken(panel.token)
|
||||
else return swal('An error occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
panel.page.innerHTML = `
|
||||
@ -519,7 +551,7 @@ panel.changeFileLength = () => {
|
||||
`
|
||||
|
||||
document.getElementById('setFileLength').addEventListener('click', function () {
|
||||
panel.setFileLength(document.getElementById('fileLength').value)
|
||||
panel.setFileLength(document.getElementById('fileLength').value, this)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
@ -528,12 +560,17 @@ panel.changeFileLength = () => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.setFileLength = fileLength => {
|
||||
panel.setFileLength = (fileLength, element) => {
|
||||
panel.isLoading(element, true)
|
||||
axios.post('api/filelength/change', { fileLength })
|
||||
.then(response => {
|
||||
panel.isLoading(element, false)
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') return panel.verifyToken(panel.token)
|
||||
else return swal('An error occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal({
|
||||
@ -546,6 +583,7 @@ panel.setFileLength = fileLength => {
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
panel.isLoading(element, false)
|
||||
return swal('An error occurred', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
@ -554,8 +592,11 @@ panel.changeToken = () => {
|
||||
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 occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
panel.page.innerHTML = `
|
||||
@ -575,7 +616,7 @@ panel.changeToken = () => {
|
||||
`
|
||||
|
||||
document.getElementById('getNewToken').addEventListener('click', function () {
|
||||
panel.getNewToken()
|
||||
panel.getNewToken(this)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
@ -584,12 +625,17 @@ panel.changeToken = () => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.getNewToken = () => {
|
||||
panel.getNewToken = element => {
|
||||
panel.isLoading(element, true)
|
||||
axios.post('api/tokens/change')
|
||||
.then(response => {
|
||||
panel.isLoading(element, false)
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') return panel.verifyToken(panel.token)
|
||||
else return swal('An error occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal({
|
||||
@ -603,6 +649,7 @@ panel.getNewToken = () => {
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
panel.isLoading(element, false)
|
||||
return swal('An error occurred', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
@ -632,7 +679,7 @@ panel.changePassword = () => {
|
||||
|
||||
document.getElementById('sendChangePassword').addEventListener('click', function () {
|
||||
if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) {
|
||||
panel.sendNewPassword(document.getElementById('password').value)
|
||||
panel.sendNewPassword(document.getElementById('password').value, this)
|
||||
} else {
|
||||
swal({
|
||||
title: 'Password mismatch!',
|
||||
@ -645,12 +692,17 @@ panel.changePassword = () => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.sendNewPassword = pass => {
|
||||
axios.post('api/password/change', {password: pass})
|
||||
panel.sendNewPassword = (pass, element) => {
|
||||
panel.isLoading(element, true)
|
||||
axios.post('api/password/change', { password: pass })
|
||||
.then(response => {
|
||||
panel.isLoading(element, false)
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') return panel.verifyToken(panel.token)
|
||||
else return swal('An error occurred', response.data.description, 'error')
|
||||
if (response.data.description === 'No token provided') {
|
||||
return panel.verifyToken(panel.token)
|
||||
} else {
|
||||
return swal('An error occurred', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal({
|
||||
@ -663,6 +715,7 @@ panel.sendNewPassword = pass => {
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
panel.isLoading(element, false)
|
||||
return swal('An error occurred', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ upload.prepareUpload = () => {
|
||||
|
||||
// If the user doesn't have any albums we don't really need to display
|
||||
// an album selection
|
||||
if (albums.length === 0) return
|
||||
if (albums.length === 0) { return }
|
||||
|
||||
// Loop through the albums and create an option for each album
|
||||
for (let i = 0; i < albums.length; i++) {
|
||||
@ -190,18 +190,18 @@ upload.prepareDropzone = () => {
|
||||
|
||||
// Add the selected albumid, if an album is selected, as a header
|
||||
upload.dropzone.on('sending', (file, xhr, formData) => {
|
||||
if (upload.album) xhr.setRequestHeader('albumid', upload.album)
|
||||
if (upload.album) { xhr.setRequestHeader('albumid', upload.album) }
|
||||
})
|
||||
|
||||
// Update the total progress bar
|
||||
upload.dropzone.on('uploadprogress', (file, progress, bytesSent) => {
|
||||
if (file.upload.chunked && progress === 100) return
|
||||
if (file.upload.chunked && progress === 100) { return }
|
||||
file.previewElement.querySelector('.progress').setAttribute('value', progress)
|
||||
file.previewElement.querySelector('.progress').innerHTML = `${progress}%`
|
||||
})
|
||||
|
||||
upload.dropzone.on('success', (file, response) => {
|
||||
if (!response) return
|
||||
if (!response) { return }
|
||||
file.previewTemplate.querySelector('.progress').style.display = 'none'
|
||||
|
||||
if (response.success === false) {
|
||||
|
4
public/libs/axios/axios.min.js
vendored
4
public/libs/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
2
public/libs/dropzone/dropzone.min.js
vendored
2
public/libs/dropzone/dropzone.min.js
vendored
File diff suppressed because one or more lines are too long
3
public/libs/sweetalert/sweetalert.min.js
vendored
3
public/libs/sweetalert/sweetalert.min.js
vendored
File diff suppressed because one or more lines are too long
@ -12,8 +12,8 @@
|
||||
<!-- Stylesheets and scripts -->
|
||||
<link rel="stylesheet" type="text/css" href="../libs/bulma/bulma.min.css?v=K6t86DbYuR">
|
||||
<link rel="stylesheet" type="text/css" href="../css/style.css?v=XcTZuW9fFV">
|
||||
<script type="text/javascript" src="../libs/sweetalert/sweetalert.min.js?v=K6t86DbYuR"></script>
|
||||
<script type="text/javascript" src="../libs/axios/axios.min.js?v=V2RnA3Mwhh"></script>
|
||||
<script type="text/javascript" src="../libs/sweetalert/sweetalert.min.js?v=8FbubjpYRC"></script>
|
||||
<script type="text/javascript" src="../libs/axios/axios.min.js?v=8FbubjpYRC"></script>
|
||||
|
||||
<!-- Open Graph tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
|
328
yarn.lock
328
yarn.lock
@ -23,9 +23,9 @@ acorn@^3.0.4:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
||||
|
||||
acorn@^5.4.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.0.tgz#1abb587fbf051f94e3de20e6b26ef910b1828298"
|
||||
acorn@^5.5.0:
|
||||
version "5.5.3"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
|
||||
|
||||
ajv-keywords@^2.1.0:
|
||||
version "2.1.1"
|
||||
@ -62,8 +62,8 @@ amdefine@>=0.0.4:
|
||||
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"
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
@ -77,12 +77,18 @@ ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
||||
ansi-styles@^3.1.0, ansi-styles@^3.2.0:
|
||||
ansi-styles@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
append-field@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a"
|
||||
@ -124,13 +130,6 @@ array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
|
||||
array-includes@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.7.0"
|
||||
|
||||
array-parallel@~0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d"
|
||||
@ -312,6 +311,10 @@ braces@^2.3.1:
|
||||
split-string "^3.0.2"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
|
||||
|
||||
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
@ -389,12 +392,12 @@ chalk@^1.1.3:
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.1.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.2.0"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chardet@^0.4.0:
|
||||
version "0.4.2"
|
||||
@ -491,9 +494,10 @@ concat-stream@^1.5.0:
|
||||
typedarray "^0.0.6"
|
||||
|
||||
concat-stream@^1.6.0:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
@ -530,10 +534,6 @@ copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
core-js@^2.4.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
|
||||
@ -577,10 +577,6 @@ dasherize@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
|
||||
|
||||
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.2, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@ -647,17 +643,6 @@ define-property@^2.0.2:
|
||||
is-descriptor "^1.0.2"
|
||||
isobject "^3.0.1"
|
||||
|
||||
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"
|
||||
@ -716,7 +701,7 @@ doctrine@1.5.0:
|
||||
esutils "^2.0.2"
|
||||
isarray "^1.0.0"
|
||||
|
||||
doctrine@^2.0.2, doctrine@^2.1.0:
|
||||
doctrine@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
|
||||
dependencies:
|
||||
@ -740,36 +725,12 @@ encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
error-ex@^1.2.0:
|
||||
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"
|
||||
|
||||
es6-promise@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6"
|
||||
@ -782,11 +743,7 @@ 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-standard-jsx@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-5.0.0.tgz#4abfac554f38668e0078c664569e7b2384e5d2aa"
|
||||
|
||||
eslint-config-standard@11.0.0:
|
||||
eslint-config-standard@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz#87ee0d3c9d95382dc761958cbb23da9eea31e0ba"
|
||||
|
||||
@ -804,7 +761,7 @@ eslint-module-utils@^2.1.1:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^1.0.0"
|
||||
|
||||
eslint-plugin-import@~2.9.0:
|
||||
eslint-plugin-import@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz#26002efbfca5989b7288ac047508bd24f217b169"
|
||||
dependencies:
|
||||
@ -819,7 +776,7 @@ eslint-plugin-import@~2.9.0:
|
||||
minimatch "^3.0.3"
|
||||
read-pkg-up "^2.0.0"
|
||||
|
||||
eslint-plugin-node@~6.0.0:
|
||||
eslint-plugin-node@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz#bf19642298064379315d7a4b2a75937376fa05e4"
|
||||
dependencies:
|
||||
@ -828,20 +785,11 @@ eslint-plugin-node@~6.0.0:
|
||||
resolve "^1.3.3"
|
||||
semver "^5.4.1"
|
||||
|
||||
eslint-plugin-promise@~3.7.0:
|
||||
eslint-plugin-promise@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz#f4bde5c2c77cdd69557a8f69a24d1ad3cfc9e67e"
|
||||
|
||||
eslint-plugin-react@~7.7.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz#f606c719dbd8a1a2b3d25c16299813878cca0160"
|
||||
dependencies:
|
||||
doctrine "^2.0.2"
|
||||
has "^1.0.1"
|
||||
jsx-ast-utils "^2.0.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
eslint-plugin-standard@~3.0.1:
|
||||
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"
|
||||
|
||||
@ -856,9 +804,9 @@ eslint-visitor-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||
|
||||
eslint@~4.18.0:
|
||||
version "4.18.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.2.tgz#0f81267ad1012e7d2051e186a9004cc2267b8d45"
|
||||
eslint@^4.19.1:
|
||||
version "4.19.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
|
||||
dependencies:
|
||||
ajv "^5.3.0"
|
||||
babel-code-frame "^6.22.0"
|
||||
@ -869,7 +817,7 @@ eslint@~4.18.0:
|
||||
doctrine "^2.1.0"
|
||||
eslint-scope "^3.7.1"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
espree "^3.5.2"
|
||||
espree "^3.5.4"
|
||||
esquery "^1.0.0"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^2.0.0"
|
||||
@ -891,6 +839,7 @@ eslint@~4.18.0:
|
||||
path-is-inside "^1.0.2"
|
||||
pluralize "^7.0.0"
|
||||
progress "^2.0.0"
|
||||
regexpp "^1.0.1"
|
||||
require-uncached "^1.0.3"
|
||||
semver "^5.3.0"
|
||||
strip-ansi "^4.0.0"
|
||||
@ -898,11 +847,11 @@ eslint@~4.18.0:
|
||||
table "4.0.2"
|
||||
text-table "~0.2.0"
|
||||
|
||||
espree@^3.5.2:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.3.tgz#931e0af64e7fbbed26b050a29daad1fc64799fa6"
|
||||
espree@^3.5.4:
|
||||
version "3.5.4"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
|
||||
dependencies:
|
||||
acorn "^5.4.0"
|
||||
acorn "^5.5.0"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
esprima@^4.0.0:
|
||||
@ -1060,18 +1009,6 @@ fast-levenshtein@~2.0.4:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
|
||||
fbjs@^0.8.16:
|
||||
version "0.8.16"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
isomorphic-fetch "^2.1.1"
|
||||
loose-envify "^1.0.0"
|
||||
object-assign "^4.1.0"
|
||||
promise "^7.1.1"
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.9"
|
||||
|
||||
figures@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
|
||||
@ -1106,10 +1043,6 @@ finalhandler@1.1.1:
|
||||
statuses "~1.4.0"
|
||||
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"
|
||||
@ -1233,7 +1166,7 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
|
||||
mkdirp ">=0.5 0"
|
||||
rimraf "2"
|
||||
|
||||
function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
|
||||
function-bind@^1.0.2, function-bind@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
|
||||
@ -1254,10 +1187,6 @@ gauge@~2.7.3:
|
||||
strip-ansi "^3.0.1"
|
||||
wide-align "^1.1.0"
|
||||
|
||||
get-stdin@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
|
||||
|
||||
get-value@^2.0.3, get-value@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
@ -1308,8 +1237,8 @@ global-prefix@^1.0.1:
|
||||
which "^1.2.14"
|
||||
|
||||
globals@^11.0.1:
|
||||
version "11.3.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0"
|
||||
version "11.4.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.4.0.tgz#b85c793349561c16076a3c13549238a27945f1bc"
|
||||
|
||||
globby@^5.0.0:
|
||||
version "5.0.0"
|
||||
@ -1486,7 +1415,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.4, iconv-lite@~0.4.13:
|
||||
iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.4:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
|
||||
@ -1500,7 +1429,7 @@ ignore-walk@^3.0.1:
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
ignore@^3.0.9, ignore@^3.3.3, ignore@^3.3.6:
|
||||
ignore@^3.3.3, ignore@^3.3.6:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
|
||||
|
||||
@ -1587,10 +1516,6 @@ is-builtin-module@^1.0.0:
|
||||
dependencies:
|
||||
builtin-modules "^1.0.0"
|
||||
|
||||
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-data-descriptor@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
||||
@ -1603,10 +1528,6 @@ is-data-descriptor@^1.0.0:
|
||||
dependencies:
|
||||
kind-of "^6.0.0"
|
||||
|
||||
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-descriptor@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||
@ -1674,13 +1595,12 @@ is-path-cwd@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
|
||||
|
||||
is-path-in-cwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
|
||||
dependencies:
|
||||
is-path-inside "^1.0.0"
|
||||
|
||||
is-path-inside@^1.0.0, is-symbol@^1.0.1:
|
||||
name is-path-inside
|
||||
is-path-inside@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
|
||||
dependencies:
|
||||
@ -1696,12 +1616,6 @@ is-promise@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
|
||||
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
|
||||
@ -1712,10 +1626,6 @@ is-resolvable@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
|
||||
|
||||
is-stream@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
@ -1756,24 +1666,17 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
||||
js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||
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:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
|
||||
version "3.11.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
@ -1782,10 +1685,6 @@ 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"
|
||||
@ -1821,12 +1720,6 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jsx-ast-utils@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f"
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
|
||||
jszip@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37"
|
||||
@ -1925,15 +1818,6 @@ load-json-file@^2.0.0:
|
||||
pify "^2.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
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"
|
||||
@ -1957,12 +1841,6 @@ longest@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||
dependencies:
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
lru-cache@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
|
||||
@ -2054,7 +1932,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.1.0, minimist@^1.2.0:
|
||||
minimist@1.2.0, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
@ -2153,13 +2031,6 @@ nocache@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-pre-gyp@0.6.36:
|
||||
version "0.6.36"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
|
||||
@ -2237,7 +2108,7 @@ object-assign@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
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"
|
||||
|
||||
@ -2374,13 +2245,6 @@ parse-json@^2.2.0:
|
||||
dependencies:
|
||||
error-ex "^1.2.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"
|
||||
@ -2447,10 +2311,6 @@ 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"
|
||||
@ -2461,21 +2321,6 @@ 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"
|
||||
@ -2506,20 +2351,12 @@ progress@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
|
||||
|
||||
promise@^7.0.0, promise@^7.1.1:
|
||||
promise@^7.0.0:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.6.0:
|
||||
version "15.6.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
proxy-addr@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||
@ -2635,6 +2472,10 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
extend-shallow "^3.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
regexpp@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.0.1.tgz#d857c3a741dce075c2848dcb019a0a975b190d43"
|
||||
|
||||
repeat-element@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
|
||||
@ -2699,8 +2540,8 @@ resolve@^1.1.6, resolve@^1.1.7:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
resolve@^1.3.3, resolve@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c"
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
@ -2733,10 +2574,6 @@ run-async@^2.2.0:
|
||||
dependencies:
|
||||
is-promise "^2.1.0"
|
||||
|
||||
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-aggregates@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
||||
@ -2824,10 +2661,6 @@ set-value@^2.0.0:
|
||||
is-plain-object "^2.0.3"
|
||||
split-string "^3.0.1"
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
|
||||
setprototypeof@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
|
||||
@ -2966,29 +2799,6 @@ sshpk@^1.7.0:
|
||||
jsbn "~0.1.0"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
standard-engine@~8.0.0:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-8.0.1.tgz#0b77be8d7ab963675717dbeac1ef1d6675fb62f0"
|
||||
dependencies:
|
||||
deglob "^2.1.0"
|
||||
get-stdin "^6.0.0"
|
||||
minimist "^1.1.0"
|
||||
pkg-conf "^2.0.0"
|
||||
|
||||
standard@^11.0.1:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/standard/-/standard-11.0.1.tgz#49be40c76f1d564964b22bbf7309929ad0335e29"
|
||||
dependencies:
|
||||
eslint "~4.18.0"
|
||||
eslint-config-standard "11.0.0"
|
||||
eslint-config-standard-jsx "5.0.0"
|
||||
eslint-plugin-import "~2.9.0"
|
||||
eslint-plugin-node "~6.0.0"
|
||||
eslint-plugin-promise "~3.7.0"
|
||||
eslint-plugin-react "~7.7.0"
|
||||
eslint-plugin-standard "~3.0.1"
|
||||
standard-engine "~8.0.0"
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
@ -3067,9 +2877,9 @@ supports-color@^4.0.0:
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
|
||||
supports-color@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
@ -3202,10 +3012,6 @@ typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
ua-parser-js@^0.7.9:
|
||||
version "0.7.17"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
||||
|
||||
uglify-js@^2.6:
|
||||
version "2.8.29"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
||||
@ -3236,10 +3042,6 @@ union-value@^1.0.0:
|
||||
is-extendable "^0.1.1"
|
||||
set-value "^0.4.3"
|
||||
|
||||
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"
|
||||
@ -3304,10 +3106,6 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
whatwg-fetch@>=0.10.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
||||
|
||||
which@^1.1.1, which@^1.2.14, which@^1.2.9:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
|
||||
@ -3350,7 +3148,7 @@ x-xss-protection@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.1.0.tgz#4f1898c332deb1e7f2be1280efb3e2c53d69c1a7"
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1:
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user