mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
core: add support for monographs sync
This commit is contained in:
@@ -54,6 +54,29 @@ function PublishView(props: PublishViewProps) {
|
|||||||
const publishNote = useStore((store) => store.publish);
|
const publishNote = useStore((store) => store.publish);
|
||||||
const unpublishNote = useStore((store) => store.unpublish);
|
const unpublishNote = useStore((store) => store.unpublish);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!publishId) return;
|
||||||
|
(async () => {
|
||||||
|
const monographId = db.monographs.monograph(note.id);
|
||||||
|
if (monographId) {
|
||||||
|
const monograph = await db.monographs.get(monographId);
|
||||||
|
if (!monograph) return;
|
||||||
|
setPublishId(monographId);
|
||||||
|
setIsPasswordProtected(!!monograph.password);
|
||||||
|
setSelfDestruct(!!monograph.selfDestruct);
|
||||||
|
|
||||||
|
if (monograph.password) {
|
||||||
|
const password = await db.monographs.decryptPassword(
|
||||||
|
monograph.password
|
||||||
|
);
|
||||||
|
if (passwordInput.current) {
|
||||||
|
passwordInput.current.value = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [publishId, isPublishing]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fileDownloadedEvent = EV.subscribe(
|
const fileDownloadedEvent = EV.subscribe(
|
||||||
EVENTS.fileDownloaded,
|
EVENTS.fileDownloaded,
|
||||||
@@ -110,7 +133,13 @@ function PublishView(props: PublishViewProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{publishId ? (
|
{publishId ? (
|
||||||
<Flex mt={1} sx={{ flexDirection: "column", overflow: "hidden" }}>
|
<Flex
|
||||||
|
mt={1}
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
variant="body"
|
variant="body"
|
||||||
sx={{ fontWeight: "bold", color: "paragraph" }}
|
sx={{ fontWeight: "bold", color: "paragraph" }}
|
||||||
@@ -154,7 +183,12 @@ function PublishView(props: PublishViewProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<Text variant="body" sx={{ color: "paragraph" }}>
|
<Text
|
||||||
|
variant="body"
|
||||||
|
sx={{
|
||||||
|
color: "paragraph"
|
||||||
|
}}
|
||||||
|
>
|
||||||
{strings.monographDesc()}
|
{strings.monographDesc()}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import Migrations from "./migrations.js";
|
|||||||
import UserManager from "./user-manager.js";
|
import UserManager from "./user-manager.js";
|
||||||
import http from "../utils/http.js";
|
import http from "../utils/http.js";
|
||||||
import { Monographs } from "./monographs.js";
|
import { Monographs } from "./monographs.js";
|
||||||
|
import { Monographs as MonographsCollection } from "../collections/monographs.js";
|
||||||
import { Offers } from "./offers.js";
|
import { Offers } from "./offers.js";
|
||||||
import { Attachments } from "../collections/attachments.js";
|
import { Attachments } from "../collections/attachments.js";
|
||||||
import { Debug } from "./debug.js";
|
import { Debug } from "./debug.js";
|
||||||
@@ -203,6 +204,7 @@ class Database {
|
|||||||
trash = new Trash(this);
|
trash = new Trash(this);
|
||||||
sanitizer = new Sanitizer(this.sql);
|
sanitizer = new Sanitizer(this.sql);
|
||||||
|
|
||||||
|
monographsCollection = new MonographsCollection(this);
|
||||||
notebooks = new Notebooks(this);
|
notebooks = new Notebooks(this);
|
||||||
tags = new Tags(this);
|
tags = new Tags(this);
|
||||||
colors = new Colors(this);
|
colors = new Colors(this);
|
||||||
@@ -329,6 +331,7 @@ class Database {
|
|||||||
await this.relations.init();
|
await this.relations.init();
|
||||||
await this.notes.init();
|
await this.notes.init();
|
||||||
await this.vaults.init();
|
await this.vaults.init();
|
||||||
|
await this.monographsCollection.init();
|
||||||
|
|
||||||
await this.trash.init();
|
await this.trash.init();
|
||||||
|
|
||||||
@@ -407,6 +410,9 @@ class Database {
|
|||||||
EV.publish(EVENTS.userEmailConfirmed);
|
EV.publish(EVENTS.userEmailConfirmed);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "triggerSync": {
|
||||||
|
await this.sync({ type: "fetch" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("SSE: Unsupported message. Message = ", event.data);
|
console.log("SSE: Unsupported message. Message = ", event.data);
|
||||||
|
|||||||
@@ -20,24 +20,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import http from "../utils/http.js";
|
import http from "../utils/http.js";
|
||||||
import Constants from "../utils/constants.js";
|
import Constants from "../utils/constants.js";
|
||||||
import Database from "./index.js";
|
import Database from "./index.js";
|
||||||
import { Note, isDeleted } from "../types.js";
|
import { Monograph, Note, isDeleted } from "../types.js";
|
||||||
import { Cipher } from "@notesnook/crypto";
|
import { Cipher } from "@notesnook/crypto";
|
||||||
import { isFalse } from "../database/index.js";
|
import { isFalse } from "../database/index.js";
|
||||||
import { logger } from "../logger.js";
|
|
||||||
|
|
||||||
type BaseMonograph = {
|
type MonographApiRequestBase = Omit<
|
||||||
id: string;
|
Monograph,
|
||||||
title: string;
|
"type" | "dateModified" | "dateCreated" | "datePublished"
|
||||||
userId: string;
|
>;
|
||||||
selfDestruct: boolean;
|
type UnencryptedMonograph = MonographApiRequestBase & {
|
||||||
};
|
|
||||||
type UnencryptedMonograph = BaseMonograph & {
|
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
type EncryptedMonograph = BaseMonograph & {
|
type EncryptedMonograph = MonographApiRequestBase & {
|
||||||
encryptedContent: Cipher<"base64">;
|
encryptedContent: Cipher<"base64">;
|
||||||
};
|
};
|
||||||
type Monograph = UnencryptedMonograph | EncryptedMonograph;
|
type MonographApiRequest = (UnencryptedMonograph | EncryptedMonograph) & {
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PublishOptions = { password?: string; selfDestruct?: boolean };
|
export type PublishOptions = { password?: string; selfDestruct?: boolean };
|
||||||
export class Monographs {
|
export class Monographs {
|
||||||
@@ -46,24 +45,12 @@ export class Monographs {
|
|||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
this.monographs = [];
|
this.monographs = [];
|
||||||
await this.db.kv().write("monographs", this.monographs);
|
await this.db.monographsCollection.collection.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async refresh() {
|
async refresh() {
|
||||||
try {
|
const ids = await this.db.monographsCollection.all.ids();
|
||||||
const user = await this.db.user.getUser();
|
this.monographs = ids;
|
||||||
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.kv().write("monographs", monographs);
|
|
||||||
if (monographs) this.monographs = monographs;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(e, "Error while refreshing monographs.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,13 +96,19 @@ export class Monographs {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const monograph: Monograph = {
|
const monographPasswordsKey = await this.db.user.getMonographPasswordsKey();
|
||||||
|
const monograph: MonographApiRequest = {
|
||||||
id: noteId,
|
id: noteId,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
selfDestruct: opts.selfDestruct || false,
|
selfDestruct: opts.selfDestruct || false,
|
||||||
...(opts.password
|
...(opts.password
|
||||||
? {
|
? {
|
||||||
|
password: monographPasswordsKey
|
||||||
|
? await this.db
|
||||||
|
.storage()
|
||||||
|
.encrypt(monographPasswordsKey, opts.password)
|
||||||
|
: undefined,
|
||||||
encryptedContent: await this.db
|
encryptedContent: await this.db
|
||||||
.storage()
|
.storage()
|
||||||
.encrypt(
|
.encrypt(
|
||||||
@@ -124,6 +117,7 @@ export class Monographs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
password: undefined,
|
||||||
content: JSON.stringify({
|
content: JSON.stringify({
|
||||||
type: content.type,
|
type: content.type,
|
||||||
data: content.data
|
data: content.data
|
||||||
@@ -132,14 +126,21 @@ export class Monographs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const method = update ? http.patch.json : http.post.json;
|
const method = update ? http.patch.json : http.post.json;
|
||||||
|
const deviceId = await this.db.kv().read("deviceId");
|
||||||
const { id } = await method(
|
const { id, datePublished } = await method(
|
||||||
`${Constants.API_HOST}/monographs`,
|
`${Constants.API_HOST}/monographs?deviceId=${deviceId}`,
|
||||||
monograph,
|
monograph,
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
this.monographs.push(id);
|
this.monographs.push(id);
|
||||||
|
await this.db.monographsCollection.add({
|
||||||
|
id,
|
||||||
|
title: monograph.title,
|
||||||
|
selfDestruct: monograph.selfDestruct,
|
||||||
|
datePublished: datePublished,
|
||||||
|
password: monograph.password
|
||||||
|
});
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,9 +157,14 @@ export class Monographs {
|
|||||||
if (!this.isPublished(noteId))
|
if (!this.isPublished(noteId))
|
||||||
throw new Error("This note is not published.");
|
throw new Error("This note is not published.");
|
||||||
|
|
||||||
await http.delete(`${Constants.API_HOST}/monographs/${noteId}`, token);
|
const deviceId = await this.db.kv().read("deviceId");
|
||||||
|
await http.delete(
|
||||||
|
`${Constants.API_HOST}/monographs/${noteId}?deviceId=${deviceId}`,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
this.monographs.splice(this.monographs.indexOf(noteId), 1);
|
this.monographs.splice(this.monographs.indexOf(noteId), 1);
|
||||||
|
await this.db.monographsCollection.collection.softDelete([noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get all() {
|
get all() {
|
||||||
@@ -173,6 +179,12 @@ export class Monographs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(monographId: string) {
|
get(monographId: string) {
|
||||||
return http.get(`${Constants.API_HOST}/monographs/${monographId}`);
|
return this.db.monographsCollection.collection.get(monographId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async decryptPassword(password: Cipher<"base64">) {
|
||||||
|
const monographPasswordsKey = await this.db.user.getMonographPasswordsKey();
|
||||||
|
if (!monographPasswordsKey) return "";
|
||||||
|
return this.db.storage().decrypt(monographPasswordsKey, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
isTrashItem,
|
isTrashItem,
|
||||||
Item,
|
Item,
|
||||||
MaybeDeletedItem,
|
MaybeDeletedItem,
|
||||||
|
Monograph,
|
||||||
Note,
|
Note,
|
||||||
Notebook
|
Notebook
|
||||||
} from "../../types.js";
|
} from "../../types.js";
|
||||||
@@ -53,6 +54,7 @@ import {
|
|||||||
import { DownloadableFile } from "../../database/fs.js";
|
import { DownloadableFile } from "../../database/fs.js";
|
||||||
import { SyncDevices } from "./devices.js";
|
import { SyncDevices } from "./devices.js";
|
||||||
import { DefaultColors } from "../../collections/colors.js";
|
import { DefaultColors } from "../../collections/colors.js";
|
||||||
|
import { Monographs } from "../monographs.js";
|
||||||
|
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
/** Log level for very low severity diagnostic messages. */
|
/** Log level for very low severity diagnostic messages. */
|
||||||
@@ -463,6 +465,19 @@ class Sync {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.connection.on("SendMonographs", async (monographs) => {
|
||||||
|
if (this.connection?.state !== HubConnectionState.Connected) return false;
|
||||||
|
|
||||||
|
this.db.monographsCollection.collection.put(
|
||||||
|
monographs.map((m: Monograph) => ({
|
||||||
|
...m,
|
||||||
|
type: "monograph"
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getKey() {
|
private async getKey() {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const ENDPOINTS = {
|
|||||||
class UserManager {
|
class UserManager {
|
||||||
private tokenManager: TokenManager;
|
private tokenManager: TokenManager;
|
||||||
private cachedAttachmentKey?: SerializedKey;
|
private cachedAttachmentKey?: SerializedKey;
|
||||||
|
private cachedMonographPasswordsKey?: SerializedKey;
|
||||||
constructor(private readonly db: Database) {
|
constructor(private readonly db: Database) {
|
||||||
this.tokenManager = new TokenManager(this.db.kv);
|
this.tokenManager = new TokenManager(this.db.kv);
|
||||||
|
|
||||||
@@ -459,6 +460,51 @@ class UserManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMonographPasswordsKey() {
|
||||||
|
if (this.cachedMonographPasswordsKey) {
|
||||||
|
return this.cachedMonographPasswordsKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let user = await this.getUser();
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
if (!user.monographPasswordsKey) {
|
||||||
|
const token = await this.tokenManager.getAccessToken();
|
||||||
|
user = await http.get(`${constants.API_HOST}${ENDPOINTS.user}`, token);
|
||||||
|
}
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const userEncryptionKey = await this.getEncryptionKey();
|
||||||
|
if (!userEncryptionKey) return;
|
||||||
|
|
||||||
|
if (!user.monographPasswordsKey) {
|
||||||
|
const key = await this.db.crypto().generateRandomKey();
|
||||||
|
user.monographPasswordsKey = await this.db
|
||||||
|
.storage()
|
||||||
|
.encrypt(userEncryptionKey, JSON.stringify(key));
|
||||||
|
|
||||||
|
await this.updateUser({
|
||||||
|
monographPasswordsKey: user.monographPasswordsKey
|
||||||
|
});
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plainData = await this.db
|
||||||
|
.storage()
|
||||||
|
.decrypt(userEncryptionKey, user.monographPasswordsKey);
|
||||||
|
if (!plainData) return;
|
||||||
|
this.cachedMonographPasswordsKey = JSON.parse(plainData) as SerializedKey;
|
||||||
|
return this.cachedMonographPasswordsKey;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e, "Could not get monographs encryption key.");
|
||||||
|
if (e instanceof Error)
|
||||||
|
throw new Error(
|
||||||
|
`Could not get monographs encryption key. Error: ${e.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendVerificationEmail(newEmail?: string) {
|
async sendVerificationEmail(newEmail?: string) {
|
||||||
const token = await this.tokenManager.getAccessToken();
|
const token = await this.tokenManager.getAccessToken();
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
@@ -533,7 +579,6 @@ class UserManager {
|
|||||||
|
|
||||||
if (!new_password) throw new Error("New password is required.");
|
if (!new_password) throw new Error("New password is required.");
|
||||||
|
|
||||||
const attachmentsKey = await this.getAttachmentsKey();
|
|
||||||
data.encryptionKey = data.encryptionKey || (await this.getEncryptionKey());
|
data.encryptionKey = data.encryptionKey || (await this.getEncryptionKey());
|
||||||
|
|
||||||
await this.clearSessions();
|
await this.clearSessions();
|
||||||
@@ -554,13 +599,26 @@ class UserManager {
|
|||||||
|
|
||||||
await this.db.sync({ type: "send", force: true });
|
await this.db.sync({ type: "send", force: true });
|
||||||
|
|
||||||
if (attachmentsKey) {
|
|
||||||
const userEncryptionKey = await this.getEncryptionKey();
|
const userEncryptionKey = await this.getEncryptionKey();
|
||||||
if (!userEncryptionKey) return;
|
if (userEncryptionKey) {
|
||||||
|
const updateUserPayload: Partial<User> = {};
|
||||||
|
const attachmentsKey = await this.getAttachmentsKey();
|
||||||
|
if (attachmentsKey) {
|
||||||
user.attachmentsKey = await this.db
|
user.attachmentsKey = await this.db
|
||||||
.storage()
|
.storage()
|
||||||
.encrypt(userEncryptionKey, JSON.stringify(attachmentsKey));
|
.encrypt(userEncryptionKey, JSON.stringify(attachmentsKey));
|
||||||
await this.updateUser({ attachmentsKey: user.attachmentsKey });
|
updateUserPayload.attachmentsKey = user.attachmentsKey;
|
||||||
|
}
|
||||||
|
const monographPasswordsKey = await this.getMonographPasswordsKey();
|
||||||
|
if (monographPasswordsKey) {
|
||||||
|
user.monographPasswordsKey = await this.db
|
||||||
|
.storage()
|
||||||
|
.encrypt(userEncryptionKey, JSON.stringify(monographPasswordsKey));
|
||||||
|
updateUserPayload.monographPasswordsKey = user.monographPasswordsKey;
|
||||||
|
}
|
||||||
|
if (Object.keys(updateUserPayload).length > 0) {
|
||||||
|
await this.updateUser(updateUserPayload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_password)
|
if (new_password)
|
||||||
|
|||||||
69
packages/core/src/collections/monographs.ts
Normal file
69
packages/core/src/collections/monographs.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Database from "../api/index.js";
|
||||||
|
import { ItemReference, Monograph, Vault } from "../types.js";
|
||||||
|
import { ICollection } from "./collection.js";
|
||||||
|
import { SQLCollection } from "../database/sql-collection.js";
|
||||||
|
import { getId } from "../utils/id.js";
|
||||||
|
import { isFalse } from "../database/index.js";
|
||||||
|
import { sql } from "@streetwriters/kysely";
|
||||||
|
|
||||||
|
export class Monographs implements ICollection {
|
||||||
|
name = "monographs";
|
||||||
|
readonly collection: SQLCollection<"monographs", Monograph>;
|
||||||
|
constructor(private readonly db: Database) {
|
||||||
|
this.collection = new SQLCollection(
|
||||||
|
db.sql,
|
||||||
|
db.transaction,
|
||||||
|
"monographs",
|
||||||
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.collection.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
get all() {
|
||||||
|
return this.collection.createFilter<Monograph>(
|
||||||
|
(qb) => qb.where(isFalse("deleted")),
|
||||||
|
this.db.options?.batchSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(monograph: Partial<Monograph>) {
|
||||||
|
const id = monograph.id || getId();
|
||||||
|
const oldMonograph = await this.collection.get(id);
|
||||||
|
const merged: Partial<Monograph> = {
|
||||||
|
...oldMonograph,
|
||||||
|
...monograph
|
||||||
|
};
|
||||||
|
|
||||||
|
this.collection.upsert({
|
||||||
|
id,
|
||||||
|
title: merged.title,
|
||||||
|
datePublished: merged.datePublished,
|
||||||
|
selfDestruct: merged.selfDestruct,
|
||||||
|
password: merged.password,
|
||||||
|
type: "monograph"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
ItemReferences,
|
ItemReferences,
|
||||||
ItemType,
|
ItemType,
|
||||||
MaybeDeletedItem,
|
MaybeDeletedItem,
|
||||||
|
Monograph,
|
||||||
Note,
|
Note,
|
||||||
Notebook,
|
Notebook,
|
||||||
Relation,
|
Relation,
|
||||||
@@ -90,6 +91,7 @@ export interface DatabaseSchema {
|
|||||||
sessioncontent: SQLiteItem<SessionContentItem>;
|
sessioncontent: SQLiteItem<SessionContentItem>;
|
||||||
shortcuts: SQLiteItem<Shortcut>;
|
shortcuts: SQLiteItem<Shortcut>;
|
||||||
vaults: SQLiteItem<Vault>;
|
vaults: SQLiteItem<Vault>;
|
||||||
|
monographs: SQLiteItem<Monograph>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RawDatabaseSchema = DatabaseSchema & {
|
export type RawDatabaseSchema = DatabaseSchema & {
|
||||||
@@ -233,7 +235,8 @@ const BooleanProperties: Set<BooleanFields> = new Set([
|
|||||||
"remote",
|
"remote",
|
||||||
"synced",
|
"synced",
|
||||||
"isGeneratedTitle",
|
"isGeneratedTitle",
|
||||||
"archived"
|
"archived",
|
||||||
|
"selfDestruct"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const DataMappers: Partial<Record<ItemType, (row: any) => void>> = {
|
const DataMappers: Partial<Record<ItemType, (row: any) => void>> = {
|
||||||
@@ -266,6 +269,9 @@ const DataMappers: Partial<Record<ItemType, (row: any) => void>> = {
|
|||||||
},
|
},
|
||||||
vault: (row) => {
|
vault: (row) => {
|
||||||
if (row.key) row.key = JSON.parse(row.key);
|
if (row.key) row.key = JSON.parse(row.key);
|
||||||
|
},
|
||||||
|
monograph: (row) => {
|
||||||
|
if (row.password) row.password = JSON.parse(row.password);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -392,6 +392,18 @@ export class NNMigrationProvider implements MigrationProvider {
|
|||||||
async up(db) {
|
async up(db) {
|
||||||
await runFTSTablesMigrations(db);
|
await runFTSTablesMigrations(db);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"a-2025-07-30": {
|
||||||
|
async up(db) {
|
||||||
|
await db.schema
|
||||||
|
.createTable("monographs")
|
||||||
|
.$call(addBaseColumns)
|
||||||
|
.addColumn("datePublished", "integer")
|
||||||
|
.addColumn("title", "text", COLLATE_NOCASE)
|
||||||
|
.addColumn("selfDestruct", "boolean")
|
||||||
|
.addColumn("password", "text")
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -701,7 +701,8 @@ const VALID_SORT_OPTIONS: Record<
|
|||||||
sessioncontent: [],
|
sessioncontent: [],
|
||||||
settings: [],
|
settings: [],
|
||||||
shortcuts: [],
|
shortcuts: [],
|
||||||
vaults: []
|
vaults: [],
|
||||||
|
monographs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
function sanitizeSortOptions(type: keyof DatabaseSchema, options: SortOptions) {
|
function sanitizeSortOptions(type: keyof DatabaseSchema, options: SortOptions) {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export type Collections = {
|
|||||||
sessioncontent: "sessioncontent";
|
sessioncontent: "sessioncontent";
|
||||||
settingsv2: "settingitem";
|
settingsv2: "settingitem";
|
||||||
vaults: "vault";
|
vaults: "vault";
|
||||||
|
monographs: "monograph";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated only kept here for migration purposes
|
* @deprecated only kept here for migration purposes
|
||||||
@@ -110,6 +111,7 @@ export type GroupableItem = ValueOf<
|
|||||||
| "settings"
|
| "settings"
|
||||||
| "settingitem"
|
| "settingitem"
|
||||||
| "vault"
|
| "vault"
|
||||||
|
| "monograph"
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -131,6 +133,7 @@ export type ItemMap = {
|
|||||||
settingitem: SettingItem;
|
settingitem: SettingItem;
|
||||||
vault: Vault;
|
vault: Vault;
|
||||||
searchResult: HighlightedResult;
|
searchResult: HighlightedResult;
|
||||||
|
monograph: Monograph;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated only kept here for migration purposes
|
* @deprecated only kept here for migration purposes
|
||||||
@@ -490,6 +493,13 @@ export interface Vault extends BaseItem<"vault"> {
|
|||||||
key: Cipher<"base64">;
|
key: Cipher<"base64">;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Monograph extends BaseItem<"monograph"> {
|
||||||
|
title: string;
|
||||||
|
datePublished: number;
|
||||||
|
selfDestruct: boolean;
|
||||||
|
password?: Cipher<"base64">;
|
||||||
|
}
|
||||||
|
|
||||||
export type Match = {
|
export type Match = {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
match: string;
|
match: string;
|
||||||
@@ -546,6 +556,7 @@ export type User = {
|
|||||||
isEmailConfirmed: boolean;
|
isEmailConfirmed: boolean;
|
||||||
salt: string;
|
salt: string;
|
||||||
attachmentsKey?: Cipher<"base64">;
|
attachmentsKey?: Cipher<"base64">;
|
||||||
|
monographPasswordsKey?: Cipher<"base64">;
|
||||||
marketingConsent?: boolean;
|
marketingConsent?: boolean;
|
||||||
mfa: {
|
mfa: {
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user