From 3b3f6082a7ed1c4e52c2e1f79f08fad8f24216a4 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 5 Jun 2023 09:23:42 +0200 Subject: [PATCH] attachment ETAPI support WIP --- dump-db/inc/dump.js | 22 +++--- src/becca/entities/bbranch.js | 2 +- src/becca/entities/bnote.js | 14 ++-- src/etapi/attachments.js | 104 +++++++++++++++++++++++++ src/etapi/attributes.js | 2 +- src/etapi/branches.js | 2 +- src/etapi/etapi_utils.js | 22 ++++-- src/etapi/mappers.js | 20 ++++- src/etapi/notes.js | 33 ++++++-- src/routes/api/revisions.js | 2 +- src/routes/api/search.js | 8 +- src/routes/routes.js | 2 + src/services/bulk_actions.js | 2 +- src/services/cloning.js | 14 +--- src/services/consistency_checks.js | 8 +- src/services/notes.js | 36 ++++----- src/services/search/services/search.js | 1 - src/services/sync.js | 14 ++-- src/services/tree.js | 4 +- 19 files changed, 229 insertions(+), 83 deletions(-) create mode 100644 src/etapi/attachments.js diff --git a/dump-db/inc/dump.js b/dump-db/inc/dump.js index e67d60a1f..2188a3b58 100644 --- a/dump-db/inc/dump.js +++ b/dump-db/inc/dump.js @@ -29,12 +29,12 @@ function dumpDocument(documentPath, targetPath, options) { function dumpNote(targetPath, noteId) { console.log(`Reading note '${noteId}'`); - let childTargetPath, note, fileNameWithPath; + let childTargetPath, noteRow, fileNameWithPath; try { - note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); + noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); - if (note.isDeleted) { + if (noteRow.isDeleted) { stats.deleted++; if (!options.includeDeleted) { @@ -44,13 +44,13 @@ function dumpDocument(documentPath, targetPath, options) { } } - if (note.isProtected) { + if (noteRow.isProtected) { stats.protected++; - note.title = decryptService.decryptString(dataKey, note.title); + noteRow.title = decryptService.decryptString(dataKey, noteRow.title); } - let safeTitle = sanitize(note.title); + let safeTitle = sanitize(noteRow.title); if (safeTitle.length > 20) { safeTitle = safeTitle.substring(0, 20); @@ -64,8 +64,8 @@ function dumpDocument(documentPath, targetPath, options) { existingPaths[childTargetPath] = true; - if (note.noteId in noteIdToPath) { - const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[note.noteId]}`; + if (noteRow.noteId in noteIdToPath) { + const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[noteRow.noteId]}`; console.log(message); @@ -74,16 +74,16 @@ function dumpDocument(documentPath, targetPath, options) { return; } - let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [note.blobId]); + let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]); - if (content !== null && note.isProtected && dataKey) { + if (content !== null && noteRow.isProtected && dataKey) { content = decryptService.decrypt(dataKey, content); } if (isContentEmpty(content)) { console.log(`Note '${noteId}' is empty, skipping.`); } else { - fileNameWithPath = extensionService.getFileName(note, childTargetPath, safeTitle); + fileNameWithPath = extensionService.getFileName(noteRow, childTargetPath, safeTitle); fs.writeFileSync(fileNameWithPath, content); diff --git a/src/becca/entities/bbranch.js b/src/becca/entities/bbranch.js index 456d1c289..f0edc6a35 100644 --- a/src/becca/entities/bbranch.js +++ b/src/becca/entities/bbranch.js @@ -187,7 +187,7 @@ class BBranch extends AbstractBeccaEntity { // first delete children and then parent - this will show up better in recent changes - log.info(`Deleting note ${note.noteId}`); + log.info(`Deleting note '${note.noteId}'`); this.becca.notes[note.noteId].isBeingDeleted = true; diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 3771f0f05..3090f1737 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -1549,6 +1549,8 @@ class BNote extends AbstractBeccaEntity { } get isDeleted() { + // isBeingDeleted is relevant only in the transition period when the deletion process have begun, but not yet + // finished (note is still in becca) return !(this.noteId in this.becca.notes) || this.isBeingDeleted; } @@ -1602,7 +1604,7 @@ class BNote extends AbstractBeccaEntity { /** * @returns {BAttachment} */ - saveAttachment({attachmentId, role, mime, title, content}) { + saveAttachment({attachmentId, role, mime, title, content, position}) { let attachment; if (attachmentId) { @@ -1613,15 +1615,13 @@ class BNote extends AbstractBeccaEntity { title, role, mime, - isProtected: this.isProtected + isProtected: this.isProtected, + position }); } - if (content !== undefined && content !== null) { - attachment.setContent(content, {forceSave: true}); - } else { - attachment.save(); - } + content = content || ""; + attachment.setContent(content, {forceSave: true}); return attachment; } diff --git a/src/etapi/attachments.js b/src/etapi/attachments.js new file mode 100644 index 000000000..d46eeb4a9 --- /dev/null +++ b/src/etapi/attachments.js @@ -0,0 +1,104 @@ +const becca = require("../becca/becca"); +const eu = require("./etapi_utils"); +const mappers = require("./mappers"); +const v = require("./validators"); +const utils = require("../services/utils.js"); +const noteService = require("../services/notes.js"); + +function register(router) { + const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = { + 'parentId': [v.notNull, v.isNoteId], + 'role': [v.notNull, v.isString], + 'mime': [v.notNull, v.isString], + 'title': [v.notNull, v.isString], + 'position': [v.notNull, v.isInteger], + 'content': [v.isString], + }; + + eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => { + const params = {}; + + eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT); + + try { + const note = becca.getNoteOrThrow(params.parentId); + const attachment = note.saveAttachment(params); + + res.status(201).json(mappers.mapAttachmentToPojo(attachment)); + } + catch (e) { + throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message); + } + }); + + eu.route(router, 'get', '/etapi/attachments/:attachmentId', (req, res, next) => { + const attachment = eu.getAndCheckAttachment(req.params.attachmentId); + + res.json(mappers.mapAttachmentToPojo(attachment)); + }); + + const ALLOWED_PROPERTIES_FOR_PATCH = { + 'role': [v.notNull, v.isString], + 'mime': [v.notNull, v.isString], + 'title': [v.notNull, v.isString], + 'position': [v.notNull, v.isInteger], + }; + + eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => { + const attachment = eu.getAndCheckAttachment(req.params.attachmentId); + + if (attachment.isProtected) { + throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`); + } + + eu.validateAndPatch(attachment, req.body, ALLOWED_PROPERTIES_FOR_PATCH); + attachment.save(); + + res.json(mappers.mapAttachmentToPojo(attachment)); + }); + + eu.route(router, 'get', '/etapi/attachments/:attachmentId/content', (req, res, next) => { + const attachment = eu.getAndCheckAttachment(req.params.attachmentId); + + if (attachment.isProtected) { + throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and content cannot be read through ETAPI.`); + } + + const filename = utils.formatDownloadTitle(attachment.title, attachment.type, attachment.mime); + + res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); + + res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + res.setHeader('Content-Type', attachment.mime); + + res.send(attachment.getContent()); + }); + + eu.route(router, 'put', '/etapi/attachments/:attachmentId/content', (req, res, next) => { + const attachment = eu.getAndCheckAttachment(req.params.attachmentId); + + if (attachment.isProtected) { + throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`); + } + + attachment.setContent(req.body); + + return res.sendStatus(204); + }); + + eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => { + const attachment = becca.getAttachment(req.params.attachmentId); + + if (!attachment) { + return res.sendStatus(204); + } + + attachment.markAsDeleted(); + + res.sendStatus(204); + }); +} + +module.exports = { + register +}; diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index fb8b2ad99..1ecd42888 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -68,7 +68,7 @@ function register(router) { eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => { const attribute = becca.getAttribute(req.params.attributeId); - if (!attribute || attribute.isDeleted) { + if (!attribute) { return res.sendStatus(204); } diff --git a/src/etapi/branches.js b/src/etapi/branches.js index 6c83c0928..daa51cabb 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -64,7 +64,7 @@ function register(router) { eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => { const branch = becca.getBranch(req.params.branchId); - if (!branch || branch.isDeleted) { + if (!branch) { return res.sendStatus(204); } diff --git a/src/etapi/etapi_utils.js b/src/etapi/etapi_utils.js index 43bd0eee0..64dc77191 100644 --- a/src/etapi/etapi_utils.js +++ b/src/etapi/etapi_utils.js @@ -77,7 +77,18 @@ function getAndCheckNote(noteId) { return note; } else { - throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found`); + throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found.`); + } +} + +function getAndCheckAttachment(attachmentId) { + const attachment = becca.getAttachment(attachmentId, {includeContentLength: true}); + + if (attachment) { + return attachment; + } + else { + throw new EtapiError(404, "ATTACHMENT_NOT_FOUND", `Attachment '${attachmentId}' not found.`); } } @@ -88,7 +99,7 @@ function getAndCheckBranch(branchId) { return branch; } else { - throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found`); + throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found.`); } } @@ -99,7 +110,7 @@ function getAndCheckAttribute(attributeId) { return attribute; } else { - throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found`); + throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found.`); } } @@ -113,7 +124,7 @@ function validateAndPatch(target, source, allowedProperties) { const validationResult = validator(source[key]); if (validationResult) { - throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}`); + throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}.`); } } } @@ -134,5 +145,6 @@ module.exports = { validateAndPatch, getAndCheckNote, getAndCheckBranch, - getAndCheckAttribute + getAndCheckAttribute, + getAndCheckAttachment } diff --git a/src/etapi/mappers.js b/src/etapi/mappers.js index 44a5fa8f6..8ec52aa26 100644 --- a/src/etapi/mappers.js +++ b/src/etapi/mappers.js @@ -46,8 +46,26 @@ function mapAttributeToPojo(attr) { }; } +/** @param {BAttachment} attachment */ +function mapAttachmentToPojo(attachment) { + return { + attachmentId: attachment.attachmentId, + parentId: attachment.parentId, + role: attachment.role, + mime: attachment.mime, + title: attachment.title, + position: attachment.position, + blobId: attachment.blobId, + dateModified: attachment.dateModified, + utcDateModified: attachment.utcDateModified, + utcDateScheduledForErasureSince: attachment.utcDateScheduledForErasureSince, + contentLength: attachment.contentLength + }; +} + module.exports = { mapNoteToPojo, mapBranchToPojo, - mapAttributeToPojo + mapAttributeToPojo, + mapAttachmentToPojo }; diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 7329c791b..6e68f98f3 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -14,7 +14,7 @@ function register(router) { const {search} = req.query; if (!search?.trim()) { - throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory"); + throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory."); } const searchParams = parseSearchParams(req); @@ -78,10 +78,10 @@ function register(router) { }; eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { - const note = eu.getAndCheckNote(req.params.noteId) + const note = eu.getAndCheckNote(req.params.noteId); if (note.isProtected) { - throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI`); + throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI.`); } eu.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH); @@ -95,7 +95,7 @@ function register(router) { const note = becca.getNote(noteId); - if (!note || note.isDeleted) { + if (!note) { return res.sendStatus(204); } @@ -107,6 +107,10 @@ function register(router) { eu.route(router, 'get', '/etapi/notes/:noteId/content', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); + if (note.isProtected) { + throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and content cannot be read through ETAPI.`); + } + const filename = utils.formatDownloadTitle(note.title, note.type, note.mime); res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); @@ -120,6 +124,10 @@ function register(router) { eu.route(router, 'put', '/etapi/notes/:noteId/content', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); + if (note.isProtected) { + throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI.`); + } + note.setContent(req.body); noteService.asyncPostProcessContent(note, req.body); @@ -132,7 +140,7 @@ function register(router) { const format = req.query.format || "html"; if (!["html", "markdown"].includes(format)) { - throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'`); + throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`); } const taskContext = new TaskContext('no-progress-reporting'); @@ -153,6 +161,15 @@ function register(router) { return res.sendStatus(204); }); + + eu.route(router, 'get', '/etapi/notes/:noteId/attachments', (req, res, next) => { + const note = eu.getAndCheckNote(req.params.noteId); + const attachments = note.getAttachments({includeContentLength: true}) + + res.json( + attachments.map(attachment => mappers.mapAttachmentToPojo(attachment)) + ); + }); } function parseSearchParams(req) { @@ -186,7 +203,7 @@ function parseBoolean(obj, name) { } if (!['true', 'false'].includes(obj[name])) { - throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`); + throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'.`); } return obj[name] === 'true'; @@ -200,7 +217,7 @@ function parseOrderDirection(obj, name) { const integer = parseInt(obj[name]); if (!['asc', 'desc'].includes(obj[name])) { - throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`); + throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'.`); } return integer; @@ -214,7 +231,7 @@ function parseInteger(obj, name) { const integer = parseInt(obj[name]); if (Number.isNaN(integer)) { - throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`); + throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}'.`); } return integer; diff --git a/src/routes/api/revisions.js b/src/routes/api/revisions.js index 461d65734..674973f2e 100644 --- a/src/routes/api/revisions.js +++ b/src/routes/api/revisions.js @@ -149,7 +149,7 @@ function getEditedNotesOnDate(req) { } return notes.map(note => { - const notePath = note.isDeleted ? null : getNotePathData(note); + const notePath = getNotePathData(note); const notePojo = note.getPojo(); notePojo.notePath = notePath ? notePath.notePath : null; diff --git a/src/routes/api/search.js b/src/routes/api/search.js index 5d7e953d7..4be87e89d 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -11,8 +11,8 @@ const ValidationError = require("../../errors/validation_error"); function searchFromNote(req) { const note = becca.getNoteOrThrow(req.params.noteId); - if (note.isDeleted) { - // this can be triggered from recent changes, and it's harmless to return empty list rather than fail + if (!note) { + // this can be triggered from recent changes, and it's harmless to return an empty list rather than fail return []; } @@ -26,8 +26,8 @@ function searchFromNote(req) { function searchAndExecute(req) { const note = becca.getNoteOrThrow(req.params.noteId); - if (note.isDeleted) { - // this can be triggered from recent changes, and it's harmless to return empty list rather than fail + if (!note) { + // this can be triggered from recent changes, and it's harmless to return an empty list rather than fail return []; } diff --git a/src/routes/routes.js b/src/routes/routes.js index 0e92a21d2..e6828de41 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -63,6 +63,7 @@ const shareRoutes = require('../share/routes'); const etapiAuthRoutes = require('../etapi/auth'); const etapiAppInfoRoutes = require('../etapi/app_info'); +const etapiAttachmentRoutes = require('../etapi/attachments'); const etapiAttributeRoutes = require('../etapi/attributes'); const etapiBranchRoutes = require('../etapi/branches'); const etapiNoteRoutes = require('../etapi/notes'); @@ -332,6 +333,7 @@ function register(app) { etapiAuthRoutes.register(router, [loginRateLimiter]); etapiAppInfoRoutes.register(router); + etapiAttachmentRoutes.register(router); etapiAttributeRoutes.register(router); etapiBranchRoutes.register(router); etapiNoteRoutes.register(router); diff --git a/src/services/bulk_actions.js b/src/services/bulk_actions.js index 2b3490fb5..b1749c79b 100644 --- a/src/services/bulk_actions.js +++ b/src/services/bulk_actions.js @@ -134,7 +134,7 @@ function executeActions(note, searchResultNoteIds) { for (const resultNoteId of searchResultNoteIds) { const resultNote = becca.getNote(resultNoteId); - if (!resultNote || resultNote.isDeleted) { + if (!resultNote) { continue; } diff --git a/src/services/cloning.js b/src/services/cloning.js index eaa7682da..0ba767c0a 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -9,6 +9,10 @@ const beccaService = require("../becca/becca_service"); const log = require("./log"); function cloneNoteToParentNote(noteId, parentNoteId, prefix) { + if (!(noteId in becca.notes) || !(parentNoteId in becca.notes)) { + return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; + } + const parentNote = becca.getNote(parentNoteId); if (parentNote.type === 'search') { @@ -18,10 +22,6 @@ function cloneNoteToParentNote(noteId, parentNoteId, prefix) { }; } - if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { - return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; - } - const validationResult = treeService.validateParentChild(parentNoteId, noteId); if (!validationResult.success) { @@ -174,12 +174,6 @@ function cloneNoteAfter(noteId, afterBranchId) { return { success: true, branchId: branch.branchId }; } -function isNoteDeleted(noteId) { - const note = becca.getNote(noteId); - - return !note || note.isDeleted; -} - module.exports = { cloneNoteToBranch, cloneNoteToParentNote, diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index 9a9af44f3..2bd90c2f0 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -607,16 +607,16 @@ class ConsistencyChecks { WHERE entity_changes.id IS NULL`, ({entityId}) => { - const entity = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]); + const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]); if (this.autoFix) { entityChangesService.addEntityChange({ entityName, entityId, hash: utils.randomString(10), // doesn't matter, will force sync, but that's OK - isErased: !!entity.isErased, - utcDateChanged: entity.utcDateModified || entity.utcDateCreated, - isSynced: entityName !== 'options' || entity.isSynced + isErased: !!entityRow.isErased, + utcDateChanged: entityRow.utcDateModified || entityRow.utcDateCreated, + isSynced: entityName !== 'options' || entityRow.isSynced }); logFix(`Created missing entity change for entityName '${entityName}', entityId '${entityId}'`); diff --git a/src/services/notes.js b/src/services/notes.js index a4062ac5e..351fb585c 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -570,7 +570,7 @@ function downloadImages(noteId, content) { for (const url in imageUrlToAttachmentIdMapping) { const imageNote = imageNotes.find(note => note.noteId === imageUrlToAttachmentIdMapping[url]); - if (imageNote && !imageNote.isDeleted) { + if (imageNote) { updatedContent = replaceUrl(updatedContent, url, imageNote); } } @@ -697,14 +697,14 @@ function updateNoteData(noteId, content) { * @param {TaskContext} taskContext */ function undeleteNote(noteId, taskContext) { - const note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); + const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); - if (!note.isDeleted) { + if (!noteRow.isDeleted) { log.error(`Note '${noteId}' is not deleted and thus cannot be undeleted.`); return; } - const undeletedParentBranchIds = getUndeletedParentBranchIds(noteId, note.deleteId); + const undeletedParentBranchIds = getUndeletedParentBranchIds(noteId, noteRow.deleteId); if (undeletedParentBranchIds.length === 0) { // cannot undelete if there's no undeleted parent @@ -712,7 +712,7 @@ function undeleteNote(noteId, taskContext) { } for (const parentBranchId of undeletedParentBranchIds) { - undeleteBranch(parentBranchId, note.deleteId, taskContext); + undeleteBranch(parentBranchId, noteRow.deleteId, taskContext); } } @@ -722,38 +722,38 @@ function undeleteNote(noteId, taskContext) { * @param {TaskContext} taskContext */ function undeleteBranch(branchId, deleteId, taskContext) { - const branch = sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]) + const branchRow = sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]) - if (!branch.isDeleted) { + if (!branchRow.isDeleted) { return; } - const note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [branch.noteId]); + const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [branchRow.noteId]); - if (note.isDeleted && note.deleteId !== deleteId) { + if (noteRow.isDeleted && noteRow.deleteId !== deleteId) { return; } - new BBranch(branch).save(); + new BBranch(branchRow).save(); taskContext.increaseProgressCount(); - if (note.isDeleted && note.deleteId === deleteId) { + if (noteRow.isDeleted && noteRow.deleteId === deleteId) { // becca entity was already created as skeleton in "new Branch()" above - const noteEntity = becca.getNote(note.noteId); - noteEntity.updateFromRow(note); + const noteEntity = becca.getNote(noteRow.noteId); + noteEntity.updateFromRow(noteRow); noteEntity.save(); - const attributes = sql.getRows(` + const attributeRows = sql.getRows(` SELECT * FROM attributes WHERE isDeleted = 1 AND deleteId = ? AND (noteId = ? - OR (type = 'relation' AND value = ?))`, [deleteId, note.noteId, note.noteId]); + OR (type = 'relation' AND value = ?))`, [deleteId, noteRow.noteId, noteRow.noteId]); - for (const attribute of attributes) { + for (const attributeRow of attributeRows) { // relation might point to a note which hasn't been undeleted yet and would thus throw up - new BAttribute(attribute).save({skipValidation: true}); + new BAttribute(attributeRow).save({skipValidation: true}); } const childBranchIds = sql.getColumn(` @@ -761,7 +761,7 @@ function undeleteBranch(branchId, deleteId, taskContext) { FROM branches WHERE branches.isDeleted = 1 AND branches.deleteId = ? - AND branches.parentNoteId = ?`, [deleteId, note.noteId]); + AND branches.parentNoteId = ?`, [deleteId, noteRow.noteId]); for (const childBranchId of childBranchIds) { undeleteBranch(childBranchId, deleteId, taskContext); diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index 43bed405c..a2317d7c4 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -155,7 +155,6 @@ function findResultsWithExpression(expression, searchContext) { const noteSet = expression.execute(allNoteSet, executionContext, searchContext); const searchResults = noteSet.notes - .filter(note => !note.isDeleted) .map(note => { const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath(); diff --git a/src/services/sync.js b/src/services/sync.js index b229eea42..8cbfaa7df 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -315,21 +315,21 @@ function getEntityChangeRow(entityName, entityId) { throw new Error(`Unknown entity '${entityName}'`); } - const entity = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); + const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); - if (!entity) { + if (!entityRow) { throw new Error(`Entity ${entityName} '${entityId}' not found.`); } - if (entityName === 'blobs' && entity.content !== null) { - if (typeof entity.content === 'string') { - entity.content = Buffer.from(entity.content, 'utf-8'); + if (entityName === 'blobs' && entityRow.content !== null) { + if (typeof entityRow.content === 'string') { + entityRow.content = Buffer.from(entityRow.content, 'utf-8'); } - entity.content = entity.content.toString("base64"); + entityRow.content = entityRow.content.toString("base64"); } - return entity; + return entityRow; } } diff --git a/src/services/tree.js b/src/services/tree.js index 5e77a9504..5d183cbca 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -199,7 +199,7 @@ function sortNotesIfNeeded(parentNoteId) { function setNoteToParent(noteId, prefix, parentNoteId) { const parentNote = becca.getNote(parentNoteId); - if (parentNote && parentNote.isDeleted) { + if (!parentNote) { throw new Error(`Cannot move note to deleted parent note '${parentNoteId}'`); } @@ -209,7 +209,7 @@ function setNoteToParent(noteId, prefix, parentNoteId) { if (branch) { if (!parentNoteId) { - log.info(`Removing note ${noteId} from parent ${parentNoteId}`); + log.info(`Removing note '${noteId}' from parent '${parentNoteId}'`); branch.markAsDeleted(); }