mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	becca entities enriched with functionality from repository entities
This commit is contained in:
		
							
								
								
									
										5
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							@@ -1,11 +1,12 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<project version="4">
 | 
					<project version="4">
 | 
				
			||||||
  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
					  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
				
			||||||
    <data-source source="LOCAL" name="document.db" uuid="4e69c96a-8a2b-43f5-9b40-d1608f75f7a4">
 | 
					    <data-source source="LOCAL" name="SQLite - document.db" uuid="30cef30d-e704-484d-a4ca-5d3bfc2ece63">
 | 
				
			||||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
					      <driver-ref>sqlite.xerial</driver-ref>
 | 
				
			||||||
      <synchronize>true</synchronize>
 | 
					      <synchronize>true</synchronize>
 | 
				
			||||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
					      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
				
			||||||
      <jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url>
 | 
					      <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
 | 
				
			||||||
 | 
					      <working-dir>$ProjectFileDir$</working-dir>
 | 
				
			||||||
    </data-source>
 | 
					    </data-source>
 | 
				
			||||||
  </component>
 | 
					  </component>
 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										2
									
								
								db/TODO.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								db/TODO.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					- drop branches.utcDateCreated - not used for anything
 | 
				
			||||||
 | 
					- drop options.utcDateCreated - not used for anything
 | 
				
			||||||
