mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Updates
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:
parent
52d336cc45
commit
00cbd3e76c
@ -12,7 +12,8 @@
|
||||
"rules": {
|
||||
"curly": [
|
||||
"error",
|
||||
"multi"
|
||||
"multi",
|
||||
"consistent"
|
||||
],
|
||||
"prefer-const": [
|
||||
"error",
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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).`)
|
||||
})
|
||||
|
||||
|
33
lolisafe.js
33
lolisafe.js
@ -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)')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
"rules": {
|
||||
"curly": [
|
||||
"error",
|
||||
"multi"
|
||||
"multi",
|
||||
"consistent"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
}
|
||||
}
|
||||
|
@ -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', {
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user