refactoring + uploading attachments WIP

This commit is contained in:
zadam 2023-05-08 00:02:08 +02:00
parent b05ce12e7b
commit 626af84f42
23 changed files with 257 additions and 199 deletions

View File

@ -3,6 +3,7 @@
const sql = require("../services/sql"); const sql = require("../services/sql");
const NoteSet = require("../services/search/note_set"); const NoteSet = require("../services/search/note_set");
const BAttachment = require("./entities/battachment.js"); const BAttachment = require("./entities/battachment.js");
const NotFoundError = require("../errors/not_found_error.js");
/** /**
* Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca. * Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca.
@ -78,6 +79,16 @@ class Becca {
return this.notes[noteId]; return this.notes[noteId];
} }
/** @returns {BNote|null} */
getNoteOrThrow(noteId) {
const note = this.notes[noteId];
if (!note) {
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
return note;
}
/** @returns {BNote[]} */ /** @returns {BNote[]} */
getNotes(noteIds, ignoreMissing = false) { getNotes(noteIds, ignoreMissing = false) {
const filteredNotes = []; const filteredNotes = [];
@ -104,11 +115,30 @@ class Becca {
return this.branches[branchId]; return this.branches[branchId];
} }
/** @returns {BBranch|null} */
getBranchOrThrow(branchId) {
const branch = this.getBranch(branchId);
if (!branch) {
throw new NotFoundError(`Branch '${branchId}' was not found in becca.`);
}
return branch;
}
/** @returns {BAttribute|null} */ /** @returns {BAttribute|null} */
getAttribute(attributeId) { getAttribute(attributeId) {
return this.attributes[attributeId]; return this.attributes[attributeId];
} }
/** @returns {BAttribute} */
getAttributeOrThrow(attributeId) {
const attribute = this.getAttribute(attributeId);
if (!attribute) {
throw new NotFoundError(`Attribute '${attributeId}' does not exist.`);
}
return attribute;
}
/** @returns {BBranch|null} */ /** @returns {BBranch|null} */
getBranchFromChildAndParent(childNoteId, parentNoteId) { getBranchFromChildAndParent(childNoteId, parentNoteId) {
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
@ -130,6 +160,15 @@ class Becca {
return row ? new BAttachment(row) : null; return row ? new BAttachment(row) : null;
} }
/** @returns {BAttachment} */
getAttachmentOrThrow(attachmentId) {
const attachment = this.getAttachment(attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
}
return attachment;
}
/** @returns {BAttachment[]} */ /** @returns {BAttachment[]} */
getAttachments(attachmentIds) { getAttachments(attachmentIds) {
const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems

View File

@ -27,7 +27,7 @@ function load() {
const start = Date.now(); const start = Date.now();
becca.reset(); becca.reset();
// using raw query and passing arrays to avoid allocating new objects // using a raw query and passing arrays to avoid allocating new objects,
// this is worth it for becca load since it happens every run and blocks the app until finished // this is worth it for becca load since it happens every run and blocks the app until finished
for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`)) { for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`)) {

View File

@ -11,7 +11,6 @@ const BAttachment = require("./battachment");
const TaskContext = require("../../services/task_context"); const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc'); const utc = require('dayjs/plugin/utc');
const NotFoundError = require("../../errors/not_found_error.js");
const eventService = require("../../services/events.js"); const eventService = require("../../services/events.js");
dayjs.extend(utc); dayjs.extend(utc);
@ -1622,11 +1621,7 @@ class BNote extends AbstractBeccaEntity {
let attachment; let attachment;
if (attachmentId) { if (attachmentId) {
attachment = this.becca.getAttachment(attachmentId); attachment = this.becca.getAttachmentOrThrow(attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
}
} else { } else {
attachment = new BAttachment({ attachment = new BAttachment({
parentId: this.noteId, parentId: this.noteId,

View File

@ -4,7 +4,11 @@ import ws from "./ws.js";
import utils from "./utils.js"; import utils from "./utils.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
export async function uploadFiles(parentNoteId, files, options) { export async function uploadFiles(entityType, parentNoteId, files, options) {
if (!['notes', 'attachments'].includes(entityType)) {
throw new Error(`Unrecognized import entity type '${entityType}'.`);
}
if (files.length === 0) { if (files.length === 0) {
return; return;
} }
@ -25,7 +29,7 @@ export async function uploadFiles(parentNoteId, files, options) {
} }
await $.ajax({ await $.ajax({
url: `${window.glob.baseApiUrl}notes/${parentNoteId}/import`, url: `${window.glob.baseApiUrl}notes/${parentNoteId}/${entityType}-import`,
headers: await server.getHeaders(), headers: await server.getHeaders(),
data: formData, data: formData,
dataType: 'json', dataType: 'json',

View File

@ -154,6 +154,6 @@ export default class ImportDialog extends BasicWidget {
this.$widget.modal('hide'); this.$widget.modal('hide');
await importService.uploadFiles(parentNoteId, files, options); await importService.uploadFiles('notes', parentNoteId, files, options);
} }
} }

View File

@ -0,0 +1,107 @@
import utils from '../../services/utils.js';
import treeService from "../../services/tree.js";
import importService from "../../services/import.js";
import options from "../../services/options.js";
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="upload-attachments-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Import into note</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="upload-attachment-form">
<div class="modal-body">
<div class="form-group">
<label for="upload-attachment-file-upload-input"><strong>Choose files</strong></label>
<input type="file" class="upload-attachment-file-upload-input form-control-file" multiple />
<p>Content of the file will be imported as child note(s) into <strong class="upload-attachment-note-title"></strong>.
</div>
<div class="form-group">
<strong>Options:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="<p>If you check this option, Trilium will attempt to shrink the uploaded images by scaling and optimization which may affect the perceived image quality. If unchecked, images will be uploaded without changes.</p>">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>Shrink images</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="upload-attachment-button btn btn-primary">Upload</button>
</div>
</form>
</div>
</div>
</div>`;
export default class UploadAttachmentsDialog extends BasicWidget {
constructor() {
super();
this.parentNoteId = null;
}
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".upload-attachment-form");
this.$noteTitle = this.$widget.find(".upload-attachment-note-title");
this.$fileUploadInput = this.$widget.find(".upload-attachment-file-upload-input");
this.$uploadButton = this.$widget.find(".upload-attachment-button");
this.$shrinkImagesCheckbox = this.$widget.find(".shrink-images-checkbox");
this.$form.on('submit', () => {
// disabling so that import is not triggered again.
this.$uploadButton.attr("disabled", "disabled");
this.uploadAttachments(this.parentNoteId);
return false;
});
this.$fileUploadInput.on('change', () => {
if (this.$fileUploadInput.val()) {
this.$uploadButton.removeAttr("disabled");
}
else {
this.$uploadButton.attr("disabled", "disabled");
}
});
this.$widget.find('[data-toggle="tooltip"]').tooltip({
html: true
});
}
async showUploadAttachmentsDialogEvent({noteId}) {
this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger upload button disabling listener below
this.$shrinkImagesCheckbox.prop("checked", options.is('compressImages'));
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
utils.openDialog(this.$widget);
}
async uploadAttachments(parentNoteId) {
const files = Array.from(this.$fileUploadInput[0].files); // shallow copy since we're resetting the upload button below
const boolToString = $el => $el.is(":checked") ? "true" : "false";
const options = {
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
};
this.$widget.modal('hide');
await importService.uploadFiles('attachments', parentNoteId, files, options);
}
}

View File

@ -116,7 +116,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
const importService = await import('../services/import.js'); const importService = await import('../services/import.js');
importService.uploadFiles(activeNote.noteId, files, { importService.uploadFiles('notes', activeNote.noteId, files, {
safeImport: true, safeImport: true,
shrinkImages: true, shrinkImages: true,
textImportedAsText: true, textImportedAsText: true,

View File

@ -442,7 +442,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const importService = await import('../services/import.js'); const importService = await import('../services/import.js');
importService.uploadFiles(node.data.noteId, files, { importService.uploadFiles('notes', node.data.noteId, files, {
safeImport: true, safeImport: true,
shrinkImages: true, shrinkImages: true,
textImportedAsText: true, textImportedAsText: true,

View File

@ -37,7 +37,10 @@ export default class AttachmentListTypeWidget extends TypeWidget {
async doRefresh(note) { async doRefresh(note) {
this.$linksWrapper.append( this.$linksWrapper.append(
"Owning note: ", "Owning note: ",
await linkService.createNoteLink(this.noteId) await linkService.createNoteLink(this.noteId),
$('<button class="btn btn-sm">')
.text("Upload attachments")
.on('click', () => this.triggerCommand("showUploadAttachmentsDialog", {noteId: this.noteId}))
); );
this.$list.empty(); this.$list.empty();

View File

@ -1,5 +1,4 @@
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error");
const utils = require("../../services/utils"); const utils = require("../../services/utils");
const blobService = require("../../services/blob.js"); const blobService = require("../../services/blob.js");
@ -11,13 +10,7 @@ function getAttachmentBlob(req) {
function getAttachments(req) { function getAttachments(req) {
const includeContent = req.query.includeContent === 'true'; const includeContent = req.query.includeContent === 'true';
const {noteId} = req.params; const note = becca.getNoteOrThrow(req.params.noteId);
const note = becca.getNote(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
return note.getAttachments() return note.getAttachments()
.map(attachment => processAttachment(attachment, includeContent)); .map(attachment => processAttachment(attachment, includeContent));
@ -27,11 +20,7 @@ function getAttachment(req) {
const includeContent = req.query.includeContent === 'true'; const includeContent = req.query.includeContent === 'true';
const {attachmentId} = req.params; const {attachmentId} = req.params;
const attachment = becca.getAttachment(attachmentId); const attachment = becca.getAttachmentOrThrow(attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' doesn't exist.`);
}
return processAttachment(attachment, includeContent); return processAttachment(attachment, includeContent);
} }
@ -62,12 +51,7 @@ function saveAttachment(req) {
const {noteId} = req.params; const {noteId} = req.params;
const {attachmentId, role, mime, title, content} = req.body; const {attachmentId, role, mime, title, content} = req.body;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
note.saveAttachment({attachmentId, role, mime, title, content}); note.saveAttachment({attachmentId, role, mime, title, content});
} }
@ -84,12 +68,7 @@ function deleteAttachment(req) {
function convertAttachmentToNote(req) { function convertAttachmentToNote(req) {
const {attachmentId} = req.params; const {attachmentId} = req.params;
const attachment = becca.getAttachment(attachmentId); const attachment = becca.getAttachmentOrThrow(attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' doesn't exist.`);
}
return attachment.convertToNote(); return attachment.convertToNote();
} }

View File

@ -6,7 +6,6 @@ const attributeService = require('../../services/attributes');
const BAttribute = require('../../becca/entities/battribute'); const BAttribute = require('../../becca/entities/battribute');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const ValidationError = require("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function getEffectiveNoteAttributes(req) { function getEffectiveNoteAttributes(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNote(req.params.noteId);
@ -20,11 +19,7 @@ function updateNoteAttribute(req) {
let attribute; let attribute;
if (body.attributeId) { if (body.attributeId) {
attribute = becca.getAttribute(body.attributeId); attribute = becca.getAttributeOrThrow(body.attributeId);
if (!attribute) {
throw new NotFoundError(`Attribute '${body.attributeId}' does not exist.`);
}
if (attribute.noteId !== noteId) { if (attribute.noteId !== noteId) {
throw new ValidationError(`Attribute '${body.attributeId}' is not owned by ${noteId}`); throw new ValidationError(`Attribute '${body.attributeId}' is not owned by ${noteId}`);

View File

@ -10,7 +10,6 @@ const TaskContext = require('../../services/task_context');
const branchService = require("../../services/branches"); const branchService = require("../../services/branches");
const log = require("../../services/log"); const log = require("../../services/log");
const ValidationError = require("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
/** /**
* Code in this file deals with moving and cloning branches. The relationship between note and parent note is unique * Code in this file deals with moving and cloning branches. The relationship between note and parent note is unique
@ -33,16 +32,8 @@ function moveBranchToParent(req) {
function moveBranchBeforeNote(req) { function moveBranchBeforeNote(req) {
const {branchId, beforeBranchId} = req.params; const {branchId, beforeBranchId} = req.params;
const branchToMove = becca.getBranch(branchId); const branchToMove = becca.getBranchOrThrow(branchId);
const beforeBranch = becca.getBranch(beforeBranchId); const beforeBranch = becca.getBranchOrThrow(beforeBranchId);
if (!branchToMove) {
throw new NotFoundError(`Can't find branch '${branchId}'`);
}
if (!beforeBranch) {
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);
@ -192,11 +183,7 @@ function setExpandedForSubtree(req) {
function deleteBranch(req) { function deleteBranch(req) {
const last = req.query.last === 'true'; const last = req.query.last === 'true';
const eraseNotes = req.query.eraseNotes === 'true'; const eraseNotes = req.query.eraseNotes === 'true';
const branch = becca.getBranch(req.params.branchId); const branch = becca.getBranchOrThrow(req.params.branchId);
if (!branch) {
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

@ -10,14 +10,10 @@ 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");
const ValidationError = require("../../errors/validation_error.js"); const ValidationError = require("../../errors/validation_error.js");
function updateFile(req) { function updateFile(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
throw new NotFoundError(`Note '${req.params.noteId}' doesn't exist.`);
}
const file = req.file; const file = req.file;
note.saveNoteRevision(); note.saveNoteRevision();
@ -37,11 +33,7 @@ function updateFile(req) {
} }
function updateAttachment(req) { function updateAttachment(req) {
const attachment = becca.getAttachment(req.params.attachmentId); const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${req.params.attachmentId}' doesn't exist.`);
}
const file = req.file; const file = req.file;
attachment.getNote().saveNoteRevision(); attachment.getNote().saveNoteRevision();
@ -107,20 +99,14 @@ const openAttachment = (req, res) => downloadAttachmentInt(req.params.attachment
function fileContentProvider(req) { function fileContentProvider(req) {
// Read the file name from route params. // Read the file name from route params.
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
throw new NotFoundError(`Note '${req.params.noteId}' doesn't exist.`);
}
return streamContent(note.getContent(), note.getFileName(), note.mime); return streamContent(note.getContent(), note.getFileName(), note.mime);
} }
function attachmentContentProvider(req) { function attachmentContentProvider(req) {
// Read the file name from route params. // Read the file name from route params.
const attachment = becca.getAttachment(req.params.attachmentId); const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${req.params.attachmentId}' doesn't exist.`);
}
return streamContent(attachment.getContent(), attachment.getFileName(), attachment.mime); return streamContent(attachment.getContent(), attachment.getFileName(), attachment.mime);
} }
@ -152,11 +138,7 @@ function streamContent(content, fileName, mimeType) {
} }
function saveNoteToTmpDir(req) { function saveNoteToTmpDir(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
throw new NotFoundError(`Note '${req.params.noteId}' doesn't exist.`);
}
const fileName = note.getFileName(); const fileName = note.getFileName();
const content = note.getContent(); const content = note.getContent();
@ -164,11 +146,7 @@ function saveNoteToTmpDir(req) {
} }
function saveAttachmentToTmpDir(req) { function saveAttachmentToTmpDir(req) {
const attachment = becca.getAttachment(req.params.attachmentId); const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${req.params.attachmentId}' doesn't exist.`);
}
const fileName = attachment.getFileName(); const fileName = attachment.getFileName();
const content = attachment.getContent(); const content = attachment.getContent();
@ -204,11 +182,7 @@ function uploadModifiedFileToNote(req) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const {filePath} = req.body; const {filePath} = req.body;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
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}'`);
@ -227,11 +201,7 @@ function uploadModifiedFileToAttachment(req) {
const {attachmentId} = req.params; const {attachmentId} = req.params;
const {filePath} = req.body; const {filePath} = req.body;
const attachment = becca.getAttachment(attachmentId); const attachment = becca.getAttachmentOrThrow(attachmentId);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' has not been found`);
}
log.info(`Updating attachment '${attachmentId}' with content from '${filePath}'`); log.info(`Updating attachment '${attachmentId}' with content from '${filePath}'`);

View File

@ -5,7 +5,6 @@ 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("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function returnImage(req, res) { function returnImage(req, res) {
const image = becca.getNote(req.params.noteId); const image = becca.getNote(req.params.noteId);
@ -64,11 +63,7 @@ function uploadImage(req) {
const {noteId} = req.query; const {noteId} = req.query;
const {file} = req; const {file} = req;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
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)) {
throw new ValidationError(`Unknown image type '${file.mimetype}'`); throw new ValidationError(`Unknown image type '${file.mimetype}'`);
@ -86,11 +81,7 @@ function updateImage(req) {
const {noteId} = req.params; const {noteId} = req.params;
const {file} = req; const {file} = req;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
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)) {
return { return {

View File

@ -11,9 +11,8 @@ 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("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
async function importToBranch(req) { async function importNotesToBranch(req) {
const {parentNoteId} = req.params; const {parentNoteId} = req.params;
const {taskId, last} = req.body; const {taskId, last} = req.body;
@ -32,11 +31,7 @@ async function importToBranch(req) {
throw new ValidationError("No file has been uploaded"); throw new ValidationError("No file has been uploaded");
} }
const parentNote = becca.getNote(parentNoteId); const parentNote = becca.getNoteOrThrow(parentNoteId);
if (!parentNote) {
throw new NotFoundError(`Note '${parentNoteId}' doesn't exist.`);
}
const extension = path.extname(file.originalname).toLowerCase(); const extension = path.extname(file.originalname).toLowerCase();
@ -79,14 +74,68 @@ async function importToBranch(req) {
}), 1000); }), 1000);
} }
// import has deactivated note events so becca is not updated // import has deactivated note events so becca is not updated, instead we force it to reload
// instead we force it to reload (can be async) beccaLoader.load();
return note.getPojo();
}
async function importAttachmentsToNote(req) {
const {parentNoteId} = req.params;
const {taskId, last} = req.body;
const options = {
shrinkImages: req.body.shrinkImages !== 'false',
};
const file = req.file;
if (!file) {
throw new ValidationError("No file has been uploaded");
}
const parentNote = becca.getNoteOrThrow(parentNoteId);
// running all the event handlers on imported notes (and attributes) is slow
// and may produce unintended consequences
cls.disableEntityEvents();
// eliminate flickering during import
cls.ignoreEntityChangeIds();
let note; // typically root of the import - client can show it after finishing the import
const taskContext = TaskContext.getInstance(taskId, 'import', options);
try {
// FIXME
note = await singleImportService.importSingleFile(taskContext, file, parentNote);
}
catch (e) {
const message = `Import failed with following error: '${e.message}'. More details might be in the logs.`;
taskContext.reportError(message);
log.error(message + e.stack);
return [500, message];
}
if (last === "true") {
// small timeout to avoid race condition (the message is received before the transaction is committed)
setTimeout(() => taskContext.taskSucceeded({
parentNoteId: parentNoteId,
importedNoteId: note.noteId
}), 1000);
}
// import has deactivated note events so becca is not updated, instead we force it to reload
beccaLoader.load(); beccaLoader.load();
return note.getPojo(); return note.getPojo();
} }
module.exports = { module.exports = {
importToBranch importNotesToBranch,
importAttachmentsToNote
}; };

View File

@ -2,7 +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");
function buildDescendantCountMap(noteIdsToCount) { function buildDescendantCountMap(noteIdsToCount) {
if (!Array.isArray(noteIdsToCount)) { if (!Array.isArray(noteIdsToCount)) {
throw new Error('noteIdsToCount: type error'); throw new Error('noteIdsToCount: type error');
@ -345,25 +345,16 @@ function getFilteredBacklinks(note) {
function getBacklinkCount(req) { function getBacklinkCount(req) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) { return {
throw new NotFoundError(`Note '${noteId}' not found`); count: getFilteredBacklinks(note).length
} };
else {
return {
count: getFilteredBacklinks(note).length
};
}
} }
function getBacklinks(req) { function getBacklinks(req) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' was not found`);
}
let backlinksWithExcerptCount = 0; let backlinksWithExcerptCount = 0;

View File

@ -8,16 +8,10 @@ const log = require('../../services/log');
const TaskContext = require('../../services/task_context'); const TaskContext = require('../../services/task_context');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const ValidationError = require("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
const blobService = require("../../services/blob"); const blobService = require("../../services/blob");
function getNote(req) { function getNote(req) {
const note = becca.getNote(req.params.noteId); return becca.getNoteOrThrow(req.params.noteId);
if (!note) {
throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`);
}
return note;
} }
function getNoteBlob(req) { function getNoteBlob(req) {
@ -27,11 +21,7 @@ function getNoteBlob(req) {
} }
function getNoteMetadata(req) { function getNoteMetadata(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`);
}
const contentMetadata = note.getContentMetadata(); const contentMetadata = note.getContentMetadata();
return { return {
@ -132,11 +122,7 @@ function changeTitle(req) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const title = req.body.title; const title = req.body.title;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' has not been found`);
}
if (!note.isContentAvailable()) { if (!note.isContentAvailable()) {
throw new ValidationError(`Note '${noteId}' is not available for change`); throw new ValidationError(`Note '${noteId}' is not available for change`);
@ -232,11 +218,7 @@ function getDeleteNotesPreview(req) {
function forceSaveNoteRevision(req) { function forceSaveNoteRevision(req) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' not found.`);
}
if (!note.isContentAvailable()) { if (!note.isContentAvailable()) {
throw new ValidationError(`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.`);
@ -247,11 +229,7 @@ function forceSaveNoteRevision(req) {
function convertNoteToAttachment(req) { function convertNoteToAttachment(req) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' not found.`);
}
return { return {
attachment: note.convertToParentAttachment({ force: true }) attachment: note.convertToParentAttachment({ force: true })

View File

@ -7,14 +7,9 @@ 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("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function searchFromNote(req) { function searchFromNote(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
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
@ -29,11 +24,7 @@ function searchFromNote(req) {
} }
function searchAndExecute(req) { function searchAndExecute(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
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

View File

@ -2,16 +2,11 @@
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");
async function getSimilarNotes(req) { async function getSimilarNotes(req) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Note '${noteId}' not found.`);
}
return await similarityService.findSimilarNotes(noteId); return await similarityService.findSimilarNotes(noteId);
} }

View File

@ -2,7 +2,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");
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`);
@ -19,11 +18,7 @@ function getSchema() {
} }
function execute(req) { function execute(req) {
const note = becca.getNote(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
if (!note) {
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,6 +1,5 @@
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");
function getNoteSize(req) { function getNoteSize(req) {
const {noteId} = req.params; const {noteId} = req.params;
@ -23,12 +22,7 @@ function getNoteSize(req) {
} }
function getSubtreeSize(req) { function getSubtreeSize(req) {
const {noteId} = req.params; const note = becca.getNoteOrThrow(req.params.noteId);
const note = becca.notes[noteId];
if (!note) {
throw new NotFoundError(`Note '${noteId}' was not found.`);
}
const subTreeNoteIds = note.getSubtreeNoteIds(); const subTreeNoteIds = note.getSubtreeNoteIds();

View File

@ -180,7 +180,8 @@ function register(app) {
route(GET, '/api/branches/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); route(GET, '/api/branches/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
route(PST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importToBranch, apiResultHandler); route(PST, '/api/notes/:parentNoteId/notes-import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importNotesToBranch, apiResultHandler);
route(PST, '/api/notes/:parentNoteId/attachments-import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importAttachmentsToNote, apiResultHandler);
apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes); apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes);
apiRoute(PST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute); apiRoute(PST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute);

View File

@ -12,8 +12,6 @@ const sanitizeFilename = require('sanitize-filename');
const isSvg = require('is-svg'); const isSvg = require('is-svg');
const isAnimated = require('is-animated'); const isAnimated = require('is-animated');
const htmlSanitizer = require("./html_sanitizer"); const htmlSanitizer = require("./html_sanitizer");
const {attach} = require("jsdom/lib/jsdom/living/helpers/svg/basic-types.js");
const NotFoundError = require("../errors/not_found_error.js");
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) { async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
const compressImages = optionService.getOptionBool("compressImages"); const compressImages = optionService.getOptionBool("compressImages");
@ -142,11 +140,7 @@ function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSw
} }
const fileName = sanitizeFilename(originalName); const fileName = sanitizeFilename(originalName);
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
if (!note) {
throw new NotFoundError(`Could not find note '${noteId}'`);
}
const attachment = note.saveAttachment({ const attachment = note.saveAttachment({
role: 'image', role: 'image',