@@ -17,7 +17,7 @@ function load() {
 | 
				
			|||||||
    const start = Date.now();
 | 
					    const start = Date.now();
 | 
				
			||||||
    becca.reset();
 | 
					    becca.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const row of sql.iterateRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`, [])) {
 | 
					    for (const row of sql.iterateRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes`, [])) {
 | 
				
			||||||
        new Note(becca, row);
 | 
					        new Note(becca, row);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								src/services/becca/entities/abstract_entity.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/services/becca/entities/abstract_entity.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AbstractEntity {
 | 
				
			||||||
 | 
					    beforeSaving() {
 | 
				
			||||||
 | 
					        this.generateIdIfNecessary();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    generateIdIfNecessary() {
 | 
				
			||||||
 | 
					        if (!this[this.constructor.primaryKeyName]) {
 | 
				
			||||||
 | 
					            this[this.constructor.primaryKeyName] = utils.newEntityId();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    generateHash() {
 | 
				
			||||||
 | 
					        let contentToHash = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const propertyName of this.constructor.hashedProperties) {
 | 
				
			||||||
 | 
					            contentToHash += "|" + this[propertyName];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return utils.hash(contentToHash).substr(0, 10);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getUtcDateChanged() {
 | 
				
			||||||
 | 
					        return this.utcDateModified || this.utcDateCreated;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get repository() {
 | 
				
			||||||
 | 
					        if (!repo) {
 | 
				
			||||||
 | 
					            repo = require('../services/repository');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return repo;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    save() {
 | 
				
			||||||
 | 
					        this.repository.updateEntity(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = AbstractEntity;
 | 
				
			||||||
@@ -1,9 +1,19 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Note = require('./note.js');
 | 
					const Note = require('./note.js');
 | 
				
			||||||
 | 
					const AbstractEntity = require("./abstract_entity.js");
 | 
				
			||||||
 | 
					const sql = require("../../sql.js");
 | 
				
			||||||
 | 
					const dateUtils = require("../../date_utils.js");
 | 
				
			||||||
 | 
					const promotedAttributeDefinitionParser = require("../../promoted_attribute_definition_parser");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Attribute extends AbstractEntity {
 | 
				
			||||||
 | 
					    static get entityName() { return "attributes"; }
 | 
				
			||||||
 | 
					    static get primaryKeyName() { return "attributeId"; }
 | 
				
			||||||
 | 
					    static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable"]; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Attribute {
 | 
					 | 
				
			||||||
    constructor(becca, row) {
 | 
					    constructor(becca, row) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @param {Becca} */
 | 
					        /** @param {Becca} */
 | 
				
			||||||
        this.becca = becca;
 | 
					        this.becca = becca;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
@@ -60,13 +70,99 @@ class Attribute {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // for logging etc
 | 
					    /**
 | 
				
			||||||
 | 
					     * @returns {Note|null}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getNote() {
 | 
				
			||||||
 | 
					        return this.repository.getNote(this.noteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @returns {Note|null}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getTargetNote() {
 | 
				
			||||||
 | 
					        if (this.type !== 'relation') {
 | 
				
			||||||
 | 
					            throw new Error(`Attribute ${this.attributeId} is not relation`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.value) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.repository.getNote(this.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return {boolean}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isDefinition() {
 | 
				
			||||||
 | 
					        return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDefinition() {
 | 
				
			||||||
 | 
					        return promotedAttributeDefinitionParser.parse(this.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDefinedName() {
 | 
				
			||||||
 | 
					        if (this.type === 'label' && this.name.startsWith('label:')) {
 | 
				
			||||||
 | 
					            return this.name.substr(6);
 | 
				
			||||||
 | 
					        } else if (this.type === 'label' && this.name.startsWith('relation:')) {
 | 
				
			||||||
 | 
					            return this.name.substr(9);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return this.name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get pojo() {
 | 
					    get pojo() {
 | 
				
			||||||
        const pojo = {...this};
 | 
					        return {
 | 
				
			||||||
 | 
					            attributeId: this.attributeId,
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            type: this.type,
 | 
				
			||||||
 | 
					            name: this.name,
 | 
				
			||||||
 | 
					            position: this.position,
 | 
				
			||||||
 | 
					            value: this.value,
 | 
				
			||||||
 | 
					            isInheritable: this.isInheritable
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        delete pojo.becca;
 | 
					    beforeSaving() {
 | 
				
			||||||
 | 
					        if (!this.value) {
 | 
				
			||||||
 | 
					            if (this.type === 'relation') {
 | 
				
			||||||
 | 
					                throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return pojo;
 | 
					            // null value isn't allowed
 | 
				
			||||||
 | 
					            this.value = "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.position === undefined) {
 | 
				
			||||||
 | 
					            this.position = 1 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.isInheritable) {
 | 
				
			||||||
 | 
					            this.isInheritable = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.isDeleted) {
 | 
				
			||||||
 | 
					            this.isDeleted = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.utcDateModified = dateUtils.utcNowDateTime();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createClone(type, name, value, isInheritable) {
 | 
				
			||||||
 | 
					        return new Attribute({
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            type: type,
 | 
				
			||||||
 | 
					            name: name,
 | 
				
			||||||
 | 
					            value: value,
 | 
				
			||||||
 | 
					            position: this.position,
 | 
				
			||||||
 | 
					            isInheritable: isInheritable,
 | 
				
			||||||
 | 
					            isDeleted: false,
 | 
				
			||||||
 | 
					            utcDateModified: this.utcDateModified
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,19 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Note = require('./note.js');
 | 
					const Note = require('./note.js');
 | 
				
			||||||
 | 
					const AbstractEntity = require("./abstract_entity.js");
 | 
				
			||||||
 | 
					const sql = require("../../sql.js");
 | 
				
			||||||
 | 
					const dateUtils = require("../../date_utils.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Branch extends AbstractEntity {
 | 
				
			||||||
 | 
					    static get entityName() { return "branches"; }
 | 
				
			||||||
 | 
					    static get primaryKeyName() { return "branchId"; }
 | 
				
			||||||
 | 
					    // notePosition is not part of hash because it would produce a lot of updates in case of reordering
 | 
				
			||||||
 | 
					    static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Branch {
 | 
					 | 
				
			||||||
    constructor(becca, row) {
 | 
					    constructor(becca, row) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @param {Becca} */
 | 
					        /** @param {Becca} */
 | 
				
			||||||
        this.becca = becca;
 | 
					        this.becca = becca;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
@@ -55,13 +65,44 @@ class Branch {
 | 
				
			|||||||
        return this.becca.notes[this.parentNoteId];
 | 
					        return this.becca.notes[this.parentNoteId];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // for logging etc
 | 
					 | 
				
			||||||
    get pojo() {
 | 
					    get pojo() {
 | 
				
			||||||
        const pojo = {...this};
 | 
					        return {
 | 
				
			||||||
 | 
					            branchId: this.branchId,
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            parentNoteId: this.parentNoteId,
 | 
				
			||||||
 | 
					            prefix: this.prefix,
 | 
				
			||||||
 | 
					            notePosition: this.notePosition,
 | 
				
			||||||
 | 
					            isExpanded: this.isExpanded
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        delete pojo.becca;
 | 
					    createClone(parentNoteId, notePosition) {
 | 
				
			||||||
 | 
					        return new Branch({
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            parentNoteId: parentNoteId,
 | 
				
			||||||
 | 
					            notePosition: notePosition,
 | 
				
			||||||
 | 
					            prefix: this.prefix,
 | 
				
			||||||
 | 
					            isExpanded: this.isExpanded
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return pojo;
 | 
					    beforeSaving() {
 | 
				
			||||||
 | 
					        if (this.notePosition === undefined || this.notePosition === null) {
 | 
				
			||||||
 | 
					            const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
 | 
				
			||||||
 | 
					            this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.isExpanded) {
 | 
				
			||||||
 | 
					            this.isExpanded = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.branchId) {
 | 
				
			||||||
 | 
					            this.utcDateCreated = dateUtils.utcNowDateTime();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.utcDateModified = dateUtils.utcNowDateTime();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const protectedSessionService = require('../../protected_session');
 | 
					const protectedSessionService = require('../../protected_session');
 | 
				
			||||||
const log = require('../../log');
 | 
					const log = require('../../log');
 | 
				
			||||||
 | 
					const sql = require('../../sql');
 | 
				
			||||||
 | 
					const utils = require('../../utils');
 | 
				
			||||||
 | 
					const dateUtils = require('../../date_utils');
 | 
				
			||||||
 | 
					const entityChangesService = require('../../entity_changes.js');
 | 
				
			||||||
 | 
					const AbstractEntity = require("./abstract_entity.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LABEL = 'label';
 | 
					const LABEL = 'label';
 | 
				
			||||||
const RELATION = 'relation';
 | 
					const RELATION = 'relation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Note {
 | 
					class Note extends AbstractEntity {
 | 
				
			||||||
 | 
					    static get entityName() { return "notes"; }
 | 
				
			||||||
 | 
					    static get primaryKeyName() { return "noteId"; }
 | 
				
			||||||
 | 
					    static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime"]; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(becca, row) {
 | 
					    constructor(becca, row) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @param {Becca} */
 | 
					        /** @param {Becca} */
 | 
				
			||||||
        this.becca = becca;
 | 
					        this.becca = becca;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,10 +57,14 @@ class Note {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    update(row) {
 | 
					    update(row) {
 | 
				
			||||||
 | 
					        // ------ Database persisted attributes ------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
        this.noteId = row.noteId;
 | 
					        this.noteId = row.noteId;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
        this.title = row.title;
 | 
					        this.title = row.title;
 | 
				
			||||||
 | 
					        /** @param {boolean} */
 | 
				
			||||||
 | 
					        this.isProtected = !!row.isProtected;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
        this.type = row.type;
 | 
					        this.type = row.type;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
@@ -62,8 +77,9 @@ class Note {
 | 
				
			|||||||
        this.utcDateCreated = row.utcDateCreated;
 | 
					        this.utcDateCreated = row.utcDateCreated;
 | 
				
			||||||
        /** @param {string} */
 | 
					        /** @param {string} */
 | 
				
			||||||
        this.utcDateModified = row.utcDateModified;
 | 
					        this.utcDateModified = row.utcDateModified;
 | 
				
			||||||
        /** @param {boolean} */
 | 
					
 | 
				
			||||||
        this.isProtected = !!row.isProtected;
 | 
					        // ------ Derived attributes ------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @param {boolean} */
 | 
					        /** @param {boolean} */
 | 
				
			||||||
        this.isDecrypted = !row.isProtected || !!row.isContentAvailable;
 | 
					        this.isDecrypted = !row.isProtected || !!row.isContentAvailable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,6 +89,162 @@ class Note {
 | 
				
			|||||||
        this.flatTextCache = null;
 | 
					        this.flatTextCache = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * Note content has quite special handling - it's not a separate entity, but a lazily loaded
 | 
				
			||||||
 | 
					     * part of Note entity with it's own sync. Reasons behind this hybrid design has been:
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * - content can be quite large and it's not necessary to load it / fill memory for any note access even if we don't need a content, especially for bulk operations like search
 | 
				
			||||||
 | 
					     * - changes in the note metadata or title should not trigger note content sync (so we keep separate utcDateModified and entity changes records)
 | 
				
			||||||
 | 
					     * - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {*} */
 | 
				
			||||||
 | 
					    getContent(silentNotFoundError = false) {
 | 
				
			||||||
 | 
					        const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!row) {
 | 
				
			||||||
 | 
					            if (silentNotFoundError) {
 | 
				
			||||||
 | 
					                return undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                throw new Error("Cannot find note content for noteId=" + this.noteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let content = row.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isProtected) {
 | 
				
			||||||
 | 
					            if (protectedSessionService.isProtectedSessionAvailable()) {
 | 
				
			||||||
 | 
					                content = content === null ? null : protectedSessionService.decrypt(content);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                content = "";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isStringNote()) {
 | 
				
			||||||
 | 
					            return content === null
 | 
				
			||||||
 | 
					                ? ""
 | 
				
			||||||
 | 
					                : content.toString("UTF-8");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            return content;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {{contentLength, dateModified, utcDateModified}} */
 | 
				
			||||||
 | 
					    getContentMetadata() {
 | 
				
			||||||
 | 
					        return sql.getRow(`
 | 
				
			||||||
 | 
					            SELECT 
 | 
				
			||||||
 | 
					                LENGTH(content) AS contentLength, 
 | 
				
			||||||
 | 
					                dateModified,
 | 
				
			||||||
 | 
					                utcDateModified 
 | 
				
			||||||
 | 
					            FROM note_contents 
 | 
				
			||||||
 | 
					            WHERE noteId = ?`, [this.noteId]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {*} */
 | 
				
			||||||
 | 
					    getJsonContent() {
 | 
				
			||||||
 | 
					        const content = this.getContent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!content || !content.trim()) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JSON.parse(content);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setContent(content) {
 | 
				
			||||||
 | 
					        if (content === null || content === undefined) {
 | 
				
			||||||
 | 
					            throw new Error(`Cannot set null content to note ${this.noteId}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isStringNote()) {
 | 
				
			||||||
 | 
					            content = content.toString();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            content = Buffer.isBuffer(content) ? content : Buffer.from(content);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const pojo = {
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            content: content,
 | 
				
			||||||
 | 
					            dateModified: dateUtils.localNowDateTime(),
 | 
				
			||||||
 | 
					            utcDateModified: dateUtils.utcNowDateTime()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isProtected) {
 | 
				
			||||||
 | 
					            if (protectedSessionService.isProtectedSessionAvailable()) {
 | 
				
			||||||
 | 
					                pojo.content = protectedSessionService.encrypt(pojo.content);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                throw new Error(`Cannot update content of noteId=${this.noteId} since we're out of protected session.`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sql.upsert("note_contents", "noteId", pojo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const hash = utils.hash(this.noteId + "|" + pojo.content.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        entityChangesService.addEntityChange({
 | 
				
			||||||
 | 
					            entityName: 'note_contents',
 | 
				
			||||||
 | 
					            entityId: this.noteId,
 | 
				
			||||||
 | 
					            hash: hash,
 | 
				
			||||||
 | 
					            isErased: false,
 | 
				
			||||||
 | 
					            utcDateChanged: pojo.utcDateModified
 | 
				
			||||||
 | 
					        }, null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setJsonContent(content) {
 | 
				
			||||||
 | 
					        this.setContent(JSON.stringify(content, null, '\t'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
 | 
				
			||||||
 | 
					    isRoot() {
 | 
				
			||||||
 | 
					        return this.noteId === 'root';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {boolean} true if this note is of application/json content type */
 | 
				
			||||||
 | 
					    isJson() {
 | 
				
			||||||
 | 
					        return this.mime === "application/json";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {boolean} true if this note is JavaScript (code or attachment) */
 | 
				
			||||||
 | 
					    isJavaScript() {
 | 
				
			||||||
 | 
					        return (this.type === "code" || this.type === "file")
 | 
				
			||||||
 | 
					            && (this.mime.startsWith("application/javascript")
 | 
				
			||||||
 | 
					                || this.mime === "application/x-javascript"
 | 
				
			||||||
 | 
					                || this.mime === "text/javascript");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {boolean} true if this note is HTML */
 | 
				
			||||||
 | 
					    isHtml() {
 | 
				
			||||||
 | 
					        return ["code", "file", "render"].includes(this.type)
 | 
				
			||||||
 | 
					            && this.mime === "text/html";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {boolean} true if the note has string content (not binary) */
 | 
				
			||||||
 | 
					    isStringNote() {
 | 
				
			||||||
 | 
					        return utils.isStringNote(this.type, this.mime);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {string|null} JS script environment - either "frontend" or "backend" */
 | 
				
			||||||
 | 
					    getScriptEnv() {
 | 
				
			||||||
 | 
					        if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
 | 
				
			||||||
 | 
					            return "frontend";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.type === 'render') {
 | 
				
			||||||
 | 
					            return "frontend";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isJavaScript() && this.mime.endsWith('env=backend')) {
 | 
				
			||||||
 | 
					            return "backend";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @return {Attribute[]} */
 | 
					    /** @return {Attribute[]} */
 | 
				
			||||||
    get attributes() {
 | 
					    get attributes() {
 | 
				
			||||||
        return this.__getAttributes([]);
 | 
					        return this.__getAttributes([]);
 | 
				
			||||||
@@ -543,19 +715,45 @@ class Note {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // for logging etc
 | 
					 | 
				
			||||||
    get pojo() {
 | 
					    get pojo() {
 | 
				
			||||||
        const pojo = {...this};
 | 
					        return {
 | 
				
			||||||
 | 
					            noteId: this.noteId,
 | 
				
			||||||
 | 
					            title: this.title,
 | 
				
			||||||
 | 
					            isProtected: this.isProtected,
 | 
				
			||||||
 | 
					            type: this.type,
 | 
				
			||||||
 | 
					            mime: this.mime,
 | 
				
			||||||
 | 
					            dateCreated: this.dateCreated,
 | 
				
			||||||
 | 
					            dateModified: this.dateModified,
 | 
				
			||||||
 | 
					            utcDateCreated: this.utcDateCreated,
 | 
				
			||||||
 | 
					            utcDateModified: this.utcDateModified
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        delete pojo.becca;
 | 
					    beforeSaving() {
 | 
				
			||||||
        delete pojo.ancestorCache;
 | 
					        if (!this.dateCreated) {
 | 
				
			||||||
        delete pojo.attributeCache;
 | 
					            this.dateCreated = dateUtils.localNowDateTime();
 | 
				
			||||||
        delete pojo.flatTextCache;
 | 
					        }
 | 
				
			||||||
        delete pojo.children;
 | 
					 | 
				
			||||||
        delete pojo.parents;
 | 
					 | 
				
			||||||
        delete pojo.parentBranches;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return pojo;
 | 
					        if (!this.utcDateCreated) {
 | 
				
			||||||
 | 
					            this.utcDateCreated = dateUtils.utcNowDateTime();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.dateModified = dateUtils.localNowDateTime();
 | 
				
			||||||
 | 
					        this.utcDateModified = dateUtils.utcNowDateTime();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updatePojo(pojo) {
 | 
				
			||||||
 | 
					        if (pojo.isProtected) {
 | 
				
			||||||
 | 
					            if (this.isDecrypted) {
 | 
				
			||||||
 | 
					                pojo.title = protectedSessionService.encrypt(pojo.title);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                // updating protected note outside of protected session means we will keep original ciphertexts
 | 
				
			||||||
 | 
					                delete pojo.title;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user