refactor: init UserError, a custom Error object

This will be used for errors that are to be delivered to users, AND not
to be logged into the server (as in it stacktraces and all).
This will eventually remove the need to throw string literals.

In this commit, this has only been implemented on albumsController.js,
but more will soon to come.
This commit is contained in:
Bobby Wibowo 2021-01-08 07:29:14 +07:00
parent 0dfdccb25e
commit ae31033c0c
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
3 changed files with 147 additions and 113 deletions

View File

@ -7,6 +7,8 @@ const paths = require('./pathsController')
const perms = require('./permissionController') const perms = require('./permissionController')
const uploadController = require('./uploadController') const uploadController = require('./uploadController')
const utils = require('./utilsController') const utils = require('./utilsController')
const apiErrorsHandler = require('./handlers/apiErrorsHandler.js')
const UserError = require('./utils/UserError')
const config = require('./../config') const config = require('./../config')
const logger = require('./../logger') const logger = require('./../logger')
const db = require('knex')(config.database) const db = require('knex')(config.database)
@ -66,10 +68,11 @@ self.getUniqueRandomName = async () => {
return identifier return identifier
} }
throw 'Sorry, we could not allocate a unique random identifier. Try again?' throw new UserError('Failed to allocate a unique identifier for the album. Try again?', 500)
} }
self.list = async (req, res, next) => { self.list = async (req, res, next) => {
try {
const user = await utils.authorize(req, res) const user = await utils.authorize(req, res)
if (!user) return if (!user) return
@ -87,7 +90,6 @@ self.list = async (req, res, next) => {
} }
} }
try {
// Query albums count for pagination // Query albums count for pagination
const count = await db.table('albums') const count = await db.table('albums')
.where(filter) .where(filter)
@ -164,12 +166,12 @@ self.list = async (req, res, next) => {
return res.json({ success: true, albums, count, users, homeDomain }) return res.json({ success: true, albums, count, users, homeDomain })
} catch (error) { } catch (error) {
logger.error(error) return apiErrorsHandler(error, req, res, next)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
} }
} }
self.create = async (req, res, next) => { self.create = async (req, res, next) => {
try {
const user = await utils.authorize(req, res) const user = await utils.authorize(req, res)
if (!user) return if (!user) return
@ -179,7 +181,6 @@ self.create = async (req, res, next) => {
if (!name) return res.json({ success: false, description: 'No album name specified.' }) if (!name) return res.json({ success: false, description: 'No album name specified.' })
try {
const album = await db.table('albums') const album = await db.table('albums')
.where({ .where({
name, name,
@ -211,8 +212,7 @@ self.create = async (req, res, next) => {
return res.json({ success: true, id: ids[0] }) return res.json({ success: true, id: ids[0] })
} catch (error) { } catch (error) {
logger.error(error) return apiErrorsHandler(error, req, res, next)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
} }
} }
@ -222,6 +222,7 @@ self.delete = async (req, res, next) => {
} }
self.disable = async (req, res, next) => { self.disable = async (req, res, next) => {
try {
const user = await utils.authorize(req, res) const user = await utils.authorize(req, res)
if (!user) return if (!user) return
@ -229,7 +230,6 @@ self.disable = async (req, res, next) => {
const purge = req.body.purge const purge = req.body.purge
if (!Number.isFinite(id)) return res.json({ success: false, description: 'No album specified.' }) if (!Number.isFinite(id)) return res.json({ success: false, description: 'No album specified.' })
try {
if (purge) { if (purge) {
const files = await db.table('files') const files = await db.table('files')
.where({ .where({
@ -264,17 +264,14 @@ self.disable = async (req, res, next) => {
.then(row => row.identifier) .then(row => row.identifier)
await paths.unlink(path.join(paths.zips, `${identifier}.zip`)) await paths.unlink(path.join(paths.zips, `${identifier}.zip`))
} catch (error) {
if (error && error.code !== 'ENOENT') {
logger.error(error)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
}
}
return res.json({ success: true }) return res.json({ success: true })
} catch (error) {
return apiErrorsHandler(error, req, res, next)
}
} }
self.edit = async (req, res, next) => { self.edit = async (req, res, next) => {
try {
const user = await utils.authorize(req, res) const user = await utils.authorize(req, res)
if (!user) return if (!user) return
@ -300,7 +297,6 @@ self.edit = async (req, res, next) => {
} }
} }
try {
const album = await db.table('albums') const album = await db.table('albums')
.where(filter) .where(filter)
.first() .first()
@ -358,8 +354,7 @@ self.edit = async (req, res, next) => {
return res.json({ success: true, name }) return res.json({ success: true, name })
} }
} catch (error) { } catch (error) {
logger.error(error) return apiErrorsHandler(error, req, res, next)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
} }
} }
@ -370,12 +365,12 @@ self.rename = async (req, res, next) => {
} }
self.get = async (req, res, next) => { self.get = async (req, res, next) => {
try {
const identifier = req.params.identifier const identifier = req.params.identifier
if (identifier === undefined) { if (identifier === undefined) {
return res.status(401).json({ success: false, description: 'No identifier provided.' }) return res.status(401).json({ success: false, description: 'No identifier provided.' })
} }
try {
const album = await db.table('albums') const album = await db.table('albums')
.where({ .where({
identifier, identifier,
@ -416,12 +411,12 @@ self.get = async (req, res, next) => {
files files
}) })
} catch (error) { } catch (error) {
logger.error(error) return apiErrorsHandler(error, req, res, next)
return res.status(500).json({ success: false, description: 'An unexpected error occcured. Try again?' })
} }
} }
self.generateZip = async (req, res, next) => { self.generateZip = async (req, res, next) => {
try {
const versionString = parseInt(req.query.v) const versionString = parseInt(req.query.v)
const identifier = req.params.identifier const identifier = req.params.identifier
@ -439,7 +434,6 @@ self.generateZip = async (req, res, next) => {
}) })
} }
try {
const album = await db.table('albums') const album = await db.table('albums')
.where({ .where({
identifier, identifier,
@ -547,8 +541,7 @@ self.generateZip = async (req, res, next) => {
self.zipEmitters.get(identifier).emit('done', filePath, fileName) self.zipEmitters.get(identifier).emit('done', filePath, fileName)
return res.download(filePath, fileName) return res.download(filePath, fileName)
} catch (error) { } catch (error) {
logger.error(error) return apiErrorsHandler(error, req, res, next)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
} }
} }
@ -589,20 +582,21 @@ self.listFiles = async (req, res, next) => {
} }
self.addFiles = async (req, res, next) => { self.addFiles = async (req, res, next) => {
let ids, albumid, failed, albumids
try {
const user = await utils.authorize(req, res) const user = await utils.authorize(req, res)
if (!user) return if (!user) return
const ids = req.body.ids ids = req.body.ids
if (!Array.isArray(ids) || !ids.length) { if (!Array.isArray(ids) || !ids.length) {
return res.json({ success: false, description: 'No files specified.' }) return res.json({ success: false, description: 'No files specified.' })
} }
let albumid = parseInt(req.body.albumid) albumid = parseInt(req.body.albumid)
if (isNaN(albumid) || albumid < 0) albumid = null if (isNaN(albumid) || albumid < 0) albumid = null
let failed = [] failed = []
const albumids = [] albumids = []
try {
if (albumid !== null) { if (albumid !== null) {
const album = await db.table('albums') const album = await db.table('albums')
.where('id', albumid) .where('id', albumid)
@ -646,14 +640,13 @@ self.addFiles = async (req, res, next) => {
return res.json({ success: true, failed }) return res.json({ success: true, failed })
} catch (error) { } catch (error) {
logger.error(error) if (Array.isArray(failed) && (failed.length === ids.length)) {
if (failed.length === ids.length) {
return res.json({ return res.json({
success: false, success: false,
description: `Could not ${albumid === null ? 'add' : 'remove'} any files ${albumid === null ? 'to' : 'from'} the album.` description: `Could not ${albumid === null ? 'add' : 'remove'} any files ${albumid === null ? 'to' : 'from'} the album.`
}) })
} else { } else {
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' }) return apiErrorsHandler(error, req, res, next)
} }
} }
} }

View File

@ -0,0 +1,30 @@
const UserError = require('./../utils/UserError')
const logger = require('./../../logger')
module.exports = (error, req, res, next) => {
if (!res) {
return logger.error(new Error('Missing "res" object.'))
}
// Intentional error messages to be delivered to users
const isUserError = error instanceof UserError
// ENOENT or missing file errors, typically harmless, so do not log stacktrace
const isENOENTError = error instanceof Error && error.code === 'ENOENT'
if (!isUserError && !isENOENTError) {
logger.error(error)
}
const statusCode = isUserError
? error.statusCode
: 500
const description = isUserError
? error.message
: 'An unexpected error occurred. Try again?'
return res
.status(statusCode)
.json({ success: false, description })
}

View File

@ -0,0 +1,11 @@
class UserError extends Error {
constructor (message, statusCode) {
super(message)
this.statusCode = statusCode !== undefined
? statusCode
: 400
}
}
module.exports = UserError