mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2024-12-12 23:46:22 +00:00
Updates
config.sample.js + uploadController.js: + Added option uploads > storeIP to toggle whether to store uploader's IPs into the database. uploadController.js + dashboard.js: + Added IP column when listing all uploads. + Improved album query when listing uploads. In addition, no longer query album when listing all uploads. + Delegate some tasks to client when listing uploads to save server's processing power, kek. Such as building the file's full URLs, and assigning album/user names. _globals.njk: + Bumped v1 version string.
This commit is contained in:
parent
4bee2ef376
commit
f48cbd1960
@ -207,6 +207,13 @@ module.exports = {
|
||||
chunkSize: 64 * 1024
|
||||
},
|
||||
|
||||
/*
|
||||
Store uploader's IPs into the database.
|
||||
NOTE: Dashboard's Manage Uploads will display IP column regardless of whether
|
||||
this is set to true or false.
|
||||
*/
|
||||
storeIP: true,
|
||||
|
||||
/*
|
||||
Chunk size for chunk uploads. Needs to be in MB.
|
||||
If this is enabled, every files uploaded from the homepage uploader will forcibly be chunked
|
||||
|
@ -545,7 +545,7 @@ uploadsController.formatInfoMap = (req, res, user, infoMap) => {
|
||||
type: info.data.mimetype,
|
||||
size: info.data.size,
|
||||
hash: fileHash,
|
||||
ip: req.ip,
|
||||
ip: config.uploads.storeIP !== false ? req.ip : null, // only disable if explicitly set to false
|
||||
albumid: albumsAuthorized[info.data.albumid] ? info.data.albumid : null,
|
||||
userid: user !== undefined ? user.id : null,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
@ -688,25 +688,33 @@ uploadsController.list = async (req, res) => {
|
||||
const ismoderator = perms.is(user, 'moderator')
|
||||
if ((all || uploader) && !ismoderator) return res.status(403).end()
|
||||
|
||||
const basedomain = config.domain
|
||||
|
||||
// For filtering by uploader's username
|
||||
let uploaderID = null
|
||||
if (uploader)
|
||||
if (uploader) {
|
||||
uploaderID = await db.table('users')
|
||||
.where('username', uploader)
|
||||
.select('id')
|
||||
.first()
|
||||
.then(row => row ? row.id : null)
|
||||
// Close request if the provided username is not valid
|
||||
if (!uploaderID)
|
||||
return res.json({ success: false, description: 'User with that username could not be found.' })
|
||||
}
|
||||
|
||||
function filter () {
|
||||
if (req.params.id === undefined)
|
||||
this.where('id', '<>', '')
|
||||
this.where('id', '<>', '') // TODO: Why is this necessary?
|
||||
else
|
||||
this.where('albumid', req.params.id)
|
||||
if (!ismoderator || !all)
|
||||
if (!all)
|
||||
this.where('userid', user.id)
|
||||
else if (uploaderID)
|
||||
this.where('userid', uploaderID)
|
||||
}
|
||||
|
||||
// Query uploads count for pagination
|
||||
const count = await db.table('files')
|
||||
.where(filter)
|
||||
.count('id as count')
|
||||
@ -716,55 +724,73 @@ uploadsController.list = async (req, res) => {
|
||||
let offset = req.params.page
|
||||
if (offset === undefined) offset = 0
|
||||
|
||||
const columns = ['id', 'timestamp', 'name', 'userid', 'size']
|
||||
// Only select IPs if we are listing all uploads
|
||||
columns.push(all ? 'ip' : 'albumid')
|
||||
|
||||
const files = await db.table('files')
|
||||
.where(filter)
|
||||
.orderBy('id', 'DESC')
|
||||
.limit(25)
|
||||
.offset(25 * offset)
|
||||
.select('id', 'albumid', 'timestamp', 'name', 'userid', 'size')
|
||||
|
||||
const albums = await db.table('albums')
|
||||
.where(function () {
|
||||
this.where('enabled', 1)
|
||||
if (!all || !ismoderator)
|
||||
this.where('userid', user.id)
|
||||
})
|
||||
|
||||
const basedomain = config.domain
|
||||
const userids = []
|
||||
.select(columns)
|
||||
|
||||
for (const file of files) {
|
||||
file.file = `${basedomain}/${file.name}`
|
||||
|
||||
file.album = ''
|
||||
if (file.albumid !== undefined)
|
||||
for (const album of albums)
|
||||
if (file.albumid === album.id)
|
||||
file.album = album.name
|
||||
|
||||
// Only push usernames if we are a moderator
|
||||
if (all && ismoderator)
|
||||
if (file.userid !== undefined && file.userid !== null && file.userid !== '')
|
||||
userids.push(file.userid)
|
||||
|
||||
file.extname = utils.extname(file.name)
|
||||
if (utils.mayGenerateThumb(file.extname))
|
||||
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -file.extname.length)}.png`
|
||||
file.thumb = `/thumbs/${file.name.slice(0, -file.extname.length)}.png`
|
||||
}
|
||||
|
||||
// If we are a normal user, send response
|
||||
if (!ismoderator) return res.json({ success: true, files, count })
|
||||
// If we are not listing all uploads, query album names
|
||||
let albums = {}
|
||||
if (!all) {
|
||||
const albumids = files
|
||||
.map(file => file.albumid)
|
||||
.filter((v, i, a) => {
|
||||
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
|
||||
})
|
||||
albums = await db.table('albums')
|
||||
.whereIn('id', albumids)
|
||||
.where('enabled', 1)
|
||||
.where('userid', user.id)
|
||||
.select('id', 'name')
|
||||
.then(rows => {
|
||||
// Build Object indexed by their IDs
|
||||
const obj = {}
|
||||
for (const row of rows) obj[row.id] = row.name
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
// If we are a moderator but there are no uploads attached to a user, send response
|
||||
if (userids.length === 0) return res.json({ success: true, files, count })
|
||||
// If we are a regular user, or we are not listing all uploads, send response
|
||||
if (!ismoderator || !all) return res.json({ success: true, files, count, albums, basedomain })
|
||||
|
||||
const users = await db.table('users').whereIn('id', userids)
|
||||
for (const dbUser of users)
|
||||
for (const file of files)
|
||||
if (file.userid === dbUser.id)
|
||||
file.username = dbUser.username
|
||||
// Otherwise proceed to querying usernames
|
||||
let users = {}
|
||||
if (uploaderID) {
|
||||
// If we are already filtering by username, manually build array
|
||||
users[uploaderID] = uploader
|
||||
} else {
|
||||
const userids = files
|
||||
.map(file => file.userid)
|
||||
.filter((v, i, a) => {
|
||||
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
|
||||
})
|
||||
// If there are no uploads attached to a registered user, send response
|
||||
if (userids.length === 0) return res.json({ success: true, files, count, basedomain })
|
||||
|
||||
return res.json({ success: true, files, count })
|
||||
// Query usernames of user IDs from currently selected files
|
||||
users = await db.table('users')
|
||||
.whereIn('id', userids)
|
||||
.then(rows => {
|
||||
// Build Object indexed by their IDs
|
||||
const obj = {}
|
||||
for (const row of rows) obj[row.id] = row.username
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({ success: true, files, count, users, basedomain })
|
||||
}
|
||||
|
||||
module.exports = uploadsController
|
||||
|
@ -203,7 +203,7 @@ page.getItemID = function (element) {
|
||||
page.domClick = function (event) {
|
||||
// We are processing clicks this way to avoid using "onclick" attribute
|
||||
// Apparently we will need to use "unsafe-inline" for "script-src" directive
|
||||
// of Content Security Policy (CSP), if we want ot use "onclick" attribute
|
||||
// of Content Security Policy (CSP), if we want to use "onclick" attribute
|
||||
// Though I think that only applies to some browsers (?)
|
||||
// Either way, I personally would rather not
|
||||
// Of course it wouldn't have mattered if we didn't use CSP to begin with
|
||||
@ -342,11 +342,12 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
if (response.data.description === 'No token provided') {
|
||||
return page.verifyToken(page.token)
|
||||
} else {
|
||||
if (element) page.isLoading(element, false)
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
if (pageNum && (response.data.files.length === 0)) {
|
||||
// Only remove loading class here, since beyond this the entire page will be replaced anyways
|
||||
const files = response.data.files
|
||||
if (pageNum && (files === 0)) {
|
||||
if (element) page.isLoading(element, false)
|
||||
return swal('An error occurred!', `There are no more uploads to populate page ${pageNum + 1}.`, 'error')
|
||||
}
|
||||
@ -354,6 +355,9 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
page.currentView = all ? 'uploadsAll' : 'uploads'
|
||||
page.cache.uploads = {}
|
||||
|
||||
const albums = response.data.albums
|
||||
const users = response.data.users
|
||||
const basedomain = response.data.basedomain
|
||||
const pagination = page.paginate(response.data.count, 25, pageNum)
|
||||
|
||||
let filter = ''
|
||||
@ -432,7 +436,30 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
</div>
|
||||
`
|
||||
|
||||
// Set to true to tick "all files" checkbox in list view
|
||||
let allSelected = true
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
// Build full URLs
|
||||
files[i].file = `${basedomain}/${files[i].name}`
|
||||
if (files[i].thumb) files[i].thumb = `${basedomain}/${files[i].thumb}`
|
||||
// Cache bare minimum data for thumbnails viewer
|
||||
page.cache.uploads[files[i].id] = {
|
||||
name: files[i].name,
|
||||
thumb: files[i].thumb,
|
||||
original: files[i].file
|
||||
}
|
||||
// Prettify
|
||||
files[i].prettyBytes = page.getPrettyBytes(parseInt(files[i].size))
|
||||
files[i].prettyDate = page.getPrettyDate(new Date(files[i].timestamp * 1000))
|
||||
// Update selected status
|
||||
files[i].selected = page.selected[page.currentView].includes(files[i].id)
|
||||
if (allSelected && !files[i].selected) allSelected = false
|
||||
// Appendix (display album or user)
|
||||
files[i].appendix = files[i].albumid ? albums[files[i].albumid] : ''
|
||||
if (all) files[i].appendix = files[i].userid ? users[files[i].userid] : ''
|
||||
}
|
||||
|
||||
if (page.views[page.currentView].type === 'thumbs') {
|
||||
page.dom.innerHTML = `
|
||||
${pagination}
|
||||
@ -447,24 +474,8 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
|
||||
const table = document.getElementById('table')
|
||||
|
||||
for (let i = 0; i < response.data.files.length; i++) {
|
||||
const upload = response.data.files[i]
|
||||
const selected = page.selected[page.currentView].includes(upload.id)
|
||||
if (!selected && allSelected) allSelected = false
|
||||
|
||||
page.cache.uploads[upload.id] = {
|
||||
name: upload.name,
|
||||
thumb: upload.thumb,
|
||||
original: upload.file
|
||||
}
|
||||
|
||||
// Prettify
|
||||
upload.prettyBytes = page.getPrettyBytes(parseInt(upload.size))
|
||||
upload.prettyDate = page.getPrettyDate(new Date(upload.timestamp * 1000))
|
||||
|
||||
let displayAlbumOrUser = upload.album
|
||||
if (all) displayAlbumOrUser = upload.username || ''
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const upload = files[i]
|
||||
const div = document.createElement('div')
|
||||
div.className = 'image-container column is-narrow'
|
||||
div.dataset.id = upload.id
|
||||
@ -474,7 +485,7 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
div.innerHTML = `<a class="image" href="${upload.file}" target="_blank" rel="noopener"><h1 class="title">${upload.extname || 'N/A'}</h1></a>`
|
||||
|
||||
div.innerHTML += `
|
||||
<input type="checkbox" class="checkbox" title="Select this file" data-action="select"${selected ? ' checked' : ''}>
|
||||
<input type="checkbox" class="checkbox" title="Select this file" data-action="select"${upload.selected ? ' checked' : ''}>
|
||||
<div class="controls">
|
||||
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
|
||||
<span class="icon">
|
||||
@ -499,7 +510,7 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
</div>
|
||||
<div class="details">
|
||||
<p><span class="name" title="${upload.file}">${upload.name}</span></p>
|
||||
<p>${displayAlbumOrUser ? `<span>${displayAlbumOrUser}</span> – ` : ''}${upload.prettyBytes}</p>
|
||||
<p>${upload.appendix ? `<span>${upload.appendix}</span> – ` : ''}${upload.prettyBytes}</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
@ -508,9 +519,6 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
page.lazyLoad.update()
|
||||
}
|
||||
} else {
|
||||
let albumOrUser = 'Album'
|
||||
if (all) albumOrUser = 'User'
|
||||
|
||||
page.dom.innerHTML = `
|
||||
${pagination}
|
||||
${extraControls}
|
||||
@ -521,8 +529,9 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
<tr>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all uploads" data-action="select-all"></th>
|
||||
<th style="width: 25%">File</th>
|
||||
<th>${albumOrUser}</th>
|
||||
<th>${all ? 'User' : 'Album'}</th>
|
||||
<th>Size</th>
|
||||
${all ? '<th>IP</th>' : ''}
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@ -538,31 +547,16 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
|
||||
const table = document.getElementById('table')
|
||||
|
||||
for (let i = 0; i < response.data.files.length; i++) {
|
||||
const upload = response.data.files[i]
|
||||
const selected = page.selected[page.currentView].includes(upload.id)
|
||||
if (!selected && allSelected) allSelected = false
|
||||
|
||||
page.cache.uploads[upload.id] = {
|
||||
name: upload.name,
|
||||
thumb: upload.thumb,
|
||||
original: upload.file
|
||||
}
|
||||
|
||||
// Prettify
|
||||
upload.prettyBytes = page.getPrettyBytes(parseInt(upload.size))
|
||||
upload.prettyDate = page.getPrettyDate(new Date(upload.timestamp * 1000))
|
||||
|
||||
let displayAlbumOrUser = upload.album
|
||||
if (all) displayAlbumOrUser = upload.username || ''
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const upload = files[i]
|
||||
const tr = document.createElement('tr')
|
||||
tr.dataset.id = upload.id
|
||||
tr.innerHTML = `
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select this file" data-action="select"${selected ? ' checked' : ''}></td>
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select this file" data-action="select"${upload.selected ? ' checked' : ''}></td>
|
||||
<th><a href="${upload.file}" target="_blank" rel="noopener" title="${upload.file}">${upload.name}</a></th>
|
||||
<th>${displayAlbumOrUser}</th>
|
||||
<th>${upload.appendix}</th>
|
||||
<td>${upload.prettyBytes}</td>
|
||||
${all ? `<td>${upload.ip || ''}</td>` : ''}
|
||||
<td>${upload.prettyDate}</td>
|
||||
<td class="controls" style="text-align: right">
|
||||
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
|
||||
@ -594,14 +588,14 @@ page.getUploads = function ({ pageNum, album, all, uploader } = {}, element) {
|
||||
}
|
||||
}
|
||||
|
||||
if (allSelected && response.data.files.length) {
|
||||
if (allSelected && files.length) {
|
||||
const selectAll = document.getElementById('selectAll')
|
||||
if (selectAll) selectAll.checked = true
|
||||
}
|
||||
|
||||
if (page.currentView === 'uploads') page.views.uploads.album = album
|
||||
if (page.currentView === 'uploadsAll') page.views.uploadsAll.uploader = uploader
|
||||
page.views[page.currentView].pageNum = response.data.files.length ? pageNum : 0
|
||||
page.views[page.currentView].pageNum = files.length ? pageNum : 0
|
||||
}).catch(function (error) {
|
||||
if (element) page.isLoading(element, false)
|
||||
console.log(error)
|
||||
|
@ -16,7 +16,7 @@
|
||||
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
||||
v4: Renders in /public/render/* directories (to be used by render.js).
|
||||
#}
|
||||
{% set v1 = "WNcjDAmPAR" %}
|
||||
{% set v1 = "uDNOxxQGxC" %}
|
||||
{% set v2 = "hiboQUzAzp" %}
|
||||
{% set v3 = "DKoamSTKbO" %}
|
||||
{% set v4 = "43gxmxi7v8" %}
|
||||
|
Loading…
Reference in New Issue
Block a user