introduced new exception classes for structured error reporting

This commit is contained in:
zadam 2022-12-09 16:04:13 +01:00
parent eaf195e0c8
commit 342ae6e5e2
27 changed files with 123 additions and 64 deletions

1
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium",
"version": "0.57.3", "version": "0.57.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",

View File

@ -1329,7 +1329,7 @@ class Note extends AbstractEntity {
} }
isLaunchBarConfig() { isLaunchBarConfig() {
return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts']; return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
} }
isOptions() { isOptions() {

View File

@ -0,0 +1,7 @@
class NotFoundError {
constructor(message) {
this.message = message;
}
}
module.exports = NotFoundError;

View File

@ -0,0 +1,7 @@
class ValidationError {
constructor(message) {
this.message = message;
}
}
module.exports = ValidationError;

View File

@ -828,7 +828,7 @@ class NoteShort {
} }
isLaunchBarConfig() { isLaunchBarConfig() {
return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'].includes(this.noteId); return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
} }
isOptions() { isOptions() {

View File

@ -1,4 +1,5 @@
import utils from './utils.js'; import utils from './utils.js';
import ValidationError from "./validation_error.js";
const REQUEST_LOGGING_ENABLED = false; const REQUEST_LOGGING_ENABLED = false;
@ -102,10 +103,15 @@ async function call(method, url, data, headers = {}) {
return resp.body; return resp.body;
} }
async function reportError(method, url, status, error) { async function reportError(method, url, status, response) {
const message = "Error when calling " + method + " " + url + ": " + status + " - " + error;
const toastService = (await import("./toast.js")).default; 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.showError(message);
toastService.throwError(message); toastService.throwError(message);
} }

View File

@ -0,0 +1,7 @@
export default class ValidationError {
constructor(resp) {
for (const key in resp) {
this[key] = resp[key];
}
}
}

View File

@ -568,7 +568,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
$span.append($refreshSearchButton); $span.append($refreshSearchButton);
} }
if (!['search', 'launcher'].includes(note.type) && !note.isOptions()) { if (!['search', 'launcher'].includes(note.type) && !note.isOptions() && !note.isLaunchBarConfig()) {
const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>'); const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>');
$span.append($createChildNoteButton); $span.append($createChildNoteButton);

View File

@ -5,6 +5,8 @@ const log = require('../../services/log');
const attributeService = require('../../services/attributes'); const attributeService = require('../../services/attributes');
const Attribute = require('../../becca/entities/attribute'); const Attribute = require('../../becca/entities/attribute');
const becca = require("../../becca/becca"); 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) { function getEffectiveNoteAttributes(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNote(req.params.noteId);
@ -21,11 +23,11 @@ function updateNoteAttribute(req) {
attribute = becca.getAttribute(body.attributeId); attribute = becca.getAttribute(body.attributeId);
if (!attribute) { if (!attribute) {
return [404, `Attribute '${body.attributeId}' does not exist.`]; throw new NotFoundError(`Attribute '${body.attributeId}' does not exist.`);
} }
if (attribute.noteId !== noteId) { 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 if (body.type !== attribute.type
@ -106,7 +108,7 @@ function deleteNoteAttribute(req) {
if (attribute) { if (attribute) {
if (attribute.noteId !== noteId) { 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(); attribute.markAsDeleted();

View File

@ -9,6 +9,8 @@ const becca = require('../../becca/becca');
const TaskContext = require('../../services/task_context'); const TaskContext = require('../../services/task_context');
const branchService = require("../../services/branches"); const branchService = require("../../services/branches");
const log = require("../../services/log.js"); 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 * 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); const branchToMove = becca.getBranch(branchId);
if (!parentBranch || !branchToMove) { 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); return branchService.moveBranchToBranch(branchToMove, parentBranch, branchId);
@ -35,11 +37,11 @@ function moveBranchBeforeNote(req) {
const beforeBranch = becca.getBranch(beforeBranchId); const beforeBranch = becca.getBranch(beforeBranchId);
if (!branchToMove) { if (!branchToMove) {
return [404, `Can't find branch ${branchId}`]; throw new NotFoundError(`Can't find branch '${branchId}'`);
} }
if (!beforeBranch) { 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); const validationResult = treeService.validateParentChild(beforeBranch.parentNoteId, branchToMove.noteId, branchId);
@ -193,7 +195,7 @@ function deleteBranch(req) {
const branch = becca.getBranch(req.params.branchId); const branch = becca.getBranch(req.params.branchId);
if (!branch) { 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'); const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');

View File

@ -6,6 +6,7 @@ const opmlExportService = require('../../services/export/opml');
const becca = require('../../becca/becca'); const becca = require('../../becca/becca');
const TaskContext = require("../../services/task_context"); const TaskContext = require("../../services/task_context");
const log = require("../../services/log"); const log = require("../../services/log");
const NotFoundError = require("../../errors/not_found_error.js");
function exportBranch(req, res) { function exportBranch(req, res) {
const {branchId, type, format, version, taskId} = req.params; const {branchId, type, format, version, taskId} = req.params;
@ -34,11 +35,11 @@ function exportBranch(req, res) {
opmlExportService.exportToOpml(taskContext, branch, version, res); opmlExportService.exportToOpml(taskContext, branch, version, res);
} }
else { else {
return [404, "Unrecognized export format " + format]; throw new NotFoundError(`Unrecognized export format '${format}'`);
} }
} }
catch (e) { 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); taskContext.reportError(message);
log.error(message + e.stack); log.error(message + e.stack);

View File

@ -10,6 +10,7 @@ const { Readable } = require('stream');
const chokidar = require('chokidar'); const chokidar = require('chokidar');
const ws = require('../../services/ws'); const ws = require('../../services/ws');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error.js");
function updateFile(req) { function updateFile(req) {
const {noteId} = req.params; const {noteId} = req.params;
@ -18,7 +19,7 @@ function updateFile(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, `Note ${noteId} doesn't exist.`]; throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
} }
note.saveNoteRevision(); note.saveNoteRevision();
@ -116,7 +117,7 @@ function saveToTmpDir(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404,`Note ${noteId} doesn't exist.`]; throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
} }
const tmpObj = tmp.fileSync({postfix: getFilename(note)}); const tmpObj = tmp.fileSync({postfix: getFilename(note)});

View File

@ -4,6 +4,8 @@ const imageService = require('../../services/image');
const becca = require('../../becca/becca'); const becca = require('../../becca/becca');
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
const fs = require('fs'); 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) { function returnImage(req, res) {
const image = becca.getNote(req.params.noteId); const image = becca.getNote(req.params.noteId);
@ -51,11 +53,11 @@ function uploadImage(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { 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)) { 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); const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true, true);
@ -73,7 +75,7 @@ function updateImage(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { 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)) { if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {

View File

@ -10,6 +10,8 @@ const becca = require('../../becca/becca');
const beccaLoader = require('../../becca/becca_loader'); const beccaLoader = require('../../becca/becca_loader');
const log = require('../../services/log'); const log = require('../../services/log');
const TaskContext = require('../../services/task_context'); 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) { async function importToBranch(req) {
const {parentNoteId} = req.params; const {parentNoteId} = req.params;
@ -27,13 +29,13 @@ async function importToBranch(req) {
const file = req.file; const file = req.file;
if (!file) { if (!file) {
return [400, "No file has been uploaded"]; throw new ValidationError("No file has been uploaded");
} }
const parentNote = becca.getNote(parentNoteId); const parentNote = becca.getNote(parentNoteId);
if (!parentNote) { if (!parentNote) {
return [404, `Note ${parentNoteId} doesn't exist.`]; throw new NotFoundError(`Note '${parentNoteId}' doesn't exist.`);
} }
const extension = path.extname(file.originalname).toLowerCase(); const extension = path.extname(file.originalname).toLowerCase();

View File

@ -2,6 +2,7 @@
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const { JSDOM } = require("jsdom"); const { JSDOM } = require("jsdom");
const NotFoundError = require("../../errors/not_found_error.js");
function buildDescendantCountMap() { function buildDescendantCountMap() {
const noteIdToCountMap = {}; const noteIdToCountMap = {};
@ -326,7 +327,7 @@ function getBacklinkCount(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, "Not found"]; throw new NotFoundError(`Note '${noteId}' not found`);
} }
else { else {
return { return {
@ -340,7 +341,7 @@ function getBacklinks(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, `Note ${noteId} was not found`]; throw new NotFoundError(`Note '${noteId}' was not found`);
} }
let backlinksWithExcerptCount = 0; let backlinksWithExcerptCount = 0;

View File

@ -9,13 +9,15 @@ const TaskContext = require('../../services/task_context');
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
const fs = require('fs'); const fs = require('fs');
const becca = require("../../becca/becca"); 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) { function getNote(req) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, "Note " + noteId + " has not been found."]; throw new NotFoundError(`Note '${noteId}' has not been found.`);
} }
const pojo = note.getPojo(); const pojo = note.getPojo();
@ -197,11 +199,11 @@ function changeTitle(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, `Note '${noteId}' has not been found`]; throw new NotFoundError(`Note '${noteId}' has not been found`);
} }
if (!note.isContentAvailable()) { 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; const noteTitleChanged = note.title !== title;
@ -290,7 +292,7 @@ function uploadModifiedFile(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { 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}`); log.info(`Updating note '${noteId}' with content from ${filePath}`);
@ -300,7 +302,7 @@ function uploadModifiedFile(req) {
const fileContent = fs.readFileSync(filePath); const fileContent = fs.readFileSync(filePath);
if (!fileContent) { if (!fileContent) {
return [400, `File ${fileContent} is empty`]; throw new ValidationError(`File '${fileContent}' is empty`);
} }
note.setContent(fileContent); note.setContent(fileContent);
@ -311,11 +313,11 @@ function forceSaveNoteRevision(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, `Note ${noteId} not found.`]; throw new NotFoundError(`Note '${noteId}' not found.`);
} }
if (!note.isContentAvailable()) { 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(); note.saveNoteRevision();

View File

@ -3,6 +3,7 @@
const optionService = require('../../services/options'); const optionService = require('../../services/options');
const log = require('../../services/log'); const log = require('../../services/log');
const searchService = require('../../services/search/services/search'); 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 // options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = new Set([ const ALLOWED_OPTIONS = new Set([
@ -82,7 +83,7 @@ function updateOption(req) {
const {name, value} = req.params; const {name, value} = req.params;
if (!update(name, value)) { if (!update(name, value)) {
return [400, "not allowed option to change"]; throw new ValidationError("not allowed option to change");
} }
} }

View File

@ -1,6 +1,7 @@
"use strict"; "use strict";
const passwordService = require('../../services/password'); const passwordService = require('../../services/password');
const ValidationError = require("../../public/app/services/validation_error.js");
function changePassword(req) { function changePassword(req) {
if (passwordService.isPasswordSet()) { if (passwordService.isPasswordSet()) {
@ -14,7 +15,7 @@ function changePassword(req) {
function resetPassword(req) { function resetPassword(req) {
// protection against accidental call (not a security measure) // protection against accidental call (not a security measure)
if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") { if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") {
return [400, "Incorrect password reset confirmation"]; throw new ValidationError("Incorrect password reset confirmation");
} }
return passwordService.resetPassword(); return passwordService.resetPassword();

View File

@ -6,12 +6,14 @@ const searchService = require('../../services/search/services/search');
const bulkActionService = require("../../services/bulk_actions"); const bulkActionService = require("../../services/bulk_actions");
const cls = require("../../services/cls"); const cls = require("../../services/cls");
const {formatAttrForSearch} = require("../../services/attribute_formatter"); 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) { function searchFromNote(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNote(req.params.noteId);
if (!note) { 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) { if (note.isDeleted) {
@ -20,7 +22,7 @@ function searchFromNote(req) {
} }
if (note.type !== 'search') { 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); return searchService.searchFromNote(note);
@ -30,16 +32,16 @@ function searchAndExecute(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNote(req.params.noteId);
if (!note) { 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) { 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 []; return [];
} }
if (note.type !== 'search') { 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); const {searchResultNoteIds} = searchService.searchFromNote(note);

View File

@ -2,6 +2,7 @@
const similarityService = require('../../becca/similarity'); const similarityService = require('../../becca/similarity');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error.js");
async function getSimilarNotes(req) { async function getSimilarNotes(req) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
@ -9,7 +10,7 @@ async function getSimilarNotes(req) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) { if (!note) {
return [404, `Note ${noteId} not found.`]; throw new NotFoundError(`Note '${noteId}' not found.`);
} }
return await similarityService.findSimilarNotes(noteId); return await similarityService.findSimilarNotes(noteId);

View File

@ -2,6 +2,7 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error.js");
function getSchema() { function getSchema() {
const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`); 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); const note = becca.getNote(req.params.noteId);
if (!note) { 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---"); const queries = note.getContent().split("\n---");

View File

@ -1,5 +1,6 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const becca = require('../../becca/becca'); const becca = require('../../becca/becca');
const NotFoundError = require("../../errors/not_found_error.js");
function getNoteSize(req) { function getNoteSize(req) {
const {noteId} = req.params; const {noteId} = req.params;
@ -26,7 +27,7 @@ function getSubtreeSize(req) {
const note = becca.notes[noteId]; const note = becca.notes[noteId];
if (!note) { if (!note) {
return [404, `Note ${noteId} was not found.`]; throw new NotFoundError(`Note '${noteId}' was not found.`);
} }
const subTreeNoteIds = note.getSubtreeNoteIds(); const subTreeNoteIds = note.getSubtreeNoteIds();

View File

@ -2,6 +2,7 @@
const becca = require('../../becca/becca'); const becca = require('../../becca/becca');
const log = require('../../services/log'); const log = require('../../services/log');
const NotFoundError = require("../../errors/not_found_error.js");
function getNotesAndBranchesAndAttributes(noteIds) { function getNotesAndBranchesAndAttributes(noteIds) {
noteIds = new Set(noteIds); noteIds = new Set(noteIds);
@ -141,7 +142,7 @@ function getTree(req) {
} }
if (!(subTreeNoteId in becca.notes)) { 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]); collect(becca.notes[subTreeNoteId]);

View File

@ -6,6 +6,7 @@ const myScryptService = require('../services/my_scrypt');
const log = require('../services/log'); const log = require('../services/log');
const passwordService = require("../services/password"); const passwordService = require("../services/password");
const assetPath = require("../services/asset_path"); const assetPath = require("../services/asset_path");
const ValidationError = require("../public/app/services/validation_error.js");
function loginPage(req, res) { function loginPage(req, res) {
res.render('login', { res.render('login', {
@ -23,7 +24,7 @@ function setPasswordPage(req, res) {
function setPassword(req, res) { function setPassword(req, res) {
if (passwordService.isPasswordSet()) { if (passwordService.isPasswordSet()) {
return [400, "Password has been already set"]; throw new ValidationError("Password has been already set");
} }
let {password1, password2} = req.body; let {password1, password2} = req.body;

View File

@ -5,6 +5,7 @@ const loginRoute = require('./login');
const indexRoute = require('./index'); const indexRoute = require('./index');
const utils = require('../services/utils'); const utils = require('../services/utils');
const multer = require('multer'); const multer = require('multer');
const ValidationError = require("../errors/validation_error.js");
// API routes // API routes
const treeApiRoute = require('./api/tree'); const treeApiRoute = require('./api/tree');
@ -61,6 +62,7 @@ const csurf = require('csurf');
const {createPartialContentHandler} = require("express-partial-content"); const {createPartialContentHandler} = require("express-partial-content");
const rateLimit = require("express-rate-limit"); const rateLimit = require("express-rate-limit");
const AbstractEntity = require("../becca/entities/abstract_entity"); const AbstractEntity = require("../becca/entities/abstract_entity");
const NotFoundError = require("../errors/not_found_error.js");
const csrfMiddleware = csurf({ const csrfMiddleware = csurf({
cookie: true, cookie: true,
@ -169,13 +171,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
log.request(req, res, Date.now() - start, responseLength); log.request(req, res, Date.now() - start, responseLength);
}) })
.catch(e => { .catch(e => handleException(method, path, e, res));
log.error(`${method} ${path} threw exception: ` + e.stack);
res.setHeader("Content-Type", "text/plain")
.status(500)
.send(e.message);
});
} }
else { else {
const responseLength = resultHandler(req, res, result); const responseLength = resultHandler(req, res, result);
@ -185,13 +181,31 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
} }
} }
catch (e) { catch (e) {
handleException(method, path, e, res);
}
});
}
function handleException(method, path, e, res) {
log.error(`${method} ${path} threw exception: ` + e.stack); 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") res.setHeader("Content-Type", "text/plain")
.status(500) .status(500)
.send(e.message); .send(e.message);
} }
});
} }
const MAX_ALLOWED_FILE_SIZE_MB = 250; const MAX_ALLOWED_FILE_SIZE_MB = 250;

View File

@ -19,6 +19,7 @@ const Note = require('../becca/entities/note');
const Attribute = require('../becca/entities/attribute'); const Attribute = require('../becca/entities/attribute');
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const htmlSanitizer = require("./html_sanitizer.js"); const htmlSanitizer = require("./html_sanitizer.js");
const ValidationError = require("../errors/validation_error.js");
function getNewNotePosition(parentNoteId) { function getNewNotePosition(parentNoteId) {
const note = becca.notes[parentNoteId]; const note = becca.notes[parentNoteId];
@ -107,15 +108,11 @@ function getAndValidateParent(params) {
const parentNote = becca.notes[params.parentNoteId]; const parentNote = becca.notes[params.parentNoteId];
if (!parentNote) { if (!parentNote) {
throw new Error(`Parent note "${params.parentNoteId}" not found.`); throw new ValidationError(`Parent note "${params.parentNoteId}" not found.`);
}
if (parentNote.type === 'launcher') {
throw new Error(`Launchers should not have child notes.`);
} }
if (!params.ignoreForbiddenParents && (parentNote.isLaunchBarConfig() || parentNote.isOptions())) { 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; return parentNote;

View File

@ -6,7 +6,7 @@ const ws = require('./ws');
const taskContexts = {}; const taskContexts = {};
class TaskContext { class TaskContext {
constructor(taskId, taskType, data) { constructor(taskId, taskType, data = null) {
this.taskId = taskId; this.taskId = taskId;
this.taskType = taskType; this.taskType = taskType;
this.data = data; this.data = data;
@ -24,7 +24,7 @@ class TaskContext {
} }
/** @returns {TaskContext} */ /** @returns {TaskContext} */
static getInstance(taskId, taskType, data) { static getInstance(taskId, taskType, data = null) {
if (!taskContexts[taskId]) { if (!taskContexts[taskId]) {
taskContexts[taskId] = new TaskContext(taskId, taskType, data); taskContexts[taskId] = new TaskContext(taskId, taskType, data);
} }