From 342ae6e5e200a1d175bf8845ec46fab6f310df21 Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 9 Dec 2022 16:04:13 +0100 Subject: [PATCH] introduced new exception classes for structured error reporting --- package-lock.json | 1 - src/becca/entities/note.js | 2 +- src/errors/not_found_error.js | 7 ++++ src/errors/validation_error.js | 7 ++++ src/public/app/entities/note_short.js | 2 +- src/public/app/services/server.js | 12 +++++-- src/public/app/services/validation_error.js | 7 ++++ src/public/app/widgets/note_tree.js | 2 +- src/routes/api/attributes.js | 8 +++-- src/routes/api/branches.js | 10 +++--- src/routes/api/export.js | 5 +-- src/routes/api/files.js | 5 +-- src/routes/api/image.js | 8 +++-- src/routes/api/import.js | 6 ++-- src/routes/api/note_map.js | 5 +-- src/routes/api/notes.js | 16 +++++---- src/routes/api/options.js | 3 +- src/routes/api/password.js | 3 +- src/routes/api/search.js | 12 ++++--- src/routes/api/similar_notes.js | 3 +- src/routes/api/sql.js | 3 +- src/routes/api/stats.js | 3 +- src/routes/api/tree.js | 3 +- src/routes/login.js | 3 +- src/routes/routes.js | 38 ++++++++++++++------- src/services/notes.js | 9 ++--- src/services/task_context.js | 4 +-- 27 files changed, 123 insertions(+), 64 deletions(-) create mode 100644 src/errors/not_found_error.js create mode 100644 src/errors/validation_error.js create mode 100644 src/public/app/services/validation_error.js diff --git a/package-lock.json b/package-lock.json index 9da7d3d6c..3ea3c36e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "trilium", "version": "0.57.3", "hasInstallScript": true, "license": "AGPL-3.0-only", diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js index 4e2593c48..8693a4263 100644 --- a/src/becca/entities/note.js +++ b/src/becca/entities/note.js @@ -1329,7 +1329,7 @@ class Note extends AbstractEntity { } isLaunchBarConfig() { - return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts']; + return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId); } isOptions() { diff --git a/src/errors/not_found_error.js b/src/errors/not_found_error.js new file mode 100644 index 000000000..af746b82c --- /dev/null +++ b/src/errors/not_found_error.js @@ -0,0 +1,7 @@ +class NotFoundError { + constructor(message) { + this.message = message; + } +} + +module.exports = NotFoundError; \ No newline at end of file diff --git a/src/errors/validation_error.js b/src/errors/validation_error.js new file mode 100644 index 000000000..1c9425669 --- /dev/null +++ b/src/errors/validation_error.js @@ -0,0 +1,7 @@ +class ValidationError { + constructor(message) { + this.message = message; + } +} + +module.exports = ValidationError; \ No newline at end of file diff --git a/src/public/app/entities/note_short.js b/src/public/app/entities/note_short.js index 3f42c1c23..7207c04de 100644 --- a/src/public/app/entities/note_short.js +++ b/src/public/app/entities/note_short.js @@ -828,7 +828,7 @@ class NoteShort { } isLaunchBarConfig() { - return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'].includes(this.noteId); + return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId); } isOptions() { diff --git a/src/public/app/services/server.js b/src/public/app/services/server.js index e18663c61..feded7a6e 100644 --- a/src/public/app/services/server.js +++ b/src/public/app/services/server.js @@ -1,4 +1,5 @@ import utils from './utils.js'; +import ValidationError from "./validation_error.js"; const REQUEST_LOGGING_ENABLED = false; @@ -102,10 +103,15 @@ async function call(method, url, data, headers = {}) { return resp.body; } -async function reportError(method, url, status, error) { - const message = "Error when calling " + method + " " + url + ": " + status + " - " + error; - +async function reportError(method, url, status, response) { const toastService = (await import("./toast.js")).default; + + if ([400, 404].includes(status) && response && typeof response === 'object') { + toastService.showError(response.message); + throw new ValidationError(response); + } + + const message = "Error when calling " + method + " " + url + ": " + status + " - " + responseText; toastService.showError(message); toastService.throwError(message); } diff --git a/src/public/app/services/validation_error.js b/src/public/app/services/validation_error.js new file mode 100644 index 000000000..6d3423e6c --- /dev/null +++ b/src/public/app/services/validation_error.js @@ -0,0 +1,7 @@ +export default class ValidationError { + constructor(resp) { + for (const key in resp) { + this[key] = resp[key]; + } + } +} \ No newline at end of file diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 350431efc..b60e981ff 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -568,7 +568,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { $span.append($refreshSearchButton); } - if (!['search', 'launcher'].includes(note.type) && !note.isOptions()) { + if (!['search', 'launcher'].includes(note.type) && !note.isOptions() && !note.isLaunchBarConfig()) { const $createChildNoteButton = $(''); $span.append($createChildNoteButton); diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index 6a2269228..60339ad71 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -5,6 +5,8 @@ const log = require('../../services/log'); const attributeService = require('../../services/attributes'); const Attribute = require('../../becca/entities/attribute'); const becca = require("../../becca/becca"); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); function getEffectiveNoteAttributes(req) { const note = becca.getNote(req.params.noteId); @@ -21,11 +23,11 @@ function updateNoteAttribute(req) { attribute = becca.getAttribute(body.attributeId); if (!attribute) { - return [404, `Attribute '${body.attributeId}' does not exist.`]; + throw new NotFoundError(`Attribute '${body.attributeId}' does not exist.`); } if (attribute.noteId !== noteId) { - return [400, `Attribute '${body.attributeId}' is not owned by ${noteId}`]; + throw new ValidationError(`Attribute '${body.attributeId}' is not owned by ${noteId}`); } if (body.type !== attribute.type @@ -106,7 +108,7 @@ function deleteNoteAttribute(req) { if (attribute) { if (attribute.noteId !== noteId) { - return [400, `Attribute ${attributeId} is not owned by ${noteId}`]; + throw new ValidationError(`Attribute ${attributeId} is not owned by ${noteId}`); } attribute.markAsDeleted(); diff --git a/src/routes/api/branches.js b/src/routes/api/branches.js index a44a93f1e..8ef10b119 100644 --- a/src/routes/api/branches.js +++ b/src/routes/api/branches.js @@ -9,6 +9,8 @@ const becca = require('../../becca/becca'); const TaskContext = require('../../services/task_context'); const branchService = require("../../services/branches"); const log = require("../../services/log.js"); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); /** * Code in this file deals with moving and cloning branches. Relationship between note and parent note is unique @@ -22,7 +24,7 @@ function moveBranchToParent(req) { const branchToMove = becca.getBranch(branchId); if (!parentBranch || !branchToMove) { - return [400, `One or both branches ${branchId}, ${parentBranchId} have not been found`]; + throw new ValidationError(`One or both branches ${branchId}, ${parentBranchId} have not been found`); } return branchService.moveBranchToBranch(branchToMove, parentBranch, branchId); @@ -35,11 +37,11 @@ function moveBranchBeforeNote(req) { const beforeBranch = becca.getBranch(beforeBranchId); if (!branchToMove) { - return [404, `Can't find branch ${branchId}`]; + throw new NotFoundError(`Can't find branch '${branchId}'`); } if (!beforeBranch) { - return [404, `Can't find branch ${beforeBranchId}`]; + throw new NotFoundError(`Can't find branch '${beforeBranchId}'`); } const validationResult = treeService.validateParentChild(beforeBranch.parentNoteId, branchToMove.noteId, branchId); @@ -193,7 +195,7 @@ function deleteBranch(req) { const branch = becca.getBranch(req.params.branchId); if (!branch) { - return [404, `Branch ${req.params.branchId} not found`]; + throw new NotFoundError(`Branch '${req.params.branchId}' not found`); } const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes'); diff --git a/src/routes/api/export.js b/src/routes/api/export.js index 6acb19952..0a22233a8 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -6,6 +6,7 @@ const opmlExportService = require('../../services/export/opml'); const becca = require('../../becca/becca'); const TaskContext = require("../../services/task_context"); const log = require("../../services/log"); +const NotFoundError = require("../../errors/not_found_error.js"); function exportBranch(req, res) { const {branchId, type, format, version, taskId} = req.params; @@ -34,11 +35,11 @@ function exportBranch(req, res) { opmlExportService.exportToOpml(taskContext, branch, version, res); } else { - return [404, "Unrecognized export format " + format]; + throw new NotFoundError(`Unrecognized export format '${format}'`); } } catch (e) { - const message = "Export failed with following error: '" + e.message + "'. More details might be in the logs."; + const message = `Export failed with following error: '${e.message}'. More details might be in the logs.`; taskContext.reportError(message); log.error(message + e.stack); diff --git a/src/routes/api/files.js b/src/routes/api/files.js index fc7ae6591..3b9bef762 100644 --- a/src/routes/api/files.js +++ b/src/routes/api/files.js @@ -10,6 +10,7 @@ const { Readable } = require('stream'); const chokidar = require('chokidar'); const ws = require('../../services/ws'); const becca = require("../../becca/becca"); +const NotFoundError = require("../../errors/not_found_error.js"); function updateFile(req) { const {noteId} = req.params; @@ -18,7 +19,7 @@ function updateFile(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} doesn't exist.`]; + throw new NotFoundError(`Note '${noteId}' doesn't exist.`); } note.saveNoteRevision(); @@ -116,7 +117,7 @@ function saveToTmpDir(req) { const note = becca.getNote(noteId); if (!note) { - return [404,`Note ${noteId} doesn't exist.`]; + throw new NotFoundError(`Note '${noteId}' doesn't exist.`); } const tmpObj = tmp.fileSync({postfix: getFilename(note)}); diff --git a/src/routes/api/image.js b/src/routes/api/image.js index f54395697..619f7b21e 100644 --- a/src/routes/api/image.js +++ b/src/routes/api/image.js @@ -4,6 +4,8 @@ const imageService = require('../../services/image'); const becca = require('../../becca/becca'); const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; const fs = require('fs'); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); function returnImage(req, res) { const image = becca.getNote(req.params.noteId); @@ -51,11 +53,11 @@ function uploadImage(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} doesn't exist.`]; + throw new NotFoundError(`Note '${noteId}' doesn't exist.`); } if (!["image/png", "image/jpg", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) { - return [400, "Unknown image type: " + file.mimetype]; + throw new ValidationError(`Unknown image type: ${file.mimetype}`); } const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true, true); @@ -73,7 +75,7 @@ function updateImage(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} doesn't exist.`]; + throw new NotFoundError(`Note '${noteId}' doesn't exist.`); } if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) { diff --git a/src/routes/api/import.js b/src/routes/api/import.js index 80ecbb255..930758ce8 100644 --- a/src/routes/api/import.js +++ b/src/routes/api/import.js @@ -10,6 +10,8 @@ const becca = require('../../becca/becca'); const beccaLoader = require('../../becca/becca_loader'); const log = require('../../services/log'); const TaskContext = require('../../services/task_context'); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); async function importToBranch(req) { const {parentNoteId} = req.params; @@ -27,13 +29,13 @@ async function importToBranch(req) { const file = req.file; if (!file) { - return [400, "No file has been uploaded"]; + throw new ValidationError("No file has been uploaded"); } const parentNote = becca.getNote(parentNoteId); if (!parentNote) { - return [404, `Note ${parentNoteId} doesn't exist.`]; + throw new NotFoundError(`Note '${parentNoteId}' doesn't exist.`); } const extension = path.extname(file.originalname).toLowerCase(); diff --git a/src/routes/api/note_map.js b/src/routes/api/note_map.js index 09b593c7f..f9fe9bc44 100644 --- a/src/routes/api/note_map.js +++ b/src/routes/api/note_map.js @@ -2,6 +2,7 @@ const becca = require("../../becca/becca"); const { JSDOM } = require("jsdom"); +const NotFoundError = require("../../errors/not_found_error.js"); function buildDescendantCountMap() { const noteIdToCountMap = {}; @@ -326,7 +327,7 @@ function getBacklinkCount(req) { const note = becca.getNote(noteId); if (!note) { - return [404, "Not found"]; + throw new NotFoundError(`Note '${noteId}' not found`); } else { return { @@ -340,7 +341,7 @@ function getBacklinks(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} was not found`]; + throw new NotFoundError(`Note '${noteId}' was not found`); } let backlinksWithExcerptCount = 0; diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index ebb345623..1033d3663 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -9,13 +9,15 @@ const TaskContext = require('../../services/task_context'); const protectedSessionService = require('../../services/protected_session'); const fs = require('fs'); const becca = require("../../becca/becca"); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); function getNote(req) { const noteId = req.params.noteId; const note = becca.getNote(noteId); if (!note) { - return [404, "Note " + noteId + " has not been found."]; + throw new NotFoundError(`Note '${noteId}' has not been found.`); } const pojo = note.getPojo(); @@ -197,11 +199,11 @@ function changeTitle(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note '${noteId}' has not been found`]; + throw new NotFoundError(`Note '${noteId}' has not been found`); } if (!note.isContentAvailable()) { - return [400, `Note '${noteId}' is not available for change`]; + throw new ValidationError(`Note '${noteId}' is not available for change`); } const noteTitleChanged = note.title !== title; @@ -290,7 +292,7 @@ function uploadModifiedFile(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note '${noteId}' has not been found`]; + throw new NotFoundError(`Note '${noteId}' has not been found`); } log.info(`Updating note '${noteId}' with content from ${filePath}`); @@ -300,7 +302,7 @@ function uploadModifiedFile(req) { const fileContent = fs.readFileSync(filePath); if (!fileContent) { - return [400, `File ${fileContent} is empty`]; + throw new ValidationError(`File '${fileContent}' is empty`); } note.setContent(fileContent); @@ -311,11 +313,11 @@ function forceSaveNoteRevision(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} not found.`]; + throw new NotFoundError(`Note '${noteId}' not found.`); } if (!note.isContentAvailable()) { - return [400, `Note revision of a protected note cannot be created outside of a protected session.`]; + throw new ValidationError(`Note revision of a protected note cannot be created outside of a protected session.`); } note.saveNoteRevision(); diff --git a/src/routes/api/options.js b/src/routes/api/options.js index e8e7bfe08..4c2add9f3 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -3,6 +3,7 @@ const optionService = require('../../services/options'); const log = require('../../services/log'); const searchService = require('../../services/search/services/search'); +const ValidationError = require("../../public/app/services/validation_error.js"); // options allowed to be updated directly in options dialog const ALLOWED_OPTIONS = new Set([ @@ -82,7 +83,7 @@ function updateOption(req) { const {name, value} = req.params; if (!update(name, value)) { - return [400, "not allowed option to change"]; + throw new ValidationError("not allowed option to change"); } } diff --git a/src/routes/api/password.js b/src/routes/api/password.js index 47466bd7e..7fe1203cb 100644 --- a/src/routes/api/password.js +++ b/src/routes/api/password.js @@ -1,6 +1,7 @@ "use strict"; const passwordService = require('../../services/password'); +const ValidationError = require("../../public/app/services/validation_error.js"); function changePassword(req) { if (passwordService.isPasswordSet()) { @@ -14,7 +15,7 @@ function changePassword(req) { function resetPassword(req) { // protection against accidental call (not a security measure) if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") { - return [400, "Incorrect password reset confirmation"]; + throw new ValidationError("Incorrect password reset confirmation"); } return passwordService.resetPassword(); diff --git a/src/routes/api/search.js b/src/routes/api/search.js index 929cc7c05..8356b14f0 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -6,12 +6,14 @@ const searchService = require('../../services/search/services/search'); const bulkActionService = require("../../services/bulk_actions"); const cls = require("../../services/cls"); const {formatAttrForSearch} = require("../../services/attribute_formatter"); +const ValidationError = require("../../public/app/services/validation_error.js"); +const NotFoundError = require("../../errors/not_found_error.js"); function searchFromNote(req) { const note = becca.getNote(req.params.noteId); if (!note) { - return [404, `Note ${req.params.noteId} has not been found.`]; + throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`); } if (note.isDeleted) { @@ -20,7 +22,7 @@ function searchFromNote(req) { } if (note.type !== 'search') { - return [400, `Note ${req.params.noteId} is not a search note.`] + throw new ValidationError(`Note '${req.params.noteId}' is not a search note.`); } return searchService.searchFromNote(note); @@ -30,16 +32,16 @@ function searchAndExecute(req) { const note = becca.getNote(req.params.noteId); if (!note) { - return [404, `Note ${req.params.noteId} has not been found.`]; + throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`); } if (note.isDeleted) { - // this can be triggered from recent changes and it's harmless to return empty list rather than fail + // this can be triggered from recent changes, and it's harmless to return empty list rather than fail return []; } if (note.type !== 'search') { - return [400, `Note ${req.params.noteId} is not a search note.`] + throw new ValidationError(`Note '${req.params.noteId}' is not a search note.`); } const {searchResultNoteIds} = searchService.searchFromNote(note); diff --git a/src/routes/api/similar_notes.js b/src/routes/api/similar_notes.js index 348a3ddde..524bfc493 100644 --- a/src/routes/api/similar_notes.js +++ b/src/routes/api/similar_notes.js @@ -2,6 +2,7 @@ const similarityService = require('../../becca/similarity'); const becca = require("../../becca/becca"); +const NotFoundError = require("../../errors/not_found_error.js"); async function getSimilarNotes(req) { const noteId = req.params.noteId; @@ -9,7 +10,7 @@ async function getSimilarNotes(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} not found.`]; + throw new NotFoundError(`Note '${noteId}' not found.`); } return await similarityService.findSimilarNotes(noteId); diff --git a/src/routes/api/sql.js b/src/routes/api/sql.js index b20f566e7..25bf111f7 100644 --- a/src/routes/api/sql.js +++ b/src/routes/api/sql.js @@ -2,6 +2,7 @@ const sql = require('../../services/sql'); const becca = require("../../becca/becca"); +const NotFoundError = require("../../errors/not_found_error.js"); function getSchema() { const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`); @@ -21,7 +22,7 @@ function execute(req) { const note = becca.getNote(req.params.noteId); if (!note) { - return [404, `Note ${req.params.noteId} was not found.`]; + throw new NotFoundError(`Note '${req.params.noteId}' was not found.`); } const queries = note.getContent().split("\n---"); diff --git a/src/routes/api/stats.js b/src/routes/api/stats.js index b747a7e28..4276655eb 100644 --- a/src/routes/api/stats.js +++ b/src/routes/api/stats.js @@ -1,5 +1,6 @@ const sql = require('../../services/sql'); const becca = require('../../becca/becca'); +const NotFoundError = require("../../errors/not_found_error.js"); function getNoteSize(req) { const {noteId} = req.params; @@ -26,7 +27,7 @@ function getSubtreeSize(req) { const note = becca.notes[noteId]; if (!note) { - return [404, `Note ${noteId} was not found.`]; + throw new NotFoundError(`Note '${noteId}' was not found.`); } const subTreeNoteIds = note.getSubtreeNoteIds(); diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index 7874814c2..5f323570c 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -2,6 +2,7 @@ const becca = require('../../becca/becca'); const log = require('../../services/log'); +const NotFoundError = require("../../errors/not_found_error.js"); function getNotesAndBranchesAndAttributes(noteIds) { noteIds = new Set(noteIds); @@ -141,7 +142,7 @@ function getTree(req) { } if (!(subTreeNoteId in becca.notes)) { - return [404, `Note ${subTreeNoteId} not found in the cache`]; + throw new NotFoundError(`Note '${subTreeNoteId}' not found in the cache`); } collect(becca.notes[subTreeNoteId]); diff --git a/src/routes/login.js b/src/routes/login.js index bf7a44226..f00e455df 100644 --- a/src/routes/login.js +++ b/src/routes/login.js @@ -6,6 +6,7 @@ const myScryptService = require('../services/my_scrypt'); const log = require('../services/log'); const passwordService = require("../services/password"); const assetPath = require("../services/asset_path"); +const ValidationError = require("../public/app/services/validation_error.js"); function loginPage(req, res) { res.render('login', { @@ -23,7 +24,7 @@ function setPasswordPage(req, res) { function setPassword(req, res) { if (passwordService.isPasswordSet()) { - return [400, "Password has been already set"]; + throw new ValidationError("Password has been already set"); } let {password1, password2} = req.body; diff --git a/src/routes/routes.js b/src/routes/routes.js index 05ad3e430..bb3f1d044 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -5,6 +5,7 @@ const loginRoute = require('./login'); const indexRoute = require('./index'); const utils = require('../services/utils'); const multer = require('multer'); +const ValidationError = require("../errors/validation_error.js"); // API routes const treeApiRoute = require('./api/tree'); @@ -61,6 +62,7 @@ const csurf = require('csurf'); const {createPartialContentHandler} = require("express-partial-content"); const rateLimit = require("express-rate-limit"); const AbstractEntity = require("../becca/entities/abstract_entity"); +const NotFoundError = require("../errors/not_found_error.js"); const csrfMiddleware = csurf({ cookie: true, @@ -169,13 +171,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio log.request(req, res, Date.now() - start, responseLength); }) - .catch(e => { - log.error(`${method} ${path} threw exception: ` + e.stack); - - res.setHeader("Content-Type", "text/plain") - .status(500) - .send(e.message); - }); + .catch(e => handleException(method, path, e, res)); } else { const responseLength = resultHandler(req, res, result); @@ -185,15 +181,33 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio } } catch (e) { - log.error(`${method} ${path} threw exception: ` + e.stack); - - res.setHeader("Content-Type", "text/plain") - .status(500) - .send(e.message); + handleException(method, path, e, res); } }); } +function handleException(method, path, e, res) { + log.error(`${method} ${path} threw exception: ` + e.stack); + + if (e instanceof ValidationError) { + res.setHeader("Content-Type", "application/json") + .status(400) + .send({ + message: e.message + }); + } if (e instanceof NotFoundError) { + res.setHeader("Content-Type", "application/json") + .status(404) + .send({ + message: e.message + }); + } else { + res.setHeader("Content-Type", "text/plain") + .status(500) + .send(e.message); + } +} + const MAX_ALLOWED_FILE_SIZE_MB = 250; const GET = 'get', POST = 'post', PUT = 'put', PATCH = 'patch', DELETE = 'delete'; diff --git a/src/services/notes.js b/src/services/notes.js index 462e22910..72fb46383 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -19,6 +19,7 @@ const Note = require('../becca/entities/note'); const Attribute = require('../becca/entities/attribute'); const dayjs = require("dayjs"); const htmlSanitizer = require("./html_sanitizer.js"); +const ValidationError = require("../errors/validation_error.js"); function getNewNotePosition(parentNoteId) { const note = becca.notes[parentNoteId]; @@ -107,15 +108,11 @@ function getAndValidateParent(params) { const parentNote = becca.notes[params.parentNoteId]; if (!parentNote) { - throw new Error(`Parent note "${params.parentNoteId}" not found.`); - } - - if (parentNote.type === 'launcher') { - throw new Error(`Launchers should not have child notes.`); + throw new ValidationError(`Parent note "${params.parentNoteId}" not found.`); } if (!params.ignoreForbiddenParents && (parentNote.isLaunchBarConfig() || parentNote.isOptions())) { - throw new Error(`Creating child notes into '${parentNote.noteId}' is not allowed.`); + throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`); } return parentNote; diff --git a/src/services/task_context.js b/src/services/task_context.js index d59bc96b3..f86b6fd59 100644 --- a/src/services/task_context.js +++ b/src/services/task_context.js @@ -6,7 +6,7 @@ const ws = require('./ws'); const taskContexts = {}; class TaskContext { - constructor(taskId, taskType, data) { + constructor(taskId, taskType, data = null) { this.taskId = taskId; this.taskType = taskType; this.data = data; @@ -24,7 +24,7 @@ class TaskContext { } /** @returns {TaskContext} */ - static getInstance(taskId, taskType, data) { + static getInstance(taskId, taskType, data = null) { if (!taskContexts[taskId]) { taskContexts[taskId] = new TaskContext(taskId, taskType, data); }