diff --git a/src/services/becca/entities/abstract_entity.js b/src/services/becca/entities/abstract_entity.js index 9797fea9c..66a6c0ae9 100644 --- a/src/services/becca/entities/abstract_entity.js +++ b/src/services/becca/entities/abstract_entity.js @@ -1,5 +1,9 @@ "use strict"; +const utils = require('../../utils'); + +let repo = null; + class AbstractEntity { beforeSaving() { this.generateIdIfNecessary(); @@ -27,7 +31,7 @@ class AbstractEntity { get repository() { if (!repo) { - repo = require('../services/repository'); + repo = require('../../repository'); } return repo; diff --git a/src/services/becca/entities/attribute.js b/src/services/becca/entities/attribute.js index 01163e439..91e610634 100644 --- a/src/services/becca/entities/attribute.js +++ b/src/services/becca/entities/attribute.js @@ -113,7 +113,7 @@ class Attribute extends AbstractEntity { } } - get pojo() { + getPojo() { return { attributeId: this.attributeId, noteId: this.noteId, diff --git a/src/services/becca/entities/branch.js b/src/services/becca/entities/branch.js index 497559a7e..c36de904a 100644 --- a/src/services/becca/entities/branch.js +++ b/src/services/becca/entities/branch.js @@ -65,15 +65,25 @@ class Branch extends AbstractEntity { return this.becca.notes[this.parentNoteId]; } - get pojo() { - return { + getPojo() { + const pojo = { branchId: this.branchId, noteId: this.noteId, parentNoteId: this.parentNoteId, prefix: this.prefix, notePosition: this.notePosition, - isExpanded: this.isExpanded + isExpanded: this.isExpanded, + utcDateModified: dateUtils.utcNowDateTime() }; + + // FIXME + if (true || !pojo.branchId) { + pojo.utcDateCreated = dateUtils.utcNowDateTime(); + } + + this.utcDateModified = dateUtils.utcNowDateTime(); + + return pojo; } createClone(parentNoteId, notePosition) { @@ -96,13 +106,7 @@ class Branch extends AbstractEntity { this.isExpanded = false; } - if (!this.branchId) { - this.utcDateCreated = dateUtils.utcNowDateTime(); - } - super.beforeSaving(); - - this.utcDateModified = dateUtils.utcNowDateTime(); } } diff --git a/src/services/becca/entities/note.js b/src/services/becca/entities/note.js index ccd58a29e..727bc10ab 100644 --- a/src/services/becca/entities/note.js +++ b/src/services/becca/entities/note.js @@ -34,7 +34,7 @@ class Note extends AbstractEntity { this.ownedAttributes = []; /** @param {Attribute[]|null} */ - this.attributeCache = null; + this.__attributeCache = null; /** @param {Attribute[]|null} */ this.inheritableAttributeCache = null; @@ -89,6 +89,18 @@ class Note extends AbstractEntity { this.flatTextCache = null; } + getParentBranches() { + return this.parentBranches; + } + + getParentNotes() { + return this.parents; + } + + getChildrenNotes() { + return this.children; + } + /* * 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: @@ -245,9 +257,26 @@ class Note extends AbstractEntity { return null; } - /** @return {Attribute[]} */ - get attributes() { - return this.__getAttributes([]); + /** + * @param {string} [type] - (optional) attribute type to filter + * @param {string} [name] - (optional) attribute name to filter + * @returns {Attribute[]} all note's attributes, including inherited ones + */ + getAttributes(type, name) { + this.__getAttributes([]); + + if (type && name) { + return this.__attributeCache.filter(attr => attr.type === type && attr.name === name); + } + else if (type) { + return this.__attributeCache.filter(attr => attr.type === type); + } + else if (name) { + return this.__attributeCache.filter(attr => attr.name === name); + } + else { + return this.__attributeCache.slice(); + } } __getAttributes(path) { @@ -255,7 +284,7 @@ class Note extends AbstractEntity { return []; } - if (!this.attributeCache) { + if (!this.__attributeCache) { const parentAttributes = this.ownedAttributes.slice(); const newPath = [...path, this.noteId]; @@ -277,7 +306,7 @@ class Note extends AbstractEntity { } } - this.attributeCache = []; + this.__attributeCache = []; const addedAttributeIds = new Set(); @@ -285,20 +314,20 @@ class Note extends AbstractEntity { if (!addedAttributeIds.has(attr.attributeId)) { addedAttributeIds.add(attr.attributeId); - this.attributeCache.push(attr); + this.__attributeCache.push(attr); } } this.inheritableAttributeCache = []; - for (const attr of this.attributeCache) { + for (const attr of this.__attributeCache) { if (attr.isInheritable) { this.inheritableAttributeCache.push(attr); } } } - return this.attributeCache; + return this.__attributeCache; } /** @return {Attribute[]} */ @@ -315,21 +344,21 @@ class Note extends AbstractEntity { } hasAttribute(type, name) { - return !!this.attributes.find(attr => attr.type === type && attr.name === name); + return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); } getAttributeCaseInsensitive(type, name, value) { name = name.toLowerCase(); value = value ? value.toLowerCase() : null; - return this.attributes.find( + return this.getAttributes().find( attr => attr.type === type && attr.name.toLowerCase() === name && (!value || attr.value.toLowerCase() === value)); } getRelationTarget(name) { - const relation = this.attributes.find(attr => attr.type === 'relation' && attr.name === name); + const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); return relation ? relation.targetNote : null; } @@ -448,6 +477,54 @@ class Note extends AbstractEntity { return attr ? attr.value : null; } + /** + * @param {string} [name] - label name to filter + * @returns {Attribute[]} all note's labels (attributes with type label), including inherited ones + */ + getLabels(name) { + return this.getAttributes(LABEL, name); + } + + /** + * @param {string} [name] - label name to filter + * @returns {string[]} all note's label values, including inherited ones + */ + getLabelValues(name) { + return this.getLabels(name).map(l => l.value); + } + + /** + * @param {string} [name] - label name to filter + * @returns {Attribute[]} all note's labels (attributes with type label), excluding inherited ones + */ + getOwnedLabels(name) { + return this.getOwnedAttributes(LABEL, name); + } + + /** + * @param {string} [name] - label name to filter + * @returns {string[]} all note's label values, excluding inherited ones + */ + getOwnedLabelValues(name) { + return this.getOwnedAttributes(LABEL, name).map(l => l.value); + } + + /** + * @param {string} [name] - relation name to filter + * @returns {Attribute[]} all note's relations (attributes with type relation), including inherited ones + */ + getRelations(name) { + return this.getAttributes(RELATION, name); + } + + /** + * @param {string} [name] - relation name to filter + * @returns {Attribute[]} all note's relations (attributes with type relation), excluding inherited ones + */ + getOwnedRelations(name) { + return this.getOwnedAttributes(RELATION, name); + } + get isArchived() { return this.hasAttribute('label', 'archived'); } @@ -485,7 +562,7 @@ class Note extends AbstractEntity { this.flatTextCache += this.title + ' '; - for (const attr of this.attributes) { + for (const attr of this.getAttributes()) { // it's best to use space as separator since spaces are filtered from the search string by the tokenization into words this.flatTextCache += (attr.type === 'label' ? '#' : '~') + attr.name; @@ -505,7 +582,7 @@ class Note extends AbstractEntity { invalidateThisCache() { this.flatTextCache = null; - this.attributeCache = null; + this.__attributeCache = null; this.inheritableAttributeCache = null; this.ancestorCache = null; } @@ -604,7 +681,7 @@ class Note extends AbstractEntity { } get labelCount() { - return this.attributes.filter(attr => attr.type === 'label').length; + return this.getAttributes().filter(attr => attr.type === 'label').length; } get ownedLabelCount() { @@ -612,11 +689,11 @@ class Note extends AbstractEntity { } get relationCount() { - return this.attributes.filter(attr => attr.type === 'relation' && !attr.isAutoLink()).length; + return this.getAttributes().filter(attr => attr.type === 'relation' && !attr.isAutoLink()).length; } get relationCountIncludingLinks() { - return this.attributes.filter(attr => attr.type === 'relation').length; + return this.getAttributes().filter(attr => attr.type === 'relation').length; } get ownedRelationCount() { @@ -636,11 +713,11 @@ class Note extends AbstractEntity { } get attributeCount() { - return this.attributes.length; + return this.getAttributes().length; } get ownedAttributeCount() { - return this.attributes.length; + return this.getAttributes().length; } get ancestors() { @@ -715,8 +792,8 @@ class Note extends AbstractEntity { } } - get pojo() { - return { + getPojo() { + const pojo = { noteId: this.noteId, title: this.title, isProtected: this.isProtected, @@ -727,6 +804,18 @@ class Note extends AbstractEntity { utcDateCreated: this.utcDateCreated, utcDateModified: this.utcDateModified }; + + 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; + } + } + + return pojo; } beforeSaving() { @@ -744,16 +833,8 @@ class Note extends AbstractEntity { 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; - } - } + markAsDeleted() { + sql.execute("UPDATE notes SET isDeleted = 1 WHERE noteId = ?", [this.noteId]); } } diff --git a/src/services/becca/similarity.js b/src/services/becca/similarity.js index a3d1bb2bb..0c72945b2 100644 --- a/src/services/becca/similarity.js +++ b/src/services/becca/similarity.js @@ -73,7 +73,7 @@ function buildRewardMap(note) { addToRewardMap(ancestorNote.title, 0.3); } - for (const branch of ancestorNote.parentBranches) { + for (const branch of ancestorNote.getParentBranches()) { addToRewardMap(branch.prefix, 0.3); } } @@ -84,11 +84,11 @@ function buildRewardMap(note) { addToRewardMap(note.title, 1); } - for (const branch of note.parentBranches) { + for (const branch of note.getParentBranches()) { addToRewardMap(branch.prefix, 1); } - for (const attr of note.attributes) { + for (const attr of note.getAttributes()) { if (attr.name.startsWith('child:') || attr.name.startsWith('relation:') || attr.name.startsWith('label:')) { @@ -222,7 +222,7 @@ function splitToWords(text) { * that it doesn't actually need to be shown to the user. */ function hasConnectingRelation(sourceNote, targetNote) { - return sourceNote.attributes.find(attr => attr.type === 'relation' + return sourceNote.getAttributes().find(attr => attr.type === 'relation' && ['includenotelink', 'imagelink'].includes(attr.name) && attr.value === targetNote.noteId); } @@ -243,7 +243,7 @@ async function findSimilarNotes(noteId) { dateLimits = buildDateLimits(baseNote); } catch (e) { - throw new Error(`Date limits failed with ${e.message}, entity: ${JSON.stringify(baseNote.pojo)}`); + throw new Error(`Date limits failed with ${e.message}, entity: ${JSON.stringify(baseNote.getPojo())}`); } const rewardMap = buildRewardMap(baseNote); @@ -298,7 +298,7 @@ async function findSimilarNotes(noteId) { score += gatherRewards(parentNote.title, 0.3); } - for (const branch of parentNote.parentBranches) { + for (const branch of parentNote.getParentBranches()) { score += gatherRewards(branch.prefix, 0.3) + gatherAncestorRewards(branch.parentNote); } @@ -319,11 +319,11 @@ async function findSimilarNotes(noteId) { score += gatherRewards(candidateNote.title); } - for (const branch of candidateNote.parentBranches) { + for (const branch of candidateNote.getParentBranches()) { score += gatherRewards(branch.prefix); } - for (const attr of candidateNote.attributes) { + for (const attr of candidateNote.getAttributes()) { if (attr.name.startsWith('child:') || attr.name.startsWith('relation:') || attr.name.startsWith('label:')) { @@ -342,7 +342,7 @@ async function findSimilarNotes(noteId) { let factor = 1; if (!value.startsWith) { - log.info(`Unexpected falsy value for attribute ${JSON.stringify(attr.pojo)}`); + log.info(`Unexpected falsy value for attribute ${JSON.stringify(attr.getPojo())}`); continue; } else if (value.startsWith('http')) { @@ -434,8 +434,6 @@ async function findSimilarNotes(noteId) { for (const {noteId} of results) { const note = becca.notes[noteId]; - console.log("NOTE", note.pojo); - displayRewards = true; ancestorRewardCache = {}; // reset cache const totalReward = computeScore(note); diff --git a/src/services/notes.js b/src/services/notes.js index 5469fb0a4..33a3ff3ce 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -7,8 +7,11 @@ const eventService = require('./events'); const repository = require('./repository'); const cls = require('../services/cls'); const Note = require('../entities/note'); +const BeccaNote = require('../services/becca/entities/note.js'); const Branch = require('../entities/branch'); +const BeccaBranch = require('../services/becca/entities/branch.js'); const Attribute = require('../entities/attribute'); +const BeccaAttribute = require('../services/becca/entities/attribute.js'); const protectedSessionService = require('../services/protected_session'); const log = require('../services/log'); const utils = require('../services/utils'); @@ -67,7 +70,7 @@ function deriveMime(type, mime) { function copyChildAttributes(parentNote, childNote) { for (const attr of parentNote.getAttributes()) { if (attr.name.startsWith("child:")) { - new Attribute({ + new BeccaAttribute({ noteId: childNote.noteId, type: attr.type, name: attr.name.substr(6), @@ -75,8 +78,6 @@ function copyChildAttributes(parentNote, childNote) { position: attr.position, isInheritable: attr.isInheritable }).save(); - - childNote.invalidateAttributeCache(); } } } @@ -99,7 +100,7 @@ function copyChildAttributes(parentNote, childNote) { * @return {{note: Note, branch: Branch}} */ function createNewNote(params) { - const parentNote = repository.getNote(params.parentNoteId); + const parentNote = becca.notes[params.parentNoteId]; if (!parentNote) { throw new Error(`Parent note "${params.parentNoteId}" not found.`); @@ -110,7 +111,7 @@ function createNewNote(params) { } return sql.transactional(() => { - const note = new Note({ + const note = new BeccaNote(becca,{ noteId: params.noteId, // optionally can force specific noteId title: params.title, isProtected: !!params.isProtected, @@ -120,7 +121,7 @@ function createNewNote(params) { note.setContent(params.content); - const branch = new Branch({ + const branch = new BeccaBranch(becca,{ noteId: note.noteId, parentNoteId: params.parentNoteId, notePosition: params.notePosition !== undefined ? params.notePosition : getNewNotePosition(params.parentNoteId), @@ -416,11 +417,12 @@ function saveLinks(note, content) { throw new Error("Unrecognized type " + note.type); } - const existingLinks = note.getLinks(); + const existingLinks = note.getRelations().filter(rel => + ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(rel.name)); for (const foundLink of foundLinks) { - const targetNote = repository.getNote(foundLink.value); - if (!targetNote || targetNote.isDeleted) { + const targetNote = becca.notes[foundLink.value]; + if (!targetNote) { continue; } @@ -429,7 +431,7 @@ function saveLinks(note, content) { && existingLink.name === foundLink.name); if (!existingLink) { - const newLink = new Attribute({ + const newLink = new BeccaAttribute({ noteId: note.noteId, type: 'relation', name: foundLink.name, @@ -438,10 +440,6 @@ function saveLinks(note, content) { existingLinks.push(newLink); } - else if (existingLink.isDeleted) { - existingLink.isDeleted = false; - existingLink.save(); - } // else the link exists so we don't need to do anything } @@ -451,8 +449,7 @@ function saveLinks(note, content) { && existingLink.name === foundLink.name)); for (const unusedLink of unusedLinks) { - unusedLink.isDeleted = true; - unusedLink.save(); + unusedLink.markAsDeleted(); } return content; diff --git a/src/services/repository.js b/src/services/repository.js index 9d8190876..045e4b51a 100644 --- a/src/services/repository.js +++ b/src/services/repository.js @@ -94,11 +94,19 @@ function updateEntity(entity) { entity.beforeSaving(); } - const clone = Object.assign({}, entity); + let clone; - // this check requires that updatePojo is not static - if (entity.updatePojo) { - entity.updatePojo(clone); + if (entity.getPojo) { + clone = entity.getPojo(); + } + else { + // FIXME: delete this branch after migration to becca + clone = Object.assign({}, entity); + + // this check requires that updatePojo is not static + if (entity.updatePojo) { + entity.updatePojo(clone); + } } for (const key in clone) {