From 6dd2cd39aab0ce7f58f57a266dc859cf3b48f87e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 17 Feb 2024 00:44:44 +0200 Subject: [PATCH] server-ts: Convert a few classes in becca --- src/becca/becca-interface.ts | 2 + ...cca_entity.js => abstract_becca_entity.ts} | 79 ++++++--------- .../{battachment.js => battachment.ts} | 98 +++++++++---------- src/becca/entities/{bnote.js => bnote.ts} | 24 ++--- .../{brecent_note.js => brecent_note.ts} | 15 +-- .../entities/{brevision.js => brevision.ts} | 97 +++++++++--------- src/becca/entities/rows.ts | 36 +++++++ .../search/{note_set.js => note_set.ts} | 2 +- 8 files changed, 182 insertions(+), 171 deletions(-) rename src/becca/entities/{abstract_becca_entity.js => abstract_becca_entity.ts} (86%) rename src/becca/entities/{battachment.js => battachment.ts} (77%) rename src/becca/entities/{bnote.js => bnote.ts} (98%) rename src/becca/entities/{brecent_note.js => brecent_note.ts} (69%) rename src/becca/entities/{brevision.js => brevision.ts} (73%) create mode 100644 src/becca/entities/rows.ts rename src/services/search/{note_set.js => note_set.ts} (97%) diff --git a/src/becca/becca-interface.ts b/src/becca/becca-interface.ts index 0f368961c..294442cbb 100644 --- a/src/becca/becca-interface.ts +++ b/src/becca/becca-interface.ts @@ -7,6 +7,8 @@ import NotFoundError = require('../errors/not_found_error'); * There's a similar frontend cache Froca, and share cache Shaca. */ class Becca { + notes!: Record; + constructor() { this.reset(); } diff --git a/src/becca/entities/abstract_becca_entity.js b/src/becca/entities/abstract_becca_entity.ts similarity index 86% rename from src/becca/entities/abstract_becca_entity.js rename to src/becca/entities/abstract_becca_entity.ts index b72b13381..35d0d2979 100644 --- a/src/becca/entities/abstract_becca_entity.js +++ b/src/becca/entities/abstract_becca_entity.ts @@ -1,47 +1,44 @@ "use strict"; -const utils = require('../../services/utils'); -const sql = require('../../services/sql'); -const entityChangesService = require('../../services/entity_changes.js'); -const eventService = require('../../services/events.js'); -const dateUtils = require('../../services/date_utils'); -const cls = require('../../services/cls'); -const log = require('../../services/log'); -const protectedSessionService = require('../../services/protected_session'); -const blobService = require('../../services/blob.js'); +import utils = require('../../services/utils'); +import sql = require('../../services/sql'); +import entityChangesService = require('../../services/entity_changes.js'); +import eventService = require('../../services/events'); +import dateUtils = require('../../services/date_utils'); +import cls = require('../../services/cls'); +import log = require('../../services/log'); +import protectedSessionService = require('../../services/protected_session'); +import blobService = require('../../services/blob.js'); +import Becca = require('../becca-interface'); -let becca = null; +let becca: Becca | null = null; /** * Base class for all backend entities. */ -class AbstractBeccaEntity { - /** @protected */ - beforeSaving() { +abstract class AbstractBeccaEntity { + + protected utcDateModified?: string; + + protected beforeSaving() { if (!this[this.constructor.primaryKeyName]) { this[this.constructor.primaryKeyName] = utils.newEntityId(); } } - /** @protected */ - getUtcDateChanged() { + protected getUtcDateChanged() { return this.utcDateModified || this.utcDateCreated; } - /** - * @protected - * @returns {Becca} - */ - get becca() { + protected get becca(): Becca { if (!becca) { - becca = require('../becca.js'); + becca = require('../becca'); } - return becca; + return becca as Becca; } - /** @protected */ - putEntityChange(isDeleted) { + protected putEntityChange(isDeleted: boolean) { entityChangesService.putEntityChange({ entityName: this.constructor.entityName, entityId: this[this.constructor.primaryKeyName], @@ -52,11 +49,7 @@ class AbstractBeccaEntity { }); } - /** - * @protected - * @returns {string} - */ - generateHash(isDeleted) { + protected generateHash(isDeleted: boolean): string { let contentToHash = ""; for (const propertyName of this.constructor.hashedProperties) { @@ -70,25 +63,16 @@ class AbstractBeccaEntity { return utils.hash(contentToHash).substr(0, 10); } - /** @protected */ - getPojoToSave() { + protected getPojoToSave() { return this.getPojo(); } - /** - * @protected - * @abstract - */ - getPojo() { - throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`) - } + protected abstract getPojo(): {}; /** * Saves entity - executes SQL, but doesn't commit the transaction on its own - * - * @returns {this} */ - save(opts = {}) { + save(opts = {}): this { const entityName = this.constructor.entityName; const primaryKeyName = this.constructor.primaryKeyName; @@ -124,8 +108,7 @@ class AbstractBeccaEntity { return this; } - /** @protected */ - _setContent(content, opts = {}) { + protected _setContent(content, opts = {}) { // client code asks to save entity even if blobId didn't change (something else was changed) opts.forceSave = !!opts.forceSave; opts.forceFrontendReload = !!opts.forceFrontendReload; @@ -243,11 +226,7 @@ class AbstractBeccaEntity { return newBlobId; } - /** - * @protected - * @returns {string|Buffer} - */ - _getContent() { + protected _getContent(): string | Buffer { const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); if (!row) { @@ -261,8 +240,6 @@ class AbstractBeccaEntity { * Mark the entity as (soft) deleted. It will be completely erased later. * * This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. - * - * @param [deleteId=null] */ markAsDeleted(deleteId = null) { const entityId = this[this.constructor.primaryKeyName]; @@ -306,4 +283,4 @@ class AbstractBeccaEntity { } } -module.exports = AbstractBeccaEntity; +export = AbstractBeccaEntity; diff --git a/src/becca/entities/battachment.js b/src/becca/entities/battachment.ts similarity index 77% rename from src/becca/entities/battachment.js rename to src/becca/entities/battachment.ts index 5445ec3de..7b6c3f657 100644 --- a/src/becca/entities/battachment.js +++ b/src/becca/entities/battachment.ts @@ -1,16 +1,24 @@ "use strict"; -const utils = require('../../services/utils'); -const dateUtils = require('../../services/date_utils'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const sql = require('../../services/sql'); -const protectedSessionService = require('../../services/protected_session'); -const log = require('../../services/log'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import sql = require('../../services/sql'); +import protectedSessionService = require('../../services/protected_session'); +import log = require('../../services/log'); +import { AttachmentRow } from './rows'; const attachmentRoleToNoteTypeMapping = { 'image': 'image' }; +interface ContentOpts { + /** will also save this BAttachment entity */ + forceFullSave: boolean; + /** override frontend heuristics on when to reload, instruct to reload */ + forceFrontendReload: boolean; +} + /** * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for * larger amounts of data and generally not accessible to the user. @@ -22,7 +30,24 @@ class BAttachment extends AbstractBeccaEntity { static get primaryKeyName() { return "attachmentId"; } static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; } - constructor(row) { + noteId?: number; + attachmentId?: string; + /** either noteId or revisionId to which this attachment belongs */ + ownerId: string; + role: string; + mime: string; + title: string; + type?: keyof typeof attachmentRoleToNoteTypeMapping; + position?: number; + blobId: string; + isProtected?: boolean; + dateModified?: string; + utcDateScheduledForErasureSince?: string; + /** optionally added to the entity */ + contentLength?: number; + isDecrypted?: boolean; + + constructor(row: AttachmentRow) { super(); if (!row.ownerId?.trim()) { @@ -35,43 +60,23 @@ class BAttachment extends AbstractBeccaEntity { throw new Error("'title' must be given to initialize a Attachment entity"); } - /** @type {string} */ this.attachmentId = row.attachmentId; - /** - * either noteId or revisionId to which this attachment belongs - * @type {string} - */ this.ownerId = row.ownerId; - /** @type {string} */ this.role = row.role; - /** @type {string} */ this.mime = row.mime; - /** @type {string} */ this.title = row.title; - /** @type {int} */ this.position = row.position; - /** @type {string} */ this.blobId = row.blobId; - /** @type {boolean} */ this.isProtected = !!row.isProtected; - /** @type {string} */ this.dateModified = row.dateModified; - /** @type {string} */ this.utcDateModified = row.utcDateModified; - /** @type {string} */ this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince; - - /** - * optionally added to the entity - * @type {int} - */ this.contentLength = row.contentLength; this.decrypt(); } - /** @returns {BAttachment} */ - copy() { + copy(): BAttachment { return new BAttachment({ ownerId: this.ownerId, role: this.role, @@ -82,13 +87,12 @@ class BAttachment extends AbstractBeccaEntity { }); } - /** @returns {BNote} */ - getNote() { + getNote(): BNote { return this.becca.notes[this.ownerId]; } - /** @returns {boolean} true if the note has string content (not binary) */ - hasStringContent() { + /** @returns true if the note has string content (not binary) */ + hasStringContent(): boolean { return utils.isStringNote(this.type, this.mime); } @@ -110,32 +114,24 @@ class BAttachment extends AbstractBeccaEntity { if (!this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { - this.title = protectedSessionService.decryptString(this.title); + this.title = protectedSessionService.decryptString(this.title) || ""; this.isDecrypted = true; } - catch (e) { + catch (e: any) { log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`); } } } - /** @returns {string|Buffer} */ - getContent() { + getContent(): string | Buffer { return this._getContent(); } - /** - * @param content - * @param {object} [opts] - * @param {object} [opts.forceSave=false] - will also save this BAttachment entity - * @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload - */ - setContent(content, opts) { + setContent(content: any, opts: ContentOpts) { this._setContent(content, opts); } - /** @returns {{note: BNote, branch: BBranch}} */ - convertToNote() { + convertToNote(): { note: BNote, branch: BBranch } { if (this.type === 'search') { throw new Error(`Note of type search cannot have child notes`); } @@ -195,9 +191,9 @@ class BAttachment extends AbstractBeccaEntity { super.beforeSaving(); if (this.position === undefined || this.position === null) { - this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0) - FROM attachments - WHERE ownerId = ?`, [this.noteId]); + this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0) + FROM attachments + WHERE ownerId = ?`, [this.noteId]); } this.dateModified = dateUtils.localNowDateTime(); @@ -210,7 +206,7 @@ class BAttachment extends AbstractBeccaEntity { ownerId: this.ownerId, role: this.role, mime: this.mime, - title: this.title, + title: this.title || undefined, position: this.position, blobId: this.blobId, isProtected: !!this.isProtected, @@ -228,7 +224,7 @@ class BAttachment extends AbstractBeccaEntity { if (pojo.isProtected) { if (this.isDecrypted) { - pojo.title = protectedSessionService.encrypt(pojo.title); + pojo.title = protectedSessionService.encrypt(pojo.title || "") || undefined; } else { // updating protected note outside of protected session means we will keep original ciphertexts @@ -240,4 +236,4 @@ class BAttachment extends AbstractBeccaEntity { } } -module.exports = BAttachment; +export = BAttachment; diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.ts similarity index 98% rename from src/becca/entities/bnote.js rename to src/becca/entities/bnote.ts index 7372b4d0d..5cfded718 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.ts @@ -1,17 +1,17 @@ "use strict"; -const protectedSessionService = require('../../services/protected_session.js'); -const log = require('../../services/log'); -const sql = require('../../services/sql'); -const utils = require('../../services/utils'); -const dateUtils = require('../../services/date_utils'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const BRevision = require('./brevision.js'); -const BAttachment = require('./battachment.js'); -const TaskContext = require('../../services/task_context.js'); -const dayjs = require("dayjs"); -const utc = require('dayjs/plugin/utc'); -const eventService = require('../../services/events.js'); +import protectedSessionService = require('../../services/protected_session'); +import log = require('../../services/log'); +import sql = require('../../services/sql'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import BRevision = require('./brevision.js'); +import BAttachment = require('./battachment.ts'); +import TaskContext = require('../../services/task_context.js'); +import dayjs = require("dayjs"); +import utc = require('dayjs/plugin/utc'); +import eventService = require('../../services/events.ts'); dayjs.extend(utc); const LABEL = 'label'; diff --git a/src/becca/entities/brecent_note.js b/src/becca/entities/brecent_note.ts similarity index 69% rename from src/becca/entities/brecent_note.js rename to src/becca/entities/brecent_note.ts index 8a840e2d0..a6796d0ff 100644 --- a/src/becca/entities/brecent_note.js +++ b/src/becca/entities/brecent_note.ts @@ -1,7 +1,9 @@ "use strict"; -const dateUtils = require('../../services/date_utils'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import { RecentNoteRow } from "./rows"; + +import dateUtils = require('../../services/date_utils'); +import AbstractBeccaEntity = require('./abstract_becca_entity.js'); /** * RecentNote represents recently visited note. @@ -12,14 +14,15 @@ class BRecentNote extends AbstractBeccaEntity { static get entityName() { return "recent_notes"; } static get primaryKeyName() { return "noteId"; } - constructor(row) { + noteId: string; + notePath: string; + utcDateCreated: string; + + constructor(row: RecentNoteRow) { super(); - /** @type {string} */ this.noteId = row.noteId; - /** @type {string} */ this.notePath = row.notePath; - /** @type {string} */ this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime(); } diff --git a/src/becca/entities/brevision.js b/src/becca/entities/brevision.ts similarity index 73% rename from src/becca/entities/brevision.js rename to src/becca/entities/brevision.ts index ad819ad90..fb590ddbb 100644 --- a/src/becca/entities/brevision.js +++ b/src/becca/entities/brevision.ts @@ -1,12 +1,22 @@ "use strict"; -const protectedSessionService = require('../../services/protected_session'); -const utils = require('../../services/utils'); -const dateUtils = require('../../services/date_utils'); -const becca = require('../becca.js'); -const AbstractBeccaEntity = require('./abstract_becca_entity.js'); -const sql = require('../../services/sql'); -const BAttachment = require('./battachment.js'); +import protectedSessionService = require('../../services/protected_session'); +import utils = require('../../services/utils'); +import dateUtils = require('../../services/date_utils'); +import becca = require('../becca.js'); +import AbstractBeccaEntity = require('./abstract_becca_entity.js'); +import sql = require('../../services/sql'); +import BAttachment = require('./battachment.js'); +import { AttachmentRow, RevisionRow } from './rows'; + +interface ContentOpts { + /** will also save this BRevision entity */ + forceSave: boolean; +} + +interface GetByIdOpts { + includeContentLength?: boolean; +} /** * Revision represents a snapshot of note's title and content at some point in the past. @@ -20,40 +30,39 @@ class BRevision extends AbstractBeccaEntity { static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; } - constructor(row, titleDecrypted = false) { + revisionId: string; + noteId: string; + type: string; + mime: string; + isProtected: boolean; + title: string; + blobId: string; + dateLastEdited: string; + dateCreated: string; + utcDateLastEdited: string; + utcDateCreated: string; + contentLength?: number; + + constructor(row: RevisionRow, titleDecrypted = false) { super(); - /** @type {string} */ this.revisionId = row.revisionId; - /** @type {string} */ this.noteId = row.noteId; - /** @type {string} */ this.type = row.type; - /** @type {string} */ this.mime = row.mime; - /** @type {boolean} */ this.isProtected = !!row.isProtected; - /** @type {string} */ this.title = row.title; - /** @type {string} */ this.blobId = row.blobId; - /** @type {string} */ this.dateLastEdited = row.dateLastEdited; - /** @type {string} */ this.dateCreated = row.dateCreated; - /** @type {string} */ this.utcDateLastEdited = row.utcDateLastEdited; - /** @type {string} */ this.utcDateCreated = row.utcDateCreated; - /** @type {string} */ this.utcDateModified = row.utcDateModified; - /** @type {int} */ this.contentLength = row.contentLength; if (this.isProtected && !titleDecrypted) { - this.title = protectedSessionService.isProtectedSessionAvailable() - ? protectedSessionService.decryptString(this.title) - : "[protected]"; + const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null; + this.title = decryptedTitle || "[protected]"; } } @@ -80,16 +89,13 @@ class BRevision extends AbstractBeccaEntity { * * This is the same approach as is used for Note's content. */ - - /** @returns {string|Buffer} */ - getContent() { + getContent(): string | Buffer { return this._getContent(); } /** - * @returns {*} * @throws Error in case of invalid JSON */ - getJsonContent() { + getJsonContent(): {} | null { const content = this.getContent(); if (!content || !content.trim()) { @@ -99,8 +105,8 @@ class BRevision extends AbstractBeccaEntity { return JSON.parse(content); } - /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */ - getJsonContentSafely() { + /** @returns valid object or null if the content cannot be parsed as JSON */ + getJsonContentSafely(): {} | null { try { return this.getJsonContent(); } @@ -109,18 +115,12 @@ class BRevision extends AbstractBeccaEntity { } } - /** - * @param content - * @param {object} [opts] - * @param {object} [opts.forceSave=false] - will also save this BRevision entity - */ - setContent(content, opts) { + setContent(content: any, opts: ContentOpts) { this._setContent(content, opts); } - /** @returns {BAttachment[]} */ - getAttachments() { - return sql.getRows(` + getAttachments(): BAttachment[] { + return sql.getRows(` SELECT attachments.* FROM attachments WHERE ownerId = ? @@ -128,8 +128,7 @@ class BRevision extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment|null} */ - getAttachmentById(attachmentId, opts = {}) { + getAttachmentById(attachmentId: String, opts: GetByIdOpts = {}): BAttachment | null { opts.includeContentLength = !!opts.includeContentLength; const query = opts.includeContentLength @@ -139,13 +138,12 @@ class BRevision extends AbstractBeccaEntity { WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0` : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`; - return sql.getRows(query, [this.revisionId, attachmentId]) + return sql.getRows(query, [this.revisionId, attachmentId]) .map(row => new BAttachment(row))[0]; } - /** @returns {BAttachment[]} */ - getAttachmentsByRole(role) { - return sql.getRows(` + getAttachmentsByRole(role: string): BAttachment[] { + return sql.getRows(` SELECT attachments.* FROM attachments WHERE ownerId = ? @@ -155,8 +153,7 @@ class BRevision extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment} */ - getAttachmentByTitle(title) { + getAttachmentByTitle(title: string): BAttachment { // cannot use SQL to filter by title since it can be encrypted return this.getAttachments().filter(attachment => attachment.title === title)[0]; } @@ -174,7 +171,7 @@ class BRevision extends AbstractBeccaEntity { type: this.type, mime: this.mime, isProtected: this.isProtected, - title: this.title, + title: this.title || undefined, blobId: this.blobId, dateLastEdited: this.dateLastEdited, dateCreated: this.dateCreated, @@ -193,7 +190,7 @@ class BRevision extends AbstractBeccaEntity { if (pojo.isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { - pojo.title = protectedSessionService.encrypt(this.title); + pojo.title = protectedSessionService.encrypt(this.title) || undefined; } else { // updating protected note outside of protected session means we will keep original ciphertexts diff --git a/src/becca/entities/rows.ts b/src/becca/entities/rows.ts new file mode 100644 index 000000000..5793ee7ab --- /dev/null +++ b/src/becca/entities/rows.ts @@ -0,0 +1,36 @@ +export interface AttachmentRow { + attachmentId?: string; + ownerId: string; + role: string; + mime: string; + title?: string; + position?: number; + blobId: string; + isProtected?: boolean; + dateModified?: string; + utcDateModified?: string; + utcDateScheduledForErasureSince?: string; + contentLength?: number; +} + +export interface RevisionRow { + revisionId: string; + noteId: string; + type: string; + mime: string; + isProtected: boolean; + title: string; + blobId: string; + dateLastEdited: string; + dateCreated: string; + utcDateLastEdited: string; + utcDateCreated: string; + utcDateModified: string; + contentLength?: number; +} + +export interface RecentNoteRow { + noteId: string; + notePath: string; + utcDateCreated?: string; +} diff --git a/src/services/search/note_set.js b/src/services/search/note_set.ts similarity index 97% rename from src/services/search/note_set.js rename to src/services/search/note_set.ts index 82c236cf6..3ce84b469 100644 --- a/src/services/search/note_set.js +++ b/src/services/search/note_set.ts @@ -59,4 +59,4 @@ class NoteSet { } } -module.exports = NoteSet; +export = NoteSet;