From f9ba8ca87d7a9e6e172602b12d116b72a41c2761 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 17 Feb 2024 10:02:50 +0200 Subject: [PATCH] server-ts: Fix errors in abstract_becca_entity --- src/becca/entities/abstract_becca_entity.ts | 100 +++++++++++++------- src/becca/entities/battribute.ts | 1 - src/becca/entities/betapi_token.ts | 2 - src/becca/entities/boption.ts | 1 - src/becca/entities/brevision.ts | 2 - src/services/blob-interface.ts | 2 +- src/services/entity_changes_interface.ts | 2 +- src/services/utils.ts | 2 +- 8 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/becca/entities/abstract_becca_entity.ts b/src/becca/entities/abstract_becca_entity.ts index 0f9769fb6..88d3e753e 100644 --- a/src/becca/entities/abstract_becca_entity.ts +++ b/src/becca/entities/abstract_becca_entity.ts @@ -13,16 +13,35 @@ import Becca = require('../becca-interface'); let becca: Becca | null = null; +interface ContentOpts { + forceSave?: boolean; + forceFrontendReload?: boolean; +} + +interface ConstructorData> { + primaryKeyName: string; + entityName: string; + hashedProperties: (keyof T)[]; +} + /** * Base class for all backend entities. */ -abstract class AbstractBeccaEntity { +abstract class AbstractBeccaEntity> { + protected utcDateCreated?: string; protected utcDateModified?: string; + protected dateModified?: string; + protected isProtected?: boolean; + protected isDeleted?: boolean; + protected isSynced?: boolean; + + protected blobId?: string; protected beforeSaving() { - if (!this[this.constructor.primaryKeyName]) { - this[this.constructor.primaryKeyName] = utils.newEntityId(); + const constructorData = (this.constructor as unknown as ConstructorData); + if (!(this as any)[constructorData.primaryKeyName]) { + (this as any)[constructorData.primaryKeyName] = utils.newEntityId(); } } @@ -39,21 +58,23 @@ abstract class AbstractBeccaEntity { } protected putEntityChange(isDeleted: boolean) { + const constructorData = (this.constructor as unknown as ConstructorData); entityChangesService.putEntityChange({ - entityName: this.constructor.entityName, - entityId: this[this.constructor.primaryKeyName], + entityName: constructorData.entityName, + entityId: (this as any)[constructorData.primaryKeyName], hash: this.generateHash(isDeleted), isErased: false, utcDateChanged: this.getUtcDateChanged(), - isSynced: this.constructor.entityName !== 'options' || !!this.isSynced + isSynced: constructorData.entityName !== 'options' || !!this.isSynced }); } protected generateHash(isDeleted: boolean): string { + const constructorData = (this.constructor as unknown as ConstructorData); let contentToHash = ""; - for (const propertyName of this.constructor.hashedProperties) { - contentToHash += `|${this[propertyName]}`; + for (const propertyName of constructorData.hashedProperties) { + contentToHash += `|${(this as any)[propertyName]}`; } if (isDeleted) { @@ -67,18 +88,21 @@ abstract class AbstractBeccaEntity { return this.getPojo(); } + abstract hasStringContent(): boolean; + abstract getPojo(): {}; /** * Saves entity - executes SQL, but doesn't commit the transaction on its own */ - save(opts = {}): this { - const entityName = this.constructor.entityName; - const primaryKeyName = this.constructor.primaryKeyName; + save(): this { + const constructorData = (this.constructor as unknown as ConstructorData); + const entityName = constructorData.entityName; + const primaryKeyName = constructorData.primaryKeyName; - const isNewEntity = !this[primaryKeyName]; + const isNewEntity = !(this as any)[primaryKeyName]; - this.beforeSaving(opts); + this.beforeSaving(); const pojo = this.getPojoToSave(); @@ -108,13 +132,14 @@ abstract class AbstractBeccaEntity { return this; } - protected _setContent(content, opts = {}) { + protected _setContent(content: string | Buffer, opts: ContentOpts = {}) { // client code asks to save entity even if blobId didn't change (something else was changed) opts.forceSave = !!opts.forceSave; opts.forceFrontendReload = !!opts.forceFrontendReload; if (content === null || content === undefined) { - throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`); + const constructorData = (this.constructor as unknown as ConstructorData); + throw new Error(`Cannot set null content to ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}'`); } if (this.hasStringContent()) { @@ -123,32 +148,36 @@ abstract class AbstractBeccaEntity { content = Buffer.isBuffer(content) ? content : Buffer.from(content); } - const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content); + const unencryptedContentForHashCalculation = this.getUnencryptedContentForHashCalculation(content); if (this.isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { - content = protectedSessionService.encrypt(content); + const encryptedContent = protectedSessionService.encrypt(content); + if (!encryptedContent) { + throw new Error(`Unable to encrypt the content of the entity.`); + } + content = encryptedContent; } else { throw new Error(`Cannot update content of blob since protected session is not available.`); } } sql.transactional(() => { - const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts); + const newBlobId = this.saveBlob(content, unencryptedContentForHashCalculation, opts); const oldBlobId = this.blobId; if (newBlobId !== oldBlobId || opts.forceSave) { this.blobId = newBlobId; this.save(); - if (newBlobId !== oldBlobId) { - this.#deleteBlobIfNotUsed(oldBlobId); + if (oldBlobId && newBlobId !== oldBlobId) { + this.deleteBlobIfNotUsed(oldBlobId); } } }); } - #deleteBlobIfNotUsed(oldBlobId) { + private deleteBlobIfNotUsed(oldBlobId: string) { if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) { return; } @@ -167,7 +196,7 @@ abstract class AbstractBeccaEntity { sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]); } - #getUnencryptedContentForHashCalculation(unencryptedContent) { + private getUnencryptedContentForHashCalculation(unencryptedContent: Buffer | string) { if (this.isProtected) { // a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_"; @@ -179,7 +208,7 @@ abstract class AbstractBeccaEntity { } } - #saveBlob(content, unencryptedContentForHashCalculation, opts = {}) { + private saveBlob(content: string | Buffer, unencryptedContentForHashCalculation: string | Buffer, opts: ContentOpts = {}) { /* * We're using the unencrypted blob for the hash calculation, because otherwise the random IV would * cause every content blob to be unique which would balloon the database size (esp. with revisioning). @@ -226,14 +255,15 @@ abstract class AbstractBeccaEntity { return newBlobId; } - protected _getContent(): string | Buffer { - const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); + protected _getContent(): string | Buffer { + const row = sql.getRow<{ content: string | Buffer }>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); if (!row) { - throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`); + const constructorData = (this.constructor as unknown as ConstructorData); + throw new Error(`Cannot find content for ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}', blobId '${this.blobId}'`); } - return blobService.processContent(row.content, this.isProtected, this.hasStringContent()); + return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent()); } /** @@ -242,19 +272,20 @@ abstract class AbstractBeccaEntity { * This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. */ markAsDeleted(deleteId = null) { - const entityId = this[this.constructor.primaryKeyName]; - const entityName = this.constructor.entityName; + const constructorData = (this.constructor as unknown as ConstructorData); + const entityId = (this as any)[constructorData.primaryKeyName]; + const entityName = constructorData.entityName; this.utcDateModified = dateUtils.utcNowDateTime(); sql.execute(`UPDATE ${entityName} SET isDeleted = 1, deleteId = ?, utcDateModified = ? - WHERE ${this.constructor.primaryKeyName} = ?`, + WHERE ${constructorData.primaryKeyName} = ?`, [deleteId, this.utcDateModified, entityId]); if (this.dateModified) { this.dateModified = dateUtils.localNowDateTime(); - sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${this.constructor.primaryKeyName} = ?`, + sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${constructorData.primaryKeyName} = ?`, [this.dateModified, entityId]); } @@ -266,13 +297,14 @@ abstract class AbstractBeccaEntity { } markAsDeletedSimple() { - const entityId = this[this.constructor.primaryKeyName]; - const entityName = this.constructor.entityName; + const constructorData = (this.constructor as unknown as ConstructorData); + const entityId = (this as any)[constructorData.primaryKeyName]; + const entityName = constructorData.entityName; this.utcDateModified = dateUtils.utcNowDateTime(); sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ? - WHERE ${this.constructor.primaryKeyName} = ?`, + WHERE ${constructorData.primaryKeyName} = ?`, [this.utcDateModified, entityId]); log.info(`Marking ${entityName} ${entityId} as deleted`); diff --git a/src/becca/entities/battribute.ts b/src/becca/entities/battribute.ts index 71d575193..ceb3edf92 100644 --- a/src/becca/entities/battribute.ts +++ b/src/becca/entities/battribute.ts @@ -25,7 +25,6 @@ class BAttribute extends AbstractBeccaEntity { position!: number; value!: string; isInheritable!: boolean; - utcDateModified!: string; constructor(row: AttributeRow) { super(); diff --git a/src/becca/entities/betapi_token.ts b/src/becca/entities/betapi_token.ts index 4809dc265..8c0227482 100644 --- a/src/becca/entities/betapi_token.ts +++ b/src/becca/entities/betapi_token.ts @@ -24,8 +24,6 @@ class BEtapiToken extends AbstractBeccaEntity { etapiTokenId!: string; name!: string; tokenHash!: string; - utcDateCreated!: string; - utcDateModified!: string; isDeleted!: boolean; constructor(row: EtapiTokenRow) { diff --git a/src/becca/entities/boption.ts b/src/becca/entities/boption.ts index ebfd951d1..871e44157 100644 --- a/src/becca/entities/boption.ts +++ b/src/becca/entities/boption.ts @@ -15,7 +15,6 @@ class BOption extends AbstractBeccaEntity { name!: string; value!: string; isSynced!: boolean; - utcDateModified!: string; constructor(row: OptionRow) { super(); diff --git a/src/becca/entities/brevision.ts b/src/becca/entities/brevision.ts index fb590ddbb..df3447aed 100644 --- a/src/becca/entities/brevision.ts +++ b/src/becca/entities/brevision.ts @@ -21,8 +21,6 @@ interface GetByIdOpts { /** * Revision represents a snapshot of note's title and content at some point in the past. * It's used for seamless note versioning. - * - * @extends AbstractBeccaEntity */ class BRevision extends AbstractBeccaEntity { static get entityName() { return "revisions"; } diff --git a/src/services/blob-interface.ts b/src/services/blob-interface.ts index 5905e9d99..8bfcf1322 100644 --- a/src/services/blob-interface.ts +++ b/src/services/blob-interface.ts @@ -1,5 +1,5 @@ export interface Blob { blobId: string; - content: Buffer; + content: string | Buffer; utcDateModified: string; } \ No newline at end of file diff --git a/src/services/entity_changes_interface.ts b/src/services/entity_changes_interface.ts index 2252f27fb..f0a583e95 100644 --- a/src/services/entity_changes_interface.ts +++ b/src/services/entity_changes_interface.ts @@ -6,7 +6,7 @@ export interface EntityChange { entity?: any; positions?: Record; hash: string; - utcDateChanged: string; + utcDateChanged?: string; isSynced: boolean | 1 | 0; isErased: boolean | 1 | 0; componentId?: string | null; diff --git a/src/services/utils.ts b/src/services/utils.ts index 9a3397f85..f16465af8 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -24,7 +24,7 @@ function md5(content: crypto.BinaryLike) { return crypto.createHash('md5').update(content).digest('hex'); } -function hashedBlobId(content: string) { +function hashedBlobId(content: string | Buffer) { if (content === null || content === undefined) { content = ""; }