server-ts: Port share/shaca/snote

This commit is contained in:
Elian Doran 2024-04-09 22:13:01 +03:00
parent b07df6061f
commit 3e4b0d5f91
No known key found for this signature in database
3 changed files with 123 additions and 118 deletions

View File

@ -1,53 +1,57 @@
"use strict"; "use strict";
const sql = require('../../sql'); import sql = require('../../sql');
const utils = require('../../../services/utils'); import utils = require('../../../services/utils');
const AbstractShacaEntity = require('./abstract_shaca_entity'); import AbstractShacaEntity = require('./abstract_shaca_entity');
const escape = require('escape-html'); import escape = require('escape-html');
import { AttributeRow } from '../../../becca/entities/rows';
import { Blob } from '../../../services/blob-interface';
const LABEL = 'label'; const LABEL = 'label';
const RELATION = 'relation'; const RELATION = 'relation';
const CREDENTIALS = 'shareCredentials'; const CREDENTIALS = 'shareCredentials';
const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS; const isCredentials = (attr: AttributeRow) => attr.type === 'label' && attr.name === CREDENTIALS;
type NoteRow = [ string, string, string, string, string, string, boolean ];
class SNote extends AbstractShacaEntity { class SNote extends AbstractShacaEntity {
constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]) { private noteId: string;
private title: string;
private type: string;
private mime: string;
private blobId: string;
private utcDateModified: string;
private isProtected: boolean;
private parentBranches: any[]; // fixme: set right data type once SBranch is ported.
private parents: SNote[];
private children: SNote[];
private ownedAttributes: any[]; // fixme: set right data type once SAttribute is ported.
private __attributeCache: any[] | null; // fixme
private __inheritableAttributeCache: any[] | null; // fixme
private targetRelations: any[]; // fixme: SAttribute[]
private attachments: any[] ; // fixme: SAttachment[]
constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) {
super(); super();
/** @param {string} */
this.noteId = noteId; this.noteId = noteId;
/** @param {string} */
this.title = isProtected ? "[protected]" : title; this.title = isProtected ? "[protected]" : title;
/** @param {string} */
this.type = type; this.type = type;
/** @param {string} */
this.mime = mime; this.mime = mime;
/** @param {string} */
this.blobId = blobId; this.blobId = blobId;
/** @param {string} */
this.utcDateModified = utcDateModified; // used for caching of images this.utcDateModified = utcDateModified; // used for caching of images
/** @param {boolean} */
this.isProtected = isProtected; this.isProtected = isProtected;
/** @param {SBranch[]} */
this.parentBranches = []; this.parentBranches = [];
/** @param {SNote[]} */
this.parents = []; this.parents = [];
/** @param {SNote[]} */
this.children = []; this.children = [];
/** @param {SAttribute[]} */
this.ownedAttributes = []; this.ownedAttributes = [];
/** @param {SAttribute[]|null} */
this.__attributeCache = null; this.__attributeCache = null;
/** @param {SAttribute[]|null} */
this.__inheritableAttributeCache = null; this.__inheritableAttributeCache = null;
/** @param {SAttribute[]} */
this.targetRelations = []; this.targetRelations = [];
/** @param {SAttachment[]} */
this.attachments = []; this.attachments = [];
this.shaca.notes[this.noteId] = this; this.shaca.notes[this.noteId] = this;
@ -102,7 +106,7 @@ class SNote extends AbstractShacaEntity {
} }
getContent(silentNotFoundError = false) { getContent(silentNotFoundError = false) {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); const row = sql.getRow<Pick<Blob, "content">>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) { if (!row) {
if (silentNotFoundError) { if (silentNotFoundError) {
@ -116,52 +120,56 @@ class SNote extends AbstractShacaEntity {
let content = row.content; let content = row.content;
if (this.hasStringContent()) { if (this.hasStringContent()) {
return content === null if (content == null) {
? "" return "";
: content.toString("utf-8"); }
if (typeof content !== "string") {
return content.toString("utf-8");
}
return content;
} }
else { else {
return content; return content;
} }
} }
/** @returns {boolean} true if the note has string content (not binary) */ /** @returns true if the note has string content (not binary) */
hasStringContent() { hasStringContent() {
return utils.isStringNote(this.type, this.mime); return utils.isStringNote(this.type, this.mime);
} }
/** /**
* @param {string} [type] - (optional) attribute type to filter * @param type - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter * @param name - (optional) attribute name to filter
* @returns {SAttribute[]} all note's attributes, including inherited ones * @returns all note's attributes, including inherited ones
*/ */
getAttributes(type, name) { getAttributes(type?: string, name?: string) {
if (!this.__attributeCache) { let attributeCache = this.__attributeCache;
this.__getAttributes([]); if (!attributeCache) {
attributeCache = this.__getAttributes([]);
} }
if (type && name) { if (type && name) {
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name && !isCredentials(attr)); return attributeCache.filter(attr => attr.type === type && attr.name === name && !isCredentials(attr));
} }
else if (type) { else if (type) {
return this.__attributeCache.filter(attr => attr.type === type && !isCredentials(attr)); return attributeCache.filter(attr => attr.type === type && !isCredentials(attr));
} }
else if (name) { else if (name) {
return this.__attributeCache.filter(attr => attr.name === name && !isCredentials(attr)); return attributeCache.filter(attr => attr.name === name && !isCredentials(attr));
} }
else { else {
return this.__attributeCache.filter(attr => !isCredentials(attr)); return attributeCache.filter(attr => !isCredentials(attr));
} }
} }
/** @returns {SAttribute[]} */
getCredentials() { getCredentials() {
this.__getAttributes([]); return this.__getAttributes([]).filter(isCredentials);
return this.__attributeCache.filter(isCredentials);
} }
__getAttributes(path) { __getAttributes(path: string[]) {
if (path.includes(this.noteId)) { if (path.includes(this.noteId)) {
return []; return [];
} }
@ -213,213 +221,212 @@ class SNote extends AbstractShacaEntity {
} }
/** @returns {SAttribute[]} */ /** @returns {SAttribute[]} */
__getInheritableAttributes(path) { __getInheritableAttributes(path: string[]) {
if (path.includes(this.noteId)) { if (path.includes(this.noteId)) {
return []; return [];
} }
if (!this.__inheritableAttributeCache) { if (!this.__inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache return this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
} else {
return this.__inheritableAttributeCache;
} }
return this.__inheritableAttributeCache;
} }
/** @returns {boolean} */ hasAttribute(type: string, name: string) {
hasAttribute(type, name) {
return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); return !!this.getAttributes().find(attr => attr.type === type && attr.name === name);
} }
/** @returns {SNote|null} */ /** @returns {SNote|null} */
getRelationTarget(name) { getRelationTarget(name: string) {
const relation = this.getAttributes().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; return relation ? relation.targetNote : null;
} }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {boolean} true if label exists (including inherited) * @returns true if label exists (including inherited)
*/ */
hasLabel(name) { return this.hasAttribute(LABEL, name); } hasLabel(name: string) { return this.hasAttribute(LABEL, name); }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {boolean} true if label exists (including inherited) and does not have "false" value. * @returns {boolean} true if label exists (including inherited) and does not have "false" value.
*/ */
isLabelTruthy(name) { isLabelTruthy(name: string) {
const label = this.getLabel(name); const label = this.getLabel(name);
if (!label) { if (!label) {
return false; return false;
} }
return label && label.value !== 'false'; return !!label && label.value !== 'false';
} }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {boolean} true if label exists (excluding inherited) * @returns true if label exists (excluding inherited)
*/ */
hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); } hasOwnedLabel(name: string) { return this.hasOwnedAttribute(LABEL, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {boolean} true if relation exists (including inherited) * @returns true if relation exists (including inherited)
*/ */
hasRelation(name) { return this.hasAttribute(RELATION, name); } hasRelation(name: string) { return this.hasAttribute(RELATION, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {boolean} true if relation exists (excluding inherited) * @returns true if relation exists (excluding inherited)
*/ */
hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); } hasOwnedRelation(name: string) { return this.hasOwnedAttribute(RELATION, name); }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {SAttribute|null} label if it exists, null otherwise * @returns {SAttribute|null} label if it exists, null otherwise
*/ */
getLabel(name) { return this.getAttribute(LABEL, name); } getLabel(name: string) { return this.getAttribute(LABEL, name); }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {SAttribute|null} label if it exists, null otherwise * @returns {SAttribute|null} label if it exists, null otherwise
*/ */
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); } getOwnedLabel(name: string) { return this.getOwnedAttribute(LABEL, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {SAttribute|null} relation if it exists, null otherwise * @returns {SAttribute|null} relation if it exists, null otherwise
*/ */
getRelation(name) { return this.getAttribute(RELATION, name); } getRelation(name: string) { return this.getAttribute(RELATION, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {SAttribute|null} relation if it exists, null otherwise * @returns {SAttribute|null} relation if it exists, null otherwise
*/ */
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); } getOwnedRelation(name: string) { return this.getOwnedAttribute(RELATION, name); }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {string|null} label value if label exists, null otherwise * @returns {string|null} label value if label exists, null otherwise
*/ */
getLabelValue(name) { return this.getAttributeValue(LABEL, name); } getLabelValue(name: string) { return this.getAttributeValue(LABEL, name); }
/** /**
* @param {string} name - label name * @param name - label name
* @returns {string|null} label value if label exists, null otherwise * @returns {string|null} label value if label exists, null otherwise
*/ */
getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); } getOwnedLabelValue(name: string) { return this.getOwnedAttributeValue(LABEL, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {string|null} relation value if relation exists, null otherwise * @returns {string|null} relation value if relation exists, null otherwise
*/ */
getRelationValue(name) { return this.getAttributeValue(RELATION, name); } getRelationValue(name: string) { return this.getAttributeValue(RELATION, name); }
/** /**
* @param {string} name - relation name * @param name - relation name
* @returns {string|null} relation value if relation exists, null otherwise * @returns {string|null} relation value if relation exists, null otherwise
*/ */
getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); } getOwnedRelationValue(name: string) { return this.getOwnedAttributeValue(RELATION, name); }
/** /**
* @param {string} type - attribute type (label, relation, etc.) * @param type - attribute type (label, relation, etc.)
* @param {string} name - attribute name * @param name - attribute name
* @returns {boolean} true if note has an attribute with given type and name (excluding inherited) * @returns true if note has an attribute with given type and name (excluding inherited)
*/ */
hasOwnedAttribute(type, name) { hasOwnedAttribute(type: string, name: string) {
return !!this.getOwnedAttribute(type, name); return !!this.getOwnedAttribute(type, name);
} }
/** /**
* @param {string} type - attribute type (label, relation, etc.) * @param type - attribute type (label, relation, etc.)
* @param {string} name - attribute name * @param name - attribute name
* @returns {SAttribute} attribute of the given type and name. If there are more such attributes, first is returned. * @returns {SAttribute} attribute of the given type and name. If there are more such attributes, first is returned.
* Returns null if there's no such attribute belonging to this note. * Returns null if there's no such attribute belonging to this note.
*/ */
getAttribute(type, name) { getAttribute(type: string, name: string) {
const attributes = this.getAttributes(); const attributes = this.getAttributes();
return attributes.find(attr => attr.type === type && attr.name === name); return attributes.find(attr => attr.type === type && attr.name === name);
} }
/** /**
* @param {string} type - attribute type (label, relation, etc.) * @param type - attribute type (label, relation, etc.)
* @param {string} name - attribute name * @param name - attribute name
* @returns {string|null} attribute value of the given type and name or null if no such attribute exists. * @returns {string|null} attribute value of the given type and name or null if no such attribute exists.
*/ */
getAttributeValue(type, name) { getAttributeValue(type: string, name: string) {
const attr = this.getAttribute(type, name); const attr = this.getAttribute(type, name);
return attr ? attr.value : null; return attr ? attr.value : null;
} }
/** /**
* @param {string} type - attribute type (label, relation, etc.) * @param type - attribute type (label, relation, etc.)
* @param {string} name - attribute name * @param name - attribute name
* @returns {string|null} attribute value of the given type and name or null if no such attribute exists. * @returns attribute value of the given type and name or null if no such attribute exists.
*/ */
getOwnedAttributeValue(type, name) { getOwnedAttributeValue(type: string, name: string) {
const attr = this.getOwnedAttribute(type, name); const attr = this.getOwnedAttribute(type, name);
return attr ? attr.value : null; return attr ? attr.value as string : null; // FIXME
} }
/** /**
* @param {string} [name] - label name to filter * @param name - label name to filter
* @returns {SAttribute[]} all note's labels (attributes with type label), including inherited ones * @returns {SAttribute[]} all note's labels (attributes with type label), including inherited ones
*/ */
getLabels(name) { getLabels(name: string) {
return this.getAttributes(LABEL, name); return this.getAttributes(LABEL, name);
} }
/** /**
* @param {string} [name] - label name to filter * @param name - label name to filter
* @returns {string[]} all note's label values, including inherited ones * @returns all note's label values, including inherited ones
*/ */
getLabelValues(name) { getLabelValues(name: string) {
return this.getLabels(name).map(l => l.value); return this.getLabels(name).map(l => l.value) as string[]; // FIXME
} }
/** /**
* @param {string} [name] - label name to filter * @param name - label name to filter
* @returns {SAttribute[]} all note's labels (attributes with type label), excluding inherited ones * @returns {SAttribute[]} all note's labels (attributes with type label), excluding inherited ones
*/ */
getOwnedLabels(name) { getOwnedLabels(name: string) {
return this.getOwnedAttributes(LABEL, name); return this.getOwnedAttributes(LABEL, name);
} }
/** /**
* @param {string} [name] - label name to filter * @param name - label name to filter
* @returns {string[]} all note's label values, excluding inherited ones * @returns {string[]} all note's label values, excluding inherited ones
*/ */
getOwnedLabelValues(name) { getOwnedLabelValues(name: string) {
return this.getOwnedAttributes(LABEL, name).map(l => l.value); return this.getOwnedAttributes(LABEL, name).map(l => l.value);
} }
/** /**
* @param {string} [name] - relation name to filter * @param name - relation name to filter
* @returns {SAttribute[]} all note's relations (attributes with type relation), including inherited ones * @returns {SAttribute[]} all note's relations (attributes with type relation), including inherited ones
*/ */
getRelations(name) { getRelations(name: string) {
return this.getAttributes(RELATION, name); return this.getAttributes(RELATION, name);
} }
/** /**
* @param {string} [name] - relation name to filter * @param {string} name - relation name to filter
* @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones * @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones
*/ */
getOwnedRelations(name) { getOwnedRelations(name: string) {
return this.getOwnedAttributes(RELATION, name); return this.getOwnedAttributes(RELATION, name);
} }
/** /**
* @param {string} [type] - (optional) attribute type to filter * @param {string} type - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter * @param {string} name - (optional) attribute name to filter
* @returns {SAttribute[]} note's "owned" attributes - excluding inherited ones * @returns {SAttribute[]} note's "owned" attributes - excluding inherited ones
*/ */
getOwnedAttributes(type, name) { getOwnedAttributes(type: string, name: string) {
// it's a common mistake to include # or ~ into attribute name // it's a common mistake to include # or ~ into attribute name
if (name && ["#", "~"].includes(name[0])) { if (name && ["#", "~"].includes(name[0])) {
name = name.substr(1); name = name.substr(1);
@ -444,18 +451,16 @@ class SNote extends AbstractShacaEntity {
* *
* This method can be significantly faster than the getAttribute() * This method can be significantly faster than the getAttribute()
*/ */
getOwnedAttribute(type, name) { getOwnedAttribute(type: string, name: string) {
const attrs = this.getOwnedAttributes(type, name); const attrs = this.getOwnedAttributes(type, name);
return attrs.length > 0 ? attrs[0] : null; return attrs.length > 0 ? attrs[0] : null;
} }
/** @returns {boolean} */
get isArchived() { get isArchived() {
return this.hasAttribute('label', 'archived'); return this.hasAttribute('label', 'archived');
} }
/** @returns {boolean} */
isInherited() { isInherited() {
return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit');
} }
@ -471,7 +476,7 @@ class SNote extends AbstractShacaEntity {
} }
/** @returns {SAttachment} */ /** @returns {SAttachment} */
getAttachmentByTitle(title) { getAttachmentByTitle(title: string) {
return this.attachments.find(attachment => attachment.title === title); return this.attachments.find(attachment => attachment.title === title);
} }

View File

@ -3,7 +3,7 @@
const sql = require('../sql'); const sql = require('../sql');
const shaca = require('./shaca.js'); const shaca = require('./shaca.js');
const log = require('../../services/log'); const log = require('../../services/log');
const SNote = require('./entities/snote.js'); const SNote = require('./entities/snote');
const SBranch = require('./entities/sbranch.js'); const SBranch = require('./entities/sbranch.js');
const SAttribute = require('./entities/sattribute.js'); const SAttribute = require('./entities/sattribute.js');
const SAttachment = require('./entities/sattachment.js'); const SAttachment = require('./entities/sattachment.js');

View File

@ -19,7 +19,7 @@ function getRawRows<T>(query: string, params = []): T[] {
return dbConnection.prepare(query).raw().all(params) as T[]; return dbConnection.prepare(query).raw().all(params) as T[];
} }
function getRow<T>(query: string, params = []): T { function getRow<T>(query: string, params: string[] = []): T {
return dbConnection.prepare(query).get(params) as T; return dbConnection.prepare(query).get(params) as T;
} }