Updated ESLint rule: curly, again.
Mainly to also enabled "consistent" rule, which enforces curly into
else/elseif blocks, if its if block requires curly.

Added support for GET requests to /api/delete route.
Its usage is /api/delete/identifier, where identifier is the filename.
Though just like its POST route, it needs token in the header.
This commit is contained in:
Bobby Wibowo 2018-12-19 00:41:42 +07:00
parent 52d336cc45
commit 00cbd3e76c
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
11 changed files with 129 additions and 124 deletions

View File

@ -12,7 +12,8 @@
"rules": {
"curly": [
"error",
"multi"
"multi",
"consistent"
],
"prefer-const": [
"error",

View File

@ -143,8 +143,9 @@ uploadsController.upload = async (req, res, next) => {
if (config.private === true) {
user = await utils.authorize(req, res)
if (!user) return
} else if (req.headers.token)
} else if (req.headers.token) {
user = await db.table('users').where('token', req.headers.token).first()
}
if (user && (user.enabled === false || user.enabled === 0))
return res.json({ success: false, description: 'This account has been disabled.' })
@ -289,7 +290,9 @@ uploadsController.actuallyUploadByUrl = async (req, res, user, albumid) => {
uploadsController.processFilesForDisplay(req, res, result.files, result.existingFiles)
}
})
} catch (error) { erred(error) }
} catch (error) {
erred(error)
}
}
}
@ -301,8 +304,9 @@ uploadsController.finishChunks = async (req, res, next) => {
if (config.private === true) {
user = await utils.authorize(req, res)
if (!user) return
} else if (req.headers.token)
} else if (req.headers.token) {
user = await db.table('users').where('token', req.headers.token).first()
}
if (user && (user.enabled === false || user.enabled === 0))
return res.json({ success: false, description: 'This account has been disabled.' })
@ -624,10 +628,11 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
}
uploadsController.delete = async (req, res) => {
const id = parseInt(req.body.id)
const id = parseInt(req.body.id) || parseInt(req.params.identifier)
req.body.field = 'id'
req.body.values = isNaN(id) ? undefined : [id]
if (req.body.id !== undefined) delete req.body.id
delete req.body.id
delete req.params.identifier
return uploadsController.bulkDelete(req, res)
}

View File

