From 5cc58592116074462aeec028950746e8f5160cf6 Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 24 Mar 2023 09:13:35 +0100 Subject: [PATCH] migrate images to attachments --- .../0215__move_content_into_blobs.js | 1 - .../0218__migrate_images_to_attachments.js | 21 +++++++ src/becca/entities/bnote.js | 61 +++++++++++++++++++ src/services/app_info.js | 2 +- 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 db/migrations/0218__migrate_images_to_attachments.js diff --git a/db/migrations/0215__move_content_into_blobs.js b/db/migrations/0215__move_content_into_blobs.js index e447e9c30..77ab76dfe 100644 --- a/db/migrations/0215__move_content_into_blobs.js +++ b/db/migrations/0215__move_content_into_blobs.js @@ -1,4 +1,3 @@ -const sql = require("../../src/services/sql"); module.exports = () => { const sql = require("../../src/services/sql"); const utils = require("../../src/services/utils"); diff --git a/db/migrations/0218__migrate_images_to_attachments.js b/db/migrations/0218__migrate_images_to_attachments.js new file mode 100644 index 000000000..5100abe06 --- /dev/null +++ b/db/migrations/0218__migrate_images_to_attachments.js @@ -0,0 +1,21 @@ +module.exports = () => { + const beccaLoader = require("../../src/becca/becca_loader"); + const becca = require("../../src/becca/becca"); + const cls = require("../../src/services/cls"); + const log = require("../../src/services/log"); + + cls.init(() => { + beccaLoader.load(); + + for (const note of Object.values(becca.notes)) { + try { + const attachment = note.convertToParentAttachment({force: false}); + + log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`) + } + catch (e) { + log.error(`Cannot convert note '${note.noteId}' to attachment: ${e.message} ${e.stack}`); + } + } + }); +}; \ No newline at end of file diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 2b153588e..2c459663c 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -1033,6 +1033,7 @@ class BNote extends AbstractBeccaEntity { return this.noteId === '_hidden' || this.hasAncestor('_hidden'); } + /** @returns {BAttribute[]} */ getTargetRelations() { return this.targetRelations; } @@ -1328,6 +1329,66 @@ class BNote extends AbstractBeccaEntity { return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); } + /** + * Some notes are eligible for conversion into an attachment of its parent, note must have these properties: + * - it has exactly one target relation + * - it has a relation from its parent note + * - it has no children + * - it has no clones + * - parent is of type text + * - both notes are either unprotected or user is in protected session + * + * Currently, works only for image notes. + * + * In future this functionality might get more generic and some of the requirements relaxed. + * + * @params {Object} [opts] + * @params {bolean} [opts.force=false} it is envisioned that user can force the conversion even if some conditions + * are not satisfied (e.g. relation to parent doesn't exist). + * + * @returns {BAttachment|null} - null if note is not eligible for conversion + */ + + convertToParentAttachment(opts = {force: false}) { + if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { + return null; + } + + const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink'); + + if (targetRelations.length !== 1) { + return null; + } + + const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent + const referencingNote = targetRelations[0].note; + + if (parentNote !== referencingNote || parentNote.type !== 'text' || !parentNote.isContentAvailable()) { + return null; + } + + const content = this.getContent(); + + const attachment = parentNote.saveAttachment({ + role: 'image', + mime: this.mime, + title: this.title, + content: content + }); + + let parentContent = parentNote.getContent(); + + const oldNoteUrl = `api/images/${this.noteId}/`; + const newAttachmentUrl = `api/notes/${parentNote.noteId}/images/${attachment.attachmentId}/`; + + const fixedContent = utils.replaceAll(parentContent, oldNoteUrl, newAttachmentUrl); + + parentNote.setContent(fixedContent); + + this.deleteNote(); + + return attachment; + } /** * (Soft) delete a note and all its descendants. diff --git a/src/services/app_info.js b/src/services/app_info.js index 667b156a2..c943d17ad 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -4,7 +4,7 @@ const build = require('./build'); const packageJson = require('../../package'); const {TRILIUM_DATA_DIR} = require('./data_dir'); -const APP_DB_VERSION = 217; +const APP_DB_VERSION = 218; const SYNC_VERSION = 30; const CLIPPER_PROTOCOL_VERSION = "1.0";