From 09a19e61d55069b548df8bb9eaed7f50b581e0be Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Mon, 18 Sep 2023 12:37:30 +0500 Subject: [PATCH] core: migrate monographs to typescript --- packages/core/__e2e__/monographs.test.js | 10 +- packages/core/src/api/index.ts | 8 +- packages/core/src/api/monographs.js | 165 --------------------- packages/core/src/api/monographs.ts | 177 +++++++++++++++++++++++ packages/core/src/api/sync/index.ts | 5 +- 5 files changed, 188 insertions(+), 177 deletions(-) delete mode 100644 packages/core/src/api/monographs.js create mode 100644 packages/core/src/api/monographs.ts diff --git a/packages/core/__e2e__/monographs.test.js b/packages/core/__e2e__/monographs.test.js index 0509e6b1d..d59373526 100644 --- a/packages/core/__e2e__/monographs.test.js +++ b/packages/core/__e2e__/monographs.test.js @@ -26,7 +26,7 @@ const TEST_TIMEOUT = 30 * 1000; afterAll(async () => { const db = await databaseTest(); await login(db); - await db.monographs.init(); + await db.monographs.refresh(); for (const id of db.monographs.monographs) { await db.monographs.unpublish(id); @@ -39,7 +39,7 @@ afterAll(async () => { // databaseTest().then(async (db) => { // await db.user.login(user.email, user.password, user.hashedPassword); -// await db.monographs.init(); +// await db.monographs.refresh(); // expect(db.monographs.all).toBeGreaterThanOrEqual(0); // })); @@ -49,7 +49,7 @@ test( () => noteTest().then(async ({ db, id }) => { await login(db); - await db.monographs.init(); + await db.monographs.refresh(); const monographId = await db.monographs.publish(id); @@ -70,7 +70,7 @@ test( () => noteTest().then(async ({ db, id }) => { await login(db); - await db.monographs.init(); + await db.monographs.refresh(); const monographId = await db.monographs.publish(id); let monograph = await db.monographs.get(monographId); @@ -93,7 +93,7 @@ test( () => noteTest().then(async ({ db, id }) => { await login(db); - await db.monographs.init(); + await db.monographs.refresh(); await db.monographs.publish(id); expect(db.monographs.all.find((m) => m.id === id)).toBeDefined(); diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 4f091d035..1fe02d383 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -37,7 +37,7 @@ import Migrations from "./migrations"; import Outbox from "./outbox"; import UserManager from "./user-manager"; import http from "../utils/http"; -import Monographs from "./monographs"; +import { Monographs } from "./monographs"; import { Offers } from "./offers"; import { Attachments } from "../collections/attachments"; import { Debug } from "./debug"; @@ -176,7 +176,7 @@ class Database { await this.fs().cancel(attachment.metadata.hash, "download"); }); EV.subscribe(EVENTS.userLoggedOut, async () => { - await this.monographs.deinit(); + await this.monographs.clear(); await this.fs().clear(); this.disconnectSSE(); }); @@ -211,7 +211,9 @@ class Database { await this.trash.init(); - this.monographs.init().catch(console.error); + // we must not wait on network requests that's why + // no await + this.monographs.refresh(); } disconnectSSE() { diff --git a/packages/core/src/api/monographs.js b/packages/core/src/api/monographs.js deleted file mode 100644 index 47fba028f..000000000 --- a/packages/core/src/api/monographs.js +++ /dev/null @@ -1,165 +0,0 @@ -/* -This file is part of the Notesnook project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -import http from "../utils/http"; -import Constants from "../utils/constants"; - -class Monographs { - /** - * - * @param {import("./index").default} db - */ - constructor(db) { - this._db = db; - this.monographs = undefined; - } - - async deinit() { - this.monographs = undefined; - await this._db.storage().write("monographs", this.monographs); - } - - async init() { - try { - const user = await this._db.user.getUser(); - const token = await this._db.tokenManager.getAccessToken(); - if (!user || !token || !user.isEmailConfirmed) return; - let monographs = await this._db.storage().read("monographs", true); - monographs = await http.get(`${Constants.API_HOST}/monographs`, token); - await this._db.storage().write("monographs", monographs); - - if (monographs) this.monographs = monographs; - } catch (e) { - console.error(e); - } - } - - /** - * Check if note is published. - * @param {string} noteId id of the note - * @returns {boolean} Whether note is published or not. - */ - isPublished(noteId) { - return this.monographs && this.monographs.indexOf(noteId) > -1; - } - - /** - * Get note published monograph id - * @param {string} noteId id of the note - * @returns Monograph Id - */ - monograph(noteId) { - if (!this.monographs) return; - - return this.monographs[this.monographs.indexOf(noteId)]; - } - - /** - * Publish a note as a monograph - * @param {string} noteId id of the note to publish - * @param {{password: string, selfDestruct: boolean}} opts Publish options - * @returns - */ - async publish(noteId, opts = { password: undefined, selfDestruct: false }) { - if (!this.monographs) await this.init(); - - let update = !!this.isPublished(noteId); - - const user = await this._db.user.getUser(); - const token = await this._db.tokenManager.getAccessToken(); - if (!user || !token) throw new Error("Please login to publish a note."); - - const note = this._db.notes.note(noteId); - if (!note) throw new Error("No such note found."); - - const content = await this._db.content.downloadMedia( - `monograph-${noteId}`, - await this._db.content.raw(note.data.contentId), - false - ); - if (!content) throw new Error("This note has no content."); - - const monograph = { - id: noteId, - title: note.title, - userId: user.id, - selfDestruct: opts.selfDestruct - }; - - if (opts.password) { - monograph.encryptedContent = await this._db - .storage() - .encrypt( - { password: opts.password }, - JSON.stringify({ type: content.type, data: content.data }) - ); - } else { - monograph.content = JSON.stringify({ - type: content.type, - data: content.data - }); - } - - const method = update ? http.patch.json : http.post.json; - - const { id } = await method( - `${Constants.API_HOST}/monographs`, - monograph, - token - ); - - this.monographs.push(id); - return id; - } - - /** - * Unpublish a note - * @param {string} noteId id of the note to unpublish - */ - async unpublish(noteId) { - if (!this.monographs) await this.init(); - - const user = await this._db.user.getUser(); - const token = await this._db.tokenManager.getAccessToken(); - if (!user || !token) throw new Error("Please login to publish a note."); - - // const note = this._db.notes.note(noteId); - // if (!note) throw new Error("No such note found."); - - if (!this.isPublished(noteId)) - throw new Error("This note is not published."); - - await http.delete(`${Constants.API_HOST}/monographs/${noteId}`, token); - - this.monographs.splice(this.monographs.indexOf(noteId), 1); - } - - get all() { - if (!this.monographs) return []; - - return this._db.notes.all.filter( - (note) => this.monographs.indexOf(note.id) > -1 - ); - } - - async get(monographId) { - return await http.get(`${Constants.API_HOST}/monographs/${monographId}`); - } -} -export default Monographs; diff --git a/packages/core/src/api/monographs.ts b/packages/core/src/api/monographs.ts new file mode 100644 index 000000000..d7727beb3 --- /dev/null +++ b/packages/core/src/api/monographs.ts @@ -0,0 +1,177 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import http from "../utils/http"; +import Constants from "../utils/constants"; +import Database from "."; +import { Note, isDeleted } from "../types"; +import { isUnencryptedContent } from "../collections/content"; +import { Cipher } from "@notesnook/crypto"; + +type BaseMonograph = { + id: string; + title: string; + userId: string; + selfDestruct: boolean; +}; +type UnencryptedMonograph = BaseMonograph & { + content: string; +}; +type EncryptedMonograph = BaseMonograph & { + encryptedContent: Cipher<"base64">; +}; +type Monograph = UnencryptedMonograph | EncryptedMonograph; + +export class Monographs { + monographs: string[] = []; + constructor(private readonly db: Database) {} + + async clear() { + this.monographs = []; + await this.db.storage().write("monographs", this.monographs); + } + + async refresh() { + try { + const user = await this.db.user.getUser(); + const token = await this.db.tokenManager.getAccessToken(); + if (!user || !token || !user.isEmailConfirmed) return; + + const monographs = await http.get( + `${Constants.API_HOST}/monographs`, + token + ); + await this.db.storage().write("monographs", monographs); + if (monographs) this.monographs = monographs; + } catch (e) { + console.error(e); + } + } + + /** + * Check if note is published. + */ + isPublished(noteId: string) { + return this.monographs && this.monographs.indexOf(noteId) > -1; + } + + /** + * Get note published monograph id + */ + monograph(noteId: string) { + return this.monographs[this.monographs.indexOf(noteId)]; + } + + /** + * Publish a note as a monograph + */ + async publish( + noteId: string, + opts: { password?: string; selfDestruct?: boolean } = {} + ) { + if (!this.monographs.length) await this.refresh(); + + const update = !!this.isPublished(noteId); + + const user = await this.db.user.getUser(); + const token = await this.db.tokenManager.getAccessToken(); + if (!user || !token) throw new Error("Please login to publish a note."); + + const note = this.db.notes.note(noteId); + if (!note) throw new Error("No such note found."); + if (!note.data.contentId) throw new Error("Cannot publish an empty note."); + + const contentItem = await this.db.content.raw(note.data.contentId); + + if (!contentItem || isDeleted(contentItem)) + throw new Error("Could not find content for this note."); + + if (!isUnencryptedContent(contentItem)) + throw new Error("Cannot published locked notes."); + + const content = await this.db.content.downloadMedia( + `monograph-${noteId}`, + contentItem, + false + ); + + const monograph: Monograph = { + id: noteId, + title: note.title, + userId: user.id, + selfDestruct: opts.selfDestruct || false, + ...(opts.password + ? { + encryptedContent: await this.db + .storage() + .encrypt( + { password: opts.password }, + JSON.stringify({ type: content.type, data: content.data }) + ) + } + : { + content: JSON.stringify({ + type: content.type, + data: content.data + }) + }) + }; + + const method = update ? http.patch.json : http.post.json; + + const { id } = await method( + `${Constants.API_HOST}/monographs`, + monograph, + token + ); + + this.monographs.push(id); + return id; + } + + /** + * Unpublish a note + */ + async unpublish(noteId: string) { + if (!this.monographs.length) await this.refresh(); + + const user = await this.db.user.getUser(); + const token = await this.db.tokenManager.getAccessToken(); + if (!user || !token) throw new Error("Please login to publish a note."); + + if (!this.isPublished(noteId)) + throw new Error("This note is not published."); + + await http.delete(`${Constants.API_HOST}/monographs/${noteId}`, token); + + this.monographs.splice(this.monographs.indexOf(noteId), 1); + } + + get all() { + if (!this.monographs.length) return []; + + return this.monographs + .map((noteId) => this.db.notes.note(noteId)?.data) + .filter(Boolean) as Note[]; + } + + get(monographId: string) { + return http.get(`${Constants.API_HOST}/monographs/${monographId}`); + } +} diff --git a/packages/core/src/api/sync/index.ts b/packages/core/src/api/sync/index.ts index 81e2d1041..fb969522d 100644 --- a/packages/core/src/api/sync/index.ts +++ b/packages/core/src/api/sync/index.ts @@ -320,7 +320,7 @@ class Sync { async stop(lastSynced: number) { // refresh monographs on sync completed - await this.db.monographs.init(); + await this.db.monographs.refresh(); this.logger.info("Stopping sync", { lastSynced }); const storedLastSynced = await this.db.lastSynced(); @@ -361,9 +361,6 @@ class Sync { false, lastSynced ); - - // refresh monographs on sync completed - await this.db.monographs.init(); } async processChunk(