@ -3,7 +3,7 @@ const perms = require('./../controllers/permissionController')
const init = function (db) {
// Create the tables we need to store galleries and files
db.schema.hasTable('albums').then(exists => {
if (!exists) {
if (exists) return
db.schema.createTable('albums', function (table) {
table.increments()
table.integer('userid')
@ -17,11 +17,10 @@ const init = function (db) {
table.integer('public')
table.string('description')
}).then(() => {})
}
})
db.schema.hasTable('files').then(exists => {
if (!exists) {
if (exists) return
db.schema.createTable('files', function (table) {
table.increments()
table.integer('userid')
@ -34,11 +33,10 @@ const init = function (db) {
table.integer('albumid')
table.integer('timestamp')
}).then(() => {})
}
})
db.schema.hasTable('users').then(exists => {
if (!exists) {
if (exists) return
db.schema.createTable('users', function (table) {
table.increments()
table.string('username')
@ -50,11 +48,9 @@ const init = function (db) {
table.integer('permission')
}).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 (error, hash) {
if (error) { console.error('Error generating password hash for root') }
if (error) console.error('Error generating password hash for root')
db.table('users').insert({
username: 'root',
password: hash,
@ -65,7 +61,6 @@ const init = function (db) {
})
})
})
}
})
}

View File

@ -23,9 +23,9 @@ migration.start = async () => {
await Promise.all(tables.map(table => {
const columns = Object.keys(map[table])
return Promise.all(columns.map(async column => {
if (await db.schema.hasColumn(table, column)) {
if (await db.schema.hasColumn(table, column))
return // console.log(`SKIP: ${column} => ${table}.`)
}
const columnType = map[table][column]
return db.schema.table(table, t => { t[columnType](column) })
.then(() => console.log(`OK: ${column} (${columnType}) => ${table}.`))
@ -42,7 +42,7 @@ migration.start = async () => {
.then(rows => {
// NOTE: permissionController.js actually have a hard-coded check for "root" account so that
// it will always have "superadmin" permission regardless of its permission value in database
if (!rows) { return console.log('Unable to update root\'s permission into superadmin.') }
if (!rows) return console.log('Unable to update root\'s permission into superadmin.')
console.log(`Updated root's permission to ${perms.permissions.superadmin} (superadmin).`)
})

View File

@ -56,16 +56,15 @@ const setHeaders = res => {
res.set('Cache-Control', 'public, max-age=2592000, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800') // max-age: 30 days
}
if (config.serveFilesWithNode) {
if (config.serveFilesWithNode)
safe.use('/', express.static(config.uploads.folder, { setHeaders }))
}
safe.use('/', express.static('./public', { setHeaders }))
safe.use('/', album)
safe.use('/', nojs)
safe.use('/api', api)
for (const page of config.pages) {
for (const page of config.pages)
if (fs.existsSync(`./pages/custom/${page}.html`)) {
safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, {
root: './pages/custom/'
@ -90,7 +89,6 @@ for (const page of config.pages) {
} else {
safe.get(`/${page}`, (req, res, next) => res.render(page))
}
}
safe.use((req, res, next) => {
res.status(404).sendFile(config.errorPages[404], { root: config.errorPages.rootDir })
@ -109,31 +107,31 @@ const start = async () => {
if (config.showGitHash) {
const gitHash = await new Promise((resolve, reject) => {
require('child_process').exec('git rev-parse HEAD', (error, stdout) => {
if (error) { return reject(error) }
if (error) return reject(error)
resolve(stdout.replace(/\n$/, ''))
})
}).catch(console.error)
if (!gitHash) { return }
if (!gitHash) return
console.log(`Git commit: ${gitHash}`)
safe.set('git-hash', gitHash)
}
if (config.uploads.scan && config.uploads.scan.enabled) {
const created = await new Promise(async (resolve, reject) => {
if (!config.uploads.scan.ip || !config.uploads.scan.port) {
if (!config.uploads.scan.ip || !config.uploads.scan.port)
return reject(new Error('clamd IP or port is missing'))
}
const ping = await clamd.ping(config.uploads.scan.ip, config.uploads.scan.port).catch(reject)
if (!ping) {
if (!ping)
return reject(new Error('Could not ping clamd'))
}
const version = await clamd.version(config.uploads.scan.ip, config.uploads.scan.port).catch(reject)
console.log(`${config.uploads.scan.ip}:${config.uploads.scan.port} ${version}`)
const scanner = clamd.createScanner(config.uploads.scan.ip, config.uploads.scan.port)
safe.set('clam-scanner', scanner)
return resolve(true)
}).catch(error => console.error(error.toString()))
if (!created) { return process.exit(1) }
if (!created) return process.exit(1)
}
if (config.uploads.cacheFileIdentifiers) {
@ -142,24 +140,21 @@ const start = async () => {
const setSize = await new Promise((resolve, reject) => {
const uploadsDir = `./${config.uploads.folder}`
fs.readdir(uploadsDir, (error, names) => {
if (error) { return reject(error) }
if (error) return reject(error)
const set = new Set()
names.forEach(name => set.add(name.split('.')[0]))
safe.set('uploads-set', set)
resolve(set.size)
})
}).catch(error => console.error(error.toString()))
if (!setSize) { return process.exit(1) }
if (!setSize) return process.exit(1)
process.stdout.write(` ${setSize} OK!\n`)
}
safe.listen(config.port, () => {
console.log(`lolisafe started on port ${config.port}`)
if (process.env.DEV === '1') {
// DEV=1 yarn start
console.log('lolisafe is in development mode, nunjucks caching disabled')
}
if (process.env.DEV === '1') {
// Add readline interface to allow evaluating arbitrary JavaScript from console
readline.createInterface({
input: process.stdin,
@ -167,7 +162,7 @@ const start = async () => {
prompt: ''
}).on('line', line => {
try {
if (line === '.exit') { process.exit(0) }
if (line === '.exit') process.exit(0)
// eslint-disable-next-line no-eval
process.stdout.write(`${require('util').inspect(eval(line), { depth: 0 })}\n`)
} catch (error) {
@ -176,6 +171,8 @@ const start = async () => {
}).on('SIGINT', () => {
process.exit(0)
})
console.warn('development mode enabled (disabled nunjucks caching & enabled readline interface)')
}
})
}

View File

@ -12,7 +12,8 @@
"rules": {
"curly": [
"error",
"multi"
"multi",
"consistent"
],
"quotes": [
"error",

View File

@ -209,8 +209,9 @@ page.domClick = function (event) {
views.album = page.views.uploads.album
views.all = page.views.uploads.all
func = page.getUploads
} else if (page.currentView === 'users')
} else if (page.currentView === 'users') {
func = page.getUsers
}
switch (action) {
case 'page-prev':
@ -785,8 +786,9 @@ page.deleteSelectedFiles = function () {
page.selected.uploads = page.selected.uploads.filter(function (id) {
return bulkdelete.data.failed.includes(id)
})
} else
} else {
page.selected.uploads = []
}
localStorage[LS_KEYS.selected.uploads] = JSON.stringify(page.selected.uploads)
@ -1801,10 +1803,11 @@ page.editUser = function (id) {
icon: 'success',
content: div
})
} else if (response.data.name !== user.name)
} else if (response.data.name !== user.name) {
swal('Success!', `${user.username} was renamed into: ${response.data.name}.`, 'success')
else
} else {
swal('Success!', 'The user was edited!', 'success')
}
page.getUsers(page.views.users)
}).catch(function (error) {
@ -1829,18 +1832,17 @@ page.disableUser = function (id) {
}
}
}).then(function (proceed) {
if (!proceed) { return }
if (!proceed) return
axios.post('api/users/disable', { id }).then(function (response) {
if (!response) { return }
if (!response) return
if (response.data.success === false) {
if (response.data.description === 'No token provided') {
if (response.data.description === 'No token provided')
return page.verifyToken(page.token)
} else {
else
return swal('An error occurred!', response.data.description, 'error')
}
}
swal('Success!', 'The user has been disabled.', 'success')
page.getUsers(page.views.users)

View File

@ -121,8 +121,9 @@ page.prepareUpload = function () {
page.uploadUrls(this)
})
page.setActiveTab('tab-files')
} else
} else {
document.getElementById('tab-files').style.display = 'block'
}
}
page.prepareAlbums = function () {
@ -296,7 +297,9 @@ page.uploadUrls = function (button) {
const previewsContainer = tabDiv.getElementsByClassName('uploads')[0]
const urls = document.getElementById('urls').value
.split(/\r?\n/)
.filter(function (url) { return url.trim().length })
.filter(function (url) {
return url.trim().length
})
document.getElementById('urls').value = urls.join('\n')
if (!urls.length)
@ -365,7 +368,11 @@ page.updateTemplate = function (file, response) {
const img = file.previewElement.querySelector('img')
img.setAttribute('alt', response.name || '')
img.dataset['src'] = response.url
img.onerror = function () { this.style.display = 'none' } // hide webp in firefox and ie
img.onerror = function () {
// Hide images that failed to load
// Consequently also WEBP in browsers that do not have WEBP support (Firefox/IE)
this.style.display = 'none'
}
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
}
}

View File

@ -8,12 +8,11 @@ const homeDomain = config.homeDomain || config.domain
routes.get('/a/:identifier', async (req, res, next) => {
const identifier = req.params.identifier
if (identifier === undefined) {
if (identifier === undefined)
return res.status(401).json({
success: false,
description: 'No identifier provided.'
})
}
const album = await db.table('albums')
.where({
@ -22,14 +21,13 @@ routes.get('/a/:identifier', async (req, res, next) => {
})
.first()
if (!album) {
if (!album)
return res.status(404).sendFile('404.html', { root: './pages/error/' })
} else if (album.public === 0) {
else if (album.public === 0)
return res.status(401).json({
success: false,
description: 'This album is not available for public.'
})
}
const files = await db.table('files')
.select('name', 'size')
@ -42,19 +40,17 @@ routes.get('/a/:identifier', async (req, res, next) => {
let totalSize = 0
for (const file of files) {
file.file = `${basedomain}/${file.name}`
totalSize += parseInt(file.size)
file.extname = path.extname(file.name).toLowerCase()
if (utils.mayGenerateThumb(file.extname)) {
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -file.extname.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 (thumb === '') { thumb = file.thumb }
if (thumb === '') thumb = file.thumb
}
totalSize += parseInt(file.size)
}
return res.render('album', {

View File

@ -21,6 +21,7 @@ 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.get('/upload/delete/:identifier', (req, res, next) => uploadController.delete(req, res, next))
routes.post('/upload/bulkdelete', (req, res, next) => uploadController.bulkDelete(req, res, next))
routes.post('/upload/finishchunks', (req, res, next) => uploadController.finishChunks(req, res, next))
routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next))

View File

@ -15,13 +15,13 @@ thumbs.mayGenerateThumb = extname => {
thumbs.getFiles = directory => {
return new Promise((resolve, reject) => {
fs.readdir(directory, async (error, names) => {
if (error) { return reject(error) }
if (error) return reject(error)
const files = []
await Promise.all(names.map(name => {
return new Promise((resolve, reject) => {
fs.lstat(path.join(directory, name), (error, stats) => {
if (error) { return reject(error) }
if (stats.isFile() && !name.startsWith('.')) { files.push(name) }
if (error) return reject(error)
if (stats.isFile() && !name.startsWith('.')) files.push(name)
resolve()
})
})
@ -62,16 +62,16 @@ thumbs.do = async () => {
await new Promise((resolve, reject) => {
const generate = async i => {
const _upload = _uploads[i]
if (!_upload) { return resolve() }
if (!_upload) return resolve()
const extname = path.extname(_upload)
const basename = _upload.slice(0, -extname.length)
if (_thumbs.includes(basename) && !thumbs.force) {
if (thumbs.verbose) { console.log(`${_upload}: thumb exists.`) }
if (thumbs.verbose) console.log(`${_upload}: thumb exists.`)
skipped++
} else if (!thumbs.mayGenerateThumb(extname)) {
if (thumbs.verbose) { console.log(`${_upload}: extension skipped.`) }
if (thumbs.verbose) console.log(`${_upload}: extension skipped.`)
skipped++
} else {
const generated = await utils.generateThumbs(_upload, thumbs.force)