basic note creation with becca

This commit is contained in:
zadam 2021-04-25 21:19:18 +02:00
parent 8d8d654fe8
commit 6c5b1420d2
7 changed files with 165 additions and 73 deletions

View file

@ -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;

View file

@ -113,7 +113,7 @@ class Attribute extends AbstractEntity {
}
}
get pojo() {
getPojo() {
return {
attributeId: this.attributeId,
noteId: this.noteId,

View file

@ -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();
}
}

View file

@ -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]);
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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) {