mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
More improvements to albums, and others
Improvements related to albums: * Changed "rename album" option with a better "edit album" feature. With it you can also disable download or public link and even request a new public link (https://i.fiery.me/fz1y.png). This also adds a new API route: /api/albums/edit. The old API route, /api/albums/rename, is still available but will silently be using the new API in backend. * Deleting album will now also delete its zip archive if exists. * Renaming albums will also rename its zip archive if exists. * Generating zip will use async fs.readFile instead of fs.readFileSync. This should improve generating speed somewhat. * The codes that tries to generate random identifier for album will now check whether an album with the same identifier already exists. It will also rely on "uploads.maxTries" config option to limit how many times it will try to re-generate a new random identifier. * Added a new config option "uploads.albumIdentifierLength" which sets the length of the randomly generated identifier. * Added "download" and "public" columns to "albums" table in database/db.js. Existing users can run "node database/migration.js" to add the columns. Others: * uploadsController.getUniqueRandomName will no longer accept 3 paramters (previously it would accept a callback in the third parameter). It will now instead return a Promise. * Album name of disabled/deleted albums will no longer be shown in uploads list. * Added "fileLength" column to "users" table in database/db.js. * Renamed HTTP404.html and HTTP500.html in /pages/error to 404.html and 500.html respectively. I'm still using symlinks though. * Added a new CSS named sweetalert.css which will be used in homepage, auth and dashboard. It will style all sweetalert modals with dark theme (matching the current color scheme used in this branch). * Updated icons (added download icon). * Some other improvements/tweaks here and there.
This commit is contained in:
parent
8496e69955
commit
4660200b1e
@ -11,9 +11,9 @@
|
||||
|
||||
This branch is the one being used at [https://safe.fiery.me](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).
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
Configuration file of lolisafe, `config.js`, is also 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
|
||||
|
||||
|
@ -8,16 +8,21 @@ const Zip = require('jszip')
|
||||
|
||||
const albumsController = {}
|
||||
|
||||
// Let's default it to only 1 try (for missing config key)
|
||||
const maxTries = config.uploads.maxTries || 1
|
||||
const homeDomain = config.homeDomain || config.domain
|
||||
const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
|
||||
const zipsDir = path.join(uploadsDir, 'zips')
|
||||
const maxTotalSize = config.uploads.generateZips.maxTotalSize
|
||||
const maxTotalSizeBytes = parseInt(maxTotalSize) * 1000000
|
||||
|
||||
albumsController.list = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) { return }
|
||||
|
||||
const fields = ['id', 'name']
|
||||
let fields = ['id', 'name']
|
||||
if (req.params.sidebar === undefined) {
|
||||
fields.push('timestamp')
|
||||
fields.push('identifier')
|
||||
fields = fields.concat(fields, ['timestamp', 'identifier', 'editedAt', 'download', 'public'])
|
||||
}
|
||||
|
||||
const albums = await db.table('albums')
|
||||
@ -34,19 +39,22 @@ albumsController.list = async (req, res, next) => {
|
||||
const ids = []
|
||||
for (const album of albums) {
|
||||
album.date = utils.getPrettyDate(new Date(album.timestamp * 1000))
|
||||
album.download = album.download !== 0
|
||||
album.public = album.public !== 0
|
||||
|
||||
album.identifier = `${homeDomain}/a/${album.identifier}`
|
||||
ids.push(album.id)
|
||||
}
|
||||
|
||||
const files = await db.table('files').whereIn('albumid', ids).select('albumid')
|
||||
const files = await db.table('files')
|
||||
.whereIn('albumid', ids)
|
||||
.select('albumid')
|
||||
const albumsCount = {}
|
||||
|
||||
for (const id of ids) { albumsCount[id] = 0 }
|
||||
for (const file of files) { albumsCount[file.albumid] += 1 }
|
||||
for (const album of albums) { album.files = albumsCount[album.id] }
|
||||
|
||||
return res.json({ success: true, albums })
|
||||
return res.json({ success: true, albums, homeDomain })
|
||||
}
|
||||
|
||||
albumsController.create = async (req, res, next) => {
|
||||
@ -70,19 +78,46 @@ albumsController.create = async (req, res, next) => {
|
||||
return res.json({ success: false, description: 'There\'s already an album with that name.' })
|
||||
}
|
||||
|
||||
const identifier = await albumsController.getUniqueRandomName()
|
||||
.catch(error => {
|
||||
res.json({ success: false, description: error.toString() })
|
||||
})
|
||||
if (!identifier) { return }
|
||||
|
||||
await db.table('albums').insert({
|
||||
name,
|
||||
enabled: 1,
|
||||
userid: user.id,
|
||||
identifier: randomstring.generate(8),
|
||||
identifier,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
editedAt: 0,
|
||||
zipGeneratedAt: 0
|
||||
zipGeneratedAt: 0,
|
||||
download: 1,
|
||||
public: 1
|
||||
})
|
||||
|
||||
return res.json({ success: true })
|
||||
}
|
||||
|
||||
albumsController.getUniqueRandomName = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const select = i => {
|
||||
const identifier = randomstring.generate(config.uploads.albumIdentifierLength)
|
||||
db.table('albums')
|
||||
.where('identifier', identifier)
|
||||
.then(rows => {
|
||||
if (!rows || !rows.length) { return resolve(identifier) }
|
||||
console.log(`An album with identifier ${name} already exists (${++i}/${maxTries}).`)
|
||||
if (i < maxTries) { return select(i) }
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return reject('Sorry, we could not allocate a unique random identifier. Try again?')
|
||||
})
|
||||
}
|
||||
// Get us a unique random identifier
|
||||
select(0)
|
||||
})
|
||||
}
|
||||
|
||||
albumsController.delete = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) { return }
|
||||
@ -106,10 +141,7 @@ albumsController.delete = async (req, res, next) => {
|
||||
failedids = await utils.bulkDeleteFilesByIds(ids, user)
|
||||
|
||||
if (failedids.length === ids.length) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Could not delete any of the files associated with the album.'
|
||||
})
|
||||
return res.json({ success: false, description: 'Could not delete any of the files associated with the album.' })
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,20 +150,35 @@ albumsController.delete = async (req, res, next) => {
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.update({ enabled: 0 })
|
||||
.update('enabled', 0)
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
failedids
|
||||
const identifier = await db.table('albums')
|
||||
.select('identifier')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.first()
|
||||
.then(row => row.identifier)
|
||||
|
||||
// Unlink zip archive of the album if it exists
|
||||
const zipPath = path.join(zipsDir, `${identifier}.zip`)
|
||||
return fs.access(zipPath, error => {
|
||||
if (error) { return res.json({ success: true, failedids }) }
|
||||
fs.unlink(zipPath, error => {
|
||||
if (!error) { return res.json({ success: true, failedids }) }
|
||||
console.log(error)
|
||||
res.json({ success: false, description: error.toString(), failedids })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
albumsController.rename = async (req, res, next) => {
|
||||
albumsController.edit = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) { return }
|
||||
|
||||
const id = req.body.id
|
||||
if (id === undefined || id === '') {
|
||||
const id = parseInt(req.body.id)
|
||||
if (isNaN(id)) {
|
||||
return res.json({ success: false, description: 'No album specified.' })
|
||||
}
|
||||
|
||||
@ -143,12 +190,15 @@ albumsController.rename = async (req, res, next) => {
|
||||
const album = await db.table('albums')
|
||||
.where({
|
||||
name,
|
||||
userid: user.id
|
||||
userid: user.id,
|
||||
enabled: 1
|
||||
})
|
||||
.first()
|
||||
|
||||
if (album) {
|
||||
if (album && (album.id !== id)) {
|
||||
return res.json({ success: false, description: 'Name already in use.' })
|
||||
} else if (req._old && (album.id === id)) {
|
||||
return res.json({ success: false, description: 'You did not specify a new name.' })
|
||||
}
|
||||
|
||||
await db.table('albums')
|
||||
@ -157,21 +207,83 @@ albumsController.rename = async (req, res, next) => {
|
||||
userid: user.id
|
||||
})
|
||||
.update({
|
||||
name
|
||||
name,
|
||||
download: Boolean(req.body.download),
|
||||
public: Boolean(req.body.public)
|
||||
})
|
||||
|
||||
return res.json({ success: true })
|
||||
if (req.body.requestLink) {
|
||||
const oldIdentifier = await db.table('albums')
|
||||
.select('identifier')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.first()
|
||||
.then(row => row.identifier)
|
||||
|
||||
const identifier = await albumsController.getUniqueRandomName()
|
||||
.catch(error => {
|
||||
res.json({ success: false, description: error.toString() })
|
||||
})
|
||||
if (!identifier) { return }
|
||||
|
||||
await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.update('identifier', identifier)
|
||||
|
||||
// Rename zip archive of the album if it exists
|
||||
const zipPath = path.join(zipsDir, `${oldIdentifier}.zip`)
|
||||
return fs.access(zipPath, error => {
|
||||
if (error) { return res.json({ success: true, identifier }) }
|
||||
fs.rename(zipPath, path.join(zipsDir, `${identifier}.zip`), error => {
|
||||
if (!error) { return res.json({ success: true, identifier }) }
|
||||
console.log(error)
|
||||
res.json({ success: false, description: error.toString() })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({ success: true, name })
|
||||
}
|
||||
|
||||
albumsController.rename = async (req, res, next) => {
|
||||
req._old = true
|
||||
req.body = { name: req.body.name }
|
||||
return albumsController.edit(req, res, next)
|
||||
}
|
||||
|
||||
albumsController.get = async (req, res, next) => {
|
||||
// TODO:
|
||||
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.' }) }
|
||||
const album = await db.table('albums')
|
||||
.where({
|
||||
identifier,
|
||||
enabled: 1
|
||||
})
|
||||
.first()
|
||||
|
||||
if (!album) {
|
||||
return res.json({ success: false, description: 'Album not found.' })
|
||||
} else if (album.public === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
description: 'This album is not available for public.'
|
||||
})
|
||||
}
|
||||
|
||||
const title = album.name
|
||||
const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC')
|
||||
const files = await db.table('files')
|
||||
.select('name')
|
||||
.where('albumid', album.id)
|
||||
.orderBy('id', 'DESC')
|
||||
|
||||
for (const file of files) {
|
||||
file.file = `${config.domain}/${file.name}`
|
||||
@ -210,21 +322,20 @@ albumsController.generateZip = async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (!config.uploads.generateZips || !config.uploads.generateZips.enabled) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
description: 'Zip generation disabled.'
|
||||
})
|
||||
return res.status(401).json({ success: false, description: 'Zip generation disabled.' })
|
||||
}
|
||||
|
||||
const album = await db.table('albums')
|
||||
.where({ identifier, enabled: 1 })
|
||||
.where({
|
||||
identifier,
|
||||
enabled: 1
|
||||
})
|
||||
.first()
|
||||
|
||||
if (!album) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Album not found.'
|
||||
})
|
||||
return res.json({ success: false, description: 'Album not found.' })
|
||||
} else if (album.download === 0) {
|
||||
return res.json({ success: false, description: 'Download for this album is disabled.' })
|
||||
}
|
||||
|
||||
if ((!versionString || versionString <= 0) && album.editedAt) {
|
||||
@ -232,64 +343,66 @@ albumsController.generateZip = async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (album.zipGeneratedAt > album.editedAt) {
|
||||
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`)
|
||||
const fileName = `${album.name}.zip`
|
||||
return download(filePath, fileName)
|
||||
} else {
|
||||
console.log(`Generating zip for album identifier: ${identifier}`)
|
||||
const files = await db.table('files')
|
||||
.select('name', 'size')
|
||||
.where('albumid', album.id)
|
||||
if (files.length === 0) {
|
||||
const filePath = path.join(zipsDir, `${identifier}.zip`)
|
||||
const exists = await new Promise(resolve => fs.access(filePath, error => resolve(!error)))
|
||||
if (exists) {
|
||||
const fileName = `${album.name}.zip`
|
||||
return download(filePath, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Generating zip for album identifier: ${identifier}`)
|
||||
const files = await db.table('files')
|
||||
.select('name', 'size')
|
||||
.where('albumid', album.id)
|
||||
if (files.length === 0) {
|
||||
return res.json({ success: false, description: 'There are no files in the album.' })
|
||||
}
|
||||
|
||||
if (maxTotalSize) {
|
||||
const totalSizeBytes = files.reduce((accumulator, file) => accumulator + parseInt(file.size), 0)
|
||||
if (totalSizeBytes > maxTotalSizeBytes) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'There are no files in the album.'
|
||||
description: `Total size of all files in the album exceeds the configured limit (${maxTotalSize}).`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (config.uploads.generateZips.maxTotalSize) {
|
||||
const maxTotalSizeBytes = parseInt(config.uploads.generateZips.maxTotalSize) * 1000000
|
||||
const totalSizeBytes = files.reduce((accumulator, file) => accumulator + parseInt(file.size), 0)
|
||||
if (totalSizeBytes > maxTotalSizeBytes) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Total size of all the files in the album exceeds the limit set by the administrator (${config.uploads.generateZips.maxTotalSize}).`
|
||||
})
|
||||
}
|
||||
}
|
||||
const zipPath = path.join(zipsDir, `${album.identifier}.zip`)
|
||||
const archive = new Zip()
|
||||
|
||||
const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`)
|
||||
const archive = new Zip()
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
// const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name))
|
||||
archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name)))
|
||||
} catch (error) {
|
||||
let iteration = 0
|
||||
for (const file of files) {
|
||||
fs.readFile(path.join(uploadsDir, file.name), (error, data) => {
|
||||
if (error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
archive.file(file.name, data)
|
||||
}
|
||||
}
|
||||
|
||||
archive
|
||||
.generateNodeStream({
|
||||
type: 'nodebuffer',
|
||||
streamFiles: true,
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: {
|
||||
level: 1
|
||||
}
|
||||
})
|
||||
.pipe(fs.createWriteStream(zipPath))
|
||||
.on('finish', async () => {
|
||||
console.log(`Generated zip for album identifier: ${identifier}`)
|
||||
await db.table('albums')
|
||||
.where('id', album.id)
|
||||
.update({ zipGeneratedAt: Math.floor(Date.now() / 1000) })
|
||||
iteration++
|
||||
if (iteration === files.length) {
|
||||
archive
|
||||
.generateNodeStream({
|
||||
type: 'nodebuffer',
|
||||
streamFiles: true,
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 1 }
|
||||
})
|
||||
.pipe(fs.createWriteStream(zipPath))
|
||||
.on('finish', async () => {
|
||||
console.log(`Generated zip for album identifier: ${identifier}`)
|
||||
await db.table('albums')
|
||||
.where('id', album.id)
|
||||
.update('zipGeneratedAt', Math.floor(Date.now() / 1000))
|
||||
|
||||
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`)
|
||||
const fileName = `${album.name}.zip`
|
||||
return download(filePath, fileName)
|
||||
})
|
||||
const filePath = path.join(zipsDir, `${identifier}.zip`)
|
||||
const fileName = `${album.name}.zip`
|
||||
return download(filePath, fileName)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,10 +430,7 @@ albumsController.addFiles = async (req, res, next) => {
|
||||
.first()
|
||||
|
||||
if (!album) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Album doesn\'t exist or it doesn\'t belong to the user.'
|
||||
})
|
||||
return res.json({ success: false, description: 'Album doesn\'t exist or it doesn\'t belong to the user.' })
|
||||
}
|
||||
|
||||
albumids.push(albumid)
|
||||
@ -357,10 +467,7 @@ albumsController.addFiles = async (req, res, next) => {
|
||||
.update('editedAt', Math.floor(Date.now() / 1000))
|
||||
}))
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
failedids
|
||||
})
|
||||
return res.json({ success: true, failedids })
|
||||
}
|
||||
|
||||
return res.json({
|
||||
|
@ -16,10 +16,7 @@ authController.verify = async (req, res, next) => {
|
||||
const user = await db.table('users').where('username', username).first()
|
||||
if (!user) { return res.json({ success: false, description: 'Username doesn\'t exist.' }) }
|
||||
if (user.enabled === false || user.enabled === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'This account has been disabled.'
|
||||
})
|
||||
return res.json({ success: false, description: 'This account has been disabled.' })
|
||||
}
|
||||
|
||||
bcrypt.compare(password, user.password, (error, result) => {
|
||||
@ -86,7 +83,10 @@ authController.changePassword = async (req, res, next) => {
|
||||
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻.' })
|
||||
}
|
||||
|
||||
await db.table('users').where('id', user.id).update({ password: hash })
|
||||
await db.table('users')
|
||||
.where('id', user.id)
|
||||
.update('password', hash)
|
||||
|
||||
return res.json({ success: true })
|
||||
})
|
||||
}
|
||||
@ -117,7 +117,10 @@ authController.changeFileLength = async (req, res, next) => {
|
||||
return res.json({ success: true })
|
||||
}
|
||||
|
||||
await db.table('users').where('id', user.id).update({ fileLength })
|
||||
await db.table('users')
|
||||
.where('id', user.id)
|
||||
.update('fileLength', fileLength)
|
||||
|
||||
return res.json({ success: true })
|
||||
}
|
||||
|
||||
|
@ -11,28 +11,23 @@ const uploadsController = {}
|
||||
|
||||
// Let's default it to only 1 try (for missing config key)
|
||||
const maxTries = config.uploads.maxTries || 1
|
||||
const uploadDir = path.join(__dirname, '..', config.uploads.folder)
|
||||
const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
|
||||
const chunkedUploads = config.uploads.chunkedUploads && config.uploads.chunkedUploads.enabled
|
||||
const chunksDir = path.join(uploadDir, 'chunks')
|
||||
const chunksDir = path.join(uploadsDir, 'chunks')
|
||||
const maxSizeBytes = parseInt(config.uploads.maxSize) * 1000000
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination (req, file, cb) {
|
||||
// If chunked uploads is disabled or the uploaded file is not a chunk
|
||||
if (!chunkedUploads || (req.body.uuid === undefined && req.body.chunkindex === undefined)) {
|
||||
return cb(null, uploadDir)
|
||||
return cb(null, uploadsDir)
|
||||
}
|
||||
|
||||
// Check for the existence of UUID dir in chunks dir
|
||||
const uuidDir = path.join(chunksDir, req.body.uuid)
|
||||
fs.access(uuidDir, error => {
|
||||
// If it exists, callback
|
||||
if (!error) { return cb(null, uuidDir) }
|
||||
// It it doesn't, then make it first
|
||||
fs.mkdir(uuidDir, error => {
|
||||
// If there was no error, callback
|
||||
if (!error) { return cb(null, uuidDir) }
|
||||
// Otherwise, log it
|
||||
console.log(error)
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return cb('Could not process the chunked upload. Try again?')
|
||||
@ -44,7 +39,9 @@ const storage = multer.diskStorage({
|
||||
if (!chunkedUploads || (req.body.uuid === undefined && req.body.chunkindex === undefined)) {
|
||||
const extension = path.extname(file.originalname)
|
||||
const length = uploadsController.getFileNameLength(req)
|
||||
return uploadsController.getUniqueRandomName(length, extension, cb)
|
||||
return uploadsController.getUniqueRandomName(length, extension)
|
||||
.then(name => cb(null, name))
|
||||
.catch(error => cb(error))
|
||||
}
|
||||
|
||||
// index.extension (e.i. 0, 1, ..., n - will prepend zeros depending on the amount of chunks)
|
||||
@ -101,22 +98,20 @@ uploadsController.getFileNameLength = req => {
|
||||
return config.uploads.fileLength.default || 32
|
||||
}
|
||||
|
||||
uploadsController.getUniqueRandomName = (length, extension, cb) => {
|
||||
const access = i => {
|
||||
const name = randomstring.generate(length) + extension
|
||||
fs.access(path.join(uploadDir, name), error => {
|
||||
// If a file with the same name does not exist
|
||||
if (error) { 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) }
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return cb('Sorry, we could not allocate a unique random name. Try again?')
|
||||
})
|
||||
}
|
||||
// Get us a unique random name
|
||||
access(0)
|
||||
uploadsController.getUniqueRandomName = (length, extension) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const access = i => {
|
||||
const name = randomstring.generate(length) + extension
|
||||
fs.access(path.join(uploadsDir, name), error => {
|
||||
if (error) { return resolve(name) }
|
||||
console.log(`A file named ${name} already exists (${++i}/${maxTries}).`)
|
||||
if (i < maxTries) { return access(i) }
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return reject('Sorry, we could not allocate a unique random name. Try again?')
|
||||
})
|
||||
}
|
||||
access(0)
|
||||
})
|
||||
}
|
||||
|
||||
uploadsController.upload = async (req, res, next) => {
|
||||
@ -129,10 +124,7 @@ uploadsController.upload = async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (user && (user.enabled === false || user.enabled === 0)) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'This account has been disabled.'
|
||||
})
|
||||
return res.json({ success: false, description: 'This account has been disabled.' })
|
||||
}
|
||||
|
||||
if (user && user.fileLength && !req.headers.filelength) {
|
||||
@ -181,10 +173,7 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
|
||||
|
||||
uploadsController.finishChunks = async (req, res, next) => {
|
||||
if (!chunkedUploads) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Chunked uploads is disabled at the moment.'
|
||||
})
|
||||
return res.json({ success: false, description: 'Chunked uploads is disabled at the moment.' })
|
||||
}
|
||||
|
||||
let user
|
||||
@ -196,10 +185,7 @@ uploadsController.finishChunks = async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (user && (user.enabled === false || user.enabled === 0)) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'This account has been disabled.'
|
||||
})
|
||||
return res.json({ success: false, description: 'This account has been disabled.' })
|
||||
}
|
||||
|
||||
if (user && user.fileLength && !req.headers.filelength) {
|
||||
@ -239,64 +225,64 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
||||
const extension = typeof original === 'string' ? path.extname(original) : ''
|
||||
const length = uploadsController.getFileNameLength(req)
|
||||
|
||||
uploadsController.getUniqueRandomName(length, extension, async (error, name) => {
|
||||
const name = await uploadsController.getUniqueRandomName(length, extension)
|
||||
.catch(erred)
|
||||
if (!name) { return }
|
||||
|
||||
const destination = path.join(uploadsDir, name)
|
||||
const destFileStream = fs.createWriteStream(destination, { flags: 'a' })
|
||||
|
||||
// Sort chunk names
|
||||
chunkNames.sort()
|
||||
|
||||
// Append all chunks
|
||||
const chunksAppended = await uploadsController.appendToStream(destFileStream, uuidDir, chunkNames)
|
||||
.then(() => true)
|
||||
.catch(erred)
|
||||
if (!chunksAppended) { return }
|
||||
|
||||
// Delete all chunks
|
||||
const chunksDeleted = await Promise.all(chunkNames.map(chunkName => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunkPath = path.join(uuidDir, chunkName)
|
||||
fs.unlink(chunkPath, error => {
|
||||
if (error) { return reject(error) }
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}))
|
||||
.then(() => true)
|
||||
.catch(erred)
|
||||
if (!chunksDeleted) { return }
|
||||
|
||||
// Delete UUID dir
|
||||
fs.rmdir(uuidDir, async error => {
|
||||
if (error) { return erred(error) }
|
||||
|
||||
const destination = path.join(uploadDir, name)
|
||||
const destFileStream = fs.createWriteStream(destination, { flags: 'a' })
|
||||
const data = {
|
||||
filename: name,
|
||||
originalname: file.original || '',
|
||||
mimetype: file.type || '',
|
||||
size: file.size || 0
|
||||
}
|
||||
|
||||
// Sort chunk names
|
||||
chunkNames.sort()
|
||||
data.albumid = parseInt(file.albumid)
|
||||
if (isNaN(data.albumid)) { data.albumid = albumid }
|
||||
|
||||
// Append all chunks
|
||||
const chunksAppended = await uploadsController.appendToStream(destFileStream, uuidDir, chunkNames)
|
||||
.then(() => true)
|
||||
.catch(erred)
|
||||
if (!chunksAppended) { return }
|
||||
|
||||
// Delete all chunks
|
||||
const chunksDeleted = await Promise.all(chunkNames.map(chunkName => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunkPath = path.join(uuidDir, chunkName)
|
||||
fs.unlink(chunkPath, error => {
|
||||
if (error) { return reject(error) }
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}))
|
||||
.then(() => true)
|
||||
.catch(erred)
|
||||
if (!chunksDeleted) { return }
|
||||
|
||||
// Delete UUID dir
|
||||
fs.rmdir(uuidDir, async error => {
|
||||
if (error) { return erred(error) }
|
||||
|
||||
const data = {
|
||||
filename: name,
|
||||
originalname: file.original || '',
|
||||
mimetype: file.type || '',
|
||||
size: file.size || 0
|
||||
}
|
||||
|
||||
data.albumid = parseInt(file.albumid)
|
||||
if (isNaN(data.albumid)) { data.albumid = albumid }
|
||||
|
||||
infoMap.push({
|
||||
path: destination,
|
||||
data
|
||||
})
|
||||
|
||||
iteration++
|
||||
if (iteration >= files.length) {
|
||||
const result = await uploadsController.writeFilesToDb(req, res, user, infoMap)
|
||||
.catch(erred)
|
||||
|
||||
if (result) {
|
||||
return uploadsController.processFilesForDisplay(req, res, result.files, result.existingFiles)
|
||||
}
|
||||
}
|
||||
infoMap.push({
|
||||
path: destination,
|
||||
data
|
||||
})
|
||||
|
||||
iteration++
|
||||
if (iteration === files.length) {
|
||||
const result = await uploadsController.writeFilesToDb(req, res, user, infoMap)
|
||||
.catch(erred)
|
||||
|
||||
if (result) {
|
||||
return uploadsController.processFilesForDisplay(req, res, result.files, result.existingFiles)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -385,7 +371,7 @@ uploadsController.writeFilesToDb = (req, res, user, infoMap) => {
|
||||
}
|
||||
|
||||
iteration++
|
||||
if (iteration >= infoMap.length) {
|
||||
if (iteration === infoMap.length) {
|
||||
return resolve({ files, existingFiles })
|
||||
}
|
||||
})
|
||||
@ -504,16 +490,10 @@ uploadsController.bulkDelete = async (req, res) => {
|
||||
|
||||
const failedids = await utils.bulkDeleteFilesByIds(ids, user)
|
||||
if (failedids.length < ids.length) {
|
||||
return res.json({
|
||||
success: true,
|
||||
failedids
|
||||
})
|
||||
return res.json({ success: true, failedids })
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Could not delete any of the selected files.'
|
||||
})
|
||||
return res.json({ success: false, description: 'Could not delete any of the selected files.' })
|
||||
}
|
||||
|
||||
uploadsController.list = async (req, res) => {
|
||||
@ -540,6 +520,13 @@ uploadsController.list = async (req, res) => {
|
||||
.select('id', 'albumid', 'timestamp', 'name', 'userid', 'size')
|
||||
|
||||
const albums = await db.table('albums')
|
||||
.where(function () {
|
||||
this.where('enabled', 1)
|
||||
if (user.username !== 'root') {
|
||||
this.where('userid', user.id)
|
||||
}
|
||||
})
|
||||
|
||||
const basedomain = config.domain
|
||||
const userids = []
|
||||
|
||||
@ -585,7 +572,8 @@ uploadsController.list = async (req, res) => {
|
||||
// If we are root but there are no uploads attached to a user, send response
|
||||
if (userids.length === 0) { return res.json({ success: true, files }) }
|
||||
|
||||
const users = await db.table('users').whereIn('id', userids)
|
||||
const users = await db.table('users')
|
||||
.whereIn('id', userids)
|
||||
for (const dbUser of users) {
|
||||
for (const file of files) {
|
||||
if (file.userid === dbUser.id) {
|
||||
|
@ -11,6 +11,8 @@ const init = function (db) {
|
||||
table.integer('timestamp')
|
||||
table.integer('editedAt')
|
||||
table.integer('zipGeneratedAt')
|
||||
table.integer('download')
|
||||
table.integer('public')
|
||||
}).then(() => {})
|
||||
}
|
||||
})
|
||||
@ -41,6 +43,7 @@ const init = function (db) {
|
||||
table.string('token')
|
||||
table.integer('enabled')
|
||||
table.integer('timestamp')
|
||||
table.integer('fileLength')
|
||||
}).then(() => {
|
||||
db.table('users').where({ username: 'root' }).then((user) => {
|
||||
if (user.length > 0) { return }
|
||||
|
@ -4,7 +4,9 @@ const db = require('knex')(config.database)
|
||||
const map = {
|
||||
albums: {
|
||||
editedAt: 'integer',
|
||||
zipGeneratedAt: 'integer'
|
||||
zipGeneratedAt: 'integer',
|
||||
download: 'integer',
|
||||
public: 'integer'
|
||||
},
|
||||
users: {
|
||||
enabled: 'integer',
|
||||
|
@ -63,11 +63,11 @@ for (const page of config.pages) {
|
||||
}
|
||||
|
||||
safe.use((req, res, next) => {
|
||||
res.status(404).sendFile('HTTP404.html', { root: './pages/error/' })
|
||||
res.status(404).sendFile('404.html', { root: './pages/error/' })
|
||||
})
|
||||
safe.use((error, req, res, next) => {
|
||||
console.error(error)
|
||||
res.status(500).sendFile('HTTP500.html', { root: './pages/error/' })
|
||||
res.status(500).sendFile('500.html', { root: './pages/error/' })
|
||||
})
|
||||
|
||||
safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`))
|
||||
|
@ -210,6 +210,10 @@ html {
|
||||
height: 2.25em;
|
||||
}
|
||||
|
||||
.table td a:not([href]) {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
#modal .modal-content {
|
||||
background-color: #31363b;
|
||||
border-radius: 5px;
|
||||
|
124
public/css/sweetalert.css
Normal file
124
public/css/sweetalert.css
Normal file
@ -0,0 +1,124 @@
|
||||
.swal-modal {
|
||||
background-color: #31363b;
|
||||
}
|
||||
|
||||
.swal-title,
|
||||
.swal-text {
|
||||
color: #eff0f1;
|
||||
}
|
||||
|
||||
.swal-content .label,
|
||||
.swal-content .checkbox,
|
||||
.swal-content .radio {
|
||||
color: #eff0f1;
|
||||
}
|
||||
|
||||
.swal-content .checkbox:hover,
|
||||
.swal-content .radio:hover {
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
.swal-button {
|
||||
background-color: #3794d2;
|
||||
color: #eff0f1;
|
||||
}
|
||||
|
||||
.swal-button:hover {
|
||||
background-color: #60a8dc;
|
||||
}
|
||||
|
||||
.swal-button:focus {
|
||||
-webkit-box-shadow: 0 0 0 1px #31363b, 0 0 0 3px rgba(55, 148, 210, 0.29);
|
||||
box-shadow: 0 0 0 1px #31363b, 0 0 0 3px rgba(55, 148, 210, 0.29);
|
||||
}
|
||||
|
||||
.swal-button--loading {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.swal-button--danger {
|
||||
background-color: #da4453;
|
||||
}
|
||||
|
||||
.swal-icon--info {
|
||||
border-color: #3794d2;
|
||||
}
|
||||
|
||||
.swal-icon--info:after,
|
||||
.swal-icon--info:before {
|
||||
background-color: #3794d2;
|
||||
}
|
||||
|
||||
.swal-icon--error {
|
||||
border-color: #da4453;
|
||||
}
|
||||
|
||||
.swal-icon--error__line {
|
||||
background-color: #da4453;
|
||||
}
|
||||
|
||||
.swal-icon--warning {
|
||||
border-color: #f67400;
|
||||
-webkit-animation: pulseWarning .5s infinite alternate;
|
||||
animation: pulseWarning .5s infinite alternate;
|
||||
}
|
||||
|
||||
.swal-icon--warning__body,
|
||||
.swal-icon--warning__dot {
|
||||
background-color: #f67400;
|
||||
-webkit-animation: pulseWarningBody .5s infinite alternate;
|
||||
animation: pulseWarningBody .5s infinite alternate;
|
||||
}
|
||||
|
||||
@-webkit-keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #ffaa60;
|
||||
}
|
||||
to {
|
||||
border-color: #f67400;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #ffaa60;
|
||||
}
|
||||
to {
|
||||
border-color: #f67400;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes pulseWarningBody {
|
||||
0% {
|
||||
background-color: #ffaa60;
|
||||
}
|
||||
to {
|
||||
background-color: #f67400;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulseWarningBody {
|
||||
0% {
|
||||
background-color: #ffaa60;
|
||||
}
|
||||
to {
|
||||
background-color: #f67400;
|
||||
}
|
||||
}
|
||||
|
||||
.swal-icon--success {
|
||||
border-color: #27ae60;
|
||||
}
|
||||
|
||||
.swal-icon--success__line {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.swal-icon--success__hide-corners {
|
||||
background-color: #31363b;
|
||||
}
|
||||
|
||||
.swal-icon--success::after,
|
||||
.swal-icon--success::before {
|
||||
background: #31363b;
|
||||
}
|
@ -10,7 +10,8 @@ const panel = {
|
||||
selectedFiles: [],
|
||||
selectAlbumContainer: undefined,
|
||||
checkboxes: undefined,
|
||||
lastSelected: undefined
|
||||
lastSelected: undefined,
|
||||
albums: []
|
||||
}
|
||||
|
||||
panel.preparePage = () => {
|
||||
@ -569,7 +570,6 @@ panel.addToAlbum = async (ids, album) => {
|
||||
// We want to this to be re-usable
|
||||
panel.selectAlbumContainer = document.createElement('div')
|
||||
panel.selectAlbumContainer.id = 'selectAlbum'
|
||||
panel.selectAlbumContainer.className = 'select is-fullwidth'
|
||||
}
|
||||
|
||||
const options = list.data.albums
|
||||
@ -577,12 +577,17 @@ panel.addToAlbum = async (ids, album) => {
|
||||
.join('\n')
|
||||
|
||||
panel.selectAlbumContainer.innerHTML = `
|
||||
<select>
|
||||
<option value="-1">Remove from album</option>
|
||||
<option value="" selected disabled>Choose an album</option>
|
||||
${options}
|
||||
</select>
|
||||
<p class="help is-danger">If a file is already in an album, it will be moved.</p>
|
||||
<div class="field">
|
||||
<label class="label">If a file is already in an album, it will be moved.</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select>
|
||||
<option value="-1">Remove from album</option>
|
||||
<option value="" selected disabled>Choose an album</option>
|
||||
${options}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const choose = await swal({
|
||||
@ -682,9 +687,13 @@ panel.getAlbums = () => {
|
||||
</div>
|
||||
`
|
||||
|
||||
panel.albums = response.data.albums
|
||||
|
||||
const homeDomain = response.data.homeDomain
|
||||
const table = document.getElementById('table')
|
||||
|
||||
for (const album of response.data.albums) {
|
||||
const albumUrl = `${homeDomain}/a/${album.identifier}`
|
||||
const tr = document.createElement('tr')
|
||||
tr.innerHTML = `
|
||||
<tr>
|
||||
@ -692,18 +701,23 @@ panel.getAlbums = () => {
|
||||
<th>${album.name}</th>
|
||||
<th>${album.files}</th>
|
||||
<td>${album.date}</td>
|
||||
<td><a href="${album.identifier}" target="_blank" rel="noopener">${album.identifier}</a></td>
|
||||
<td><a${album.public ? ` href="${albumUrl}"` : ''} target="_blank" rel="noopener">${albumUrl}</a></td>
|
||||
<td style="text-align: right">
|
||||
<a class="button is-small is-primary" title="Edit name" onclick="panel.renameAlbum(${album.id})">
|
||||
<a class="button is-small is-primary" title="Edit album" onclick="panel.editAlbum(${album.id})">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-pencil-1"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" data-clipboard-text="${album.identifier}">
|
||||
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" ${album.public ? `data-clipboard-text="${album.identifier}"` : 'disabled'}>
|
||||
<span class="icon is-small">
|
||||
<i class="icon-clipboard-1"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-warning" title="Download album" ${album.download ? `href="api/album/zip/${album.identifier}?v=${album.editedAt}"` : 'disabled'}>
|
||||
<span class="icon is-small">
|
||||
<i class="icon-download"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger" title="Delete album" onclick="panel.deleteAlbum(${album.id})">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-trash"></i>
|
||||
@ -726,48 +740,94 @@ panel.getAlbums = () => {
|
||||
})
|
||||
}
|
||||
|
||||
panel.renameAlbum = id => {
|
||||
swal({
|
||||
title: 'Rename album',
|
||||
text: 'New name you want to give the album:',
|
||||
panel.editAlbum = async id => {
|
||||
const album = panel.albums.find(a => a.id === id)
|
||||
if (!album) {
|
||||
return swal('An error occurred!', 'Album with that ID could not be found.', 'error')
|
||||
}
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = `
|
||||
<div class="field">
|
||||
<label class="label">Album name</label>
|
||||
<div class="controls">
|
||||
<input id="_name" class="input" type="text" value="${album.name || 'My super album'}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input id="_download" type="checkbox" ${album.download ? 'checked' : ''}>
|
||||
Enable download
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input id="_public" type="checkbox" ${album.public ? 'checked' : ''}>
|
||||
Enable public link
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input id="_requestLink" type="checkbox">
|
||||
Request new public link
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
const value = await swal({
|
||||
title: 'Edit album',
|
||||
icon: 'info',
|
||||
content: {
|
||||
element: 'input',
|
||||
attributes: {
|
||||
placeholder: 'My super album'
|
||||
}
|
||||
},
|
||||
content: div,
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (!value) { return swal.close() }
|
||||
axios.post('api/albums/rename', {
|
||||
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') }
|
||||
return
|
||||
}
|
||||
|
||||
swal('Success!', 'Your album was renamed to: ' + value, 'success')
|
||||
panel.getAlbumsSidebar()
|
||||
panel.getAlbums()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
})
|
||||
if (!value) { return }
|
||||
|
||||
const response = await axios.post('api/albums/edit', {
|
||||
id,
|
||||
name: document.getElementById('_name').value,
|
||||
download: document.getElementById('_download').checked,
|
||||
public: document.getElementById('_public').checked,
|
||||
requestLink: document.getElementById('_requestLink').checked
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
|
||||
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') {
|
||||
return swal.showInputError('That name is already in use!')
|
||||
} else {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
if (response.data.identifier) {
|
||||
swal('Success!', `Your album's new identifier is: ${response.data.identifier}.`, 'success')
|
||||
} else if (response.data.name !== album.name) {
|
||||
swal('Success!', `Your album was renamed to: ${response.data.name}.`, 'success')
|
||||
} else {
|
||||
swal('Success!', 'Your album was edited!', 'success')
|
||||
}
|
||||
|
||||
panel.getAlbumsSidebar()
|
||||
panel.getAlbums()
|
||||
}
|
||||
|
||||
panel.deleteAlbum = id => {
|
||||
swal({
|
||||
panel.deleteAlbum = async id => {
|
||||
const proceed = await swal({
|
||||
title: 'Are you sure?',
|
||||
text: 'This won\'t delete your files, only the album!',
|
||||
icon: 'warning',
|
||||
@ -785,30 +845,29 @@ panel.deleteAlbum = id => {
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (!value) { return }
|
||||
axios.post('api/albums/delete', {
|
||||
id,
|
||||
purge: value === 'purge'
|
||||
})
|
||||
.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')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success')
|
||||
panel.getAlbumsSidebar()
|
||||
panel.getAlbums()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
})
|
||||
if (!proceed) { return }
|
||||
|
||||
const response = await axios.post('api/albums/delete', {
|
||||
id,
|
||||
purge: proceed === 'purge'
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success')
|
||||
panel.getAlbumsSidebar()
|
||||
panel.getAlbums()
|
||||
}
|
||||
|
||||
panel.submitAlbum = element => {
|
||||
@ -816,7 +875,7 @@ panel.submitAlbum = element => {
|
||||
axios.post('api/albums', {
|
||||
name: document.getElementById('albumName').value
|
||||
})
|
||||
.then(async response => {
|
||||
.then(response => {
|
||||
panel.isLoading(element, false)
|
||||
if (response.data.success === false) {
|
||||
if (response.data.description === 'No token provided') {
|
||||
|
13
public/libs/fontello/fontello.css
vendored
13
public/libs/fontello/fontello.css
vendored
@ -1,11 +1,11 @@
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('fontello.eot?8080531');
|
||||
src: url('fontello.eot?8080531#iefix') format('embedded-opentype'),
|
||||
url('fontello.woff2?8080531') format('woff2'),
|
||||
url('fontello.woff?8080531') format('woff'),
|
||||
url('fontello.ttf?8080531') format('truetype'),
|
||||
url('fontello.svg?8080531#fontello') format('svg');
|
||||
src: url('fontello.eot?61363773');
|
||||
src: url('fontello.eot?61363773#iefix') format('embedded-opentype'),
|
||||
url('fontello.woff2?61363773') format('woff2'),
|
||||
url('fontello.woff?61363773') format('woff'),
|
||||
url('fontello.ttf?61363773') format('truetype'),
|
||||
url('fontello.svg?61363773#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@ -69,6 +69,7 @@
|
||||
.icon-clipboard-1:before { content: '\e80b'; } /* '' */
|
||||
.icon-login:before { content: '\e80c'; } /* '' */
|
||||
.icon-home:before { content: '\e80d'; } /* '' */
|
||||
.icon-download:before { content: '\e80e'; } /* '' */
|
||||
.icon-help-circled:before { content: '\e80f'; } /* '' */
|
||||
.icon-terminal:before { content: '\e810'; } /* '' */
|
||||
.icon-github-circled:before { content: '\f09b'; } /* '' */
|
||||
|
Binary file not shown.
@ -34,6 +34,8 @@
|
||||
|
||||
<glyph glyph-name="home" unicode="" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="download" unicode="" d="M714 100q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="help-circled" unicode="" d="M500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-13 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-15-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="terminal" unicode="" d="M929 779h-858c-39 0-71-32-71-72v-714c0-40 32-72 71-72h858c39 0 71 32 71 72v714c0 40-32 72-71 72z m-786-500l143 142-143 143 71 72 215-215-215-214-71 72z m571-72h-285v72h285v-72z" horiz-adv-x="1000" />
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,11 +16,19 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
||||
}
|
||||
|
||||
const album = await db.table('albums')
|
||||
.where({ identifier, enabled: 1 })
|
||||
.where({
|
||||
identifier,
|
||||
enabled: 1
|
||||
})
|
||||
.first()
|
||||
|
||||
if (!album) {
|
||||
return res.status(404).sendFile('404.html', { root: './pages/error/' })
|
||||
} 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')
|
||||
@ -61,7 +69,8 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
||||
thumb,
|
||||
files,
|
||||
identifier,
|
||||
enableDownload: Boolean(config.uploads.generateZips && config.uploads.generateZips.enabled),
|
||||
generateZips: config.uploads.generateZips && config.uploads.generateZips.enabled,
|
||||
downloadLink: album.download === 0 ? null : `../api/album/zip/${album.identifier}?v=${album.editedAt}`,
|
||||
editedAt: album.editedAt,
|
||||
url: `${homeDomain}/a/${album.identifier}`
|
||||
})
|
||||
|
@ -33,6 +33,7 @@ routes.get('/albums/:sidebar', (req, res, next) => albumsController.list(req, re
|
||||
routes.post('/albums', (req, res, next) => albumsController.create(req, res, next))
|
||||
routes.post('/albums/addfiles', (req, res, next) => albumsController.addFiles(req, res, next))
|
||||
routes.post('/albums/delete', (req, res, next) => albumsController.delete(req, res, next))
|
||||
routes.post('/albums/edit', (req, res, next) => albumsController.edit(req, res, next))
|
||||
routes.post('/albums/rename', (req, res, next) => albumsController.rename(req, res, next))
|
||||
routes.get('/albums/test', (req, res, next) => albumsController.test(req, res, next))
|
||||
routes.get('/tokens', (req, res, next) => tokenController.list(req, res, next))
|
||||
|
@ -12,7 +12,7 @@
|
||||
v1: CSS and JS files.
|
||||
v2: Images and config files (manifest.json, browserconfig.xml, etcetera).
|
||||
#}
|
||||
{% set v1 = "uuEpP64ca8" %}
|
||||
{% set v1 = "cDnmwkVVmk" %}
|
||||
{% set v2 = "MSEpgpfFIQ" %}
|
||||
|
||||
{#
|
||||
|
@ -41,15 +41,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if enableDownload and files.length -%}
|
||||
{% if generateZips and files.length -%}
|
||||
<div class="level-right">
|
||||
<p class="level-item">
|
||||
<a class="button is-primary is-outlined" title="Download album" href="../api/album/zip/{{ identifier }}?v={{ editedAt }}">Download Album</a>
|
||||
{% if downloadLink -%}
|
||||
<a class="button is-primary is-outlined" title="Download album" href="{{ downloadLink }}">Download album</a>
|
||||
{%- else -%}
|
||||
<a class="button is-primary is-outlined" title="Download disabled" disabled>Download disabled</a>
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
{%- endif %}
|
||||
</nav>
|
||||
{% if enableDownload and files.length -%}
|
||||
{% if generateZips and downloadLink and files.length -%}
|
||||
<article class="message">
|
||||
<div class="message-body">
|
||||
Album archives may be cached by CDN, if the one you downloaded seems outdated, you should try refreshing the page to get the latest version of the download link.
|
||||
|
@ -3,6 +3,7 @@
|
||||
{% block stylesheets %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="libs/fontello/fontello.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/sweetalert.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/auth.css?v={{ globals.v1 }}">
|
||||
{% endblock %}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
{% block stylesheets %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="libs/fontello/fontello.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/sweetalert.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard.css?v={{ globals.v1 }}">
|
||||
{% endblock %}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
{% block stylesheets %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="libs/fontello/fontello.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/sweetalert.css?v={{ globals.v1 }}">
|
||||
<link rel="stylesheet" type="text/css" href="css/home.css?v={{ globals.v1 }}">
|
||||
{% endblock %}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user