mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
core: fix table x has no column y errors
This commit is contained in:
@@ -161,7 +161,7 @@ test("update note", () =>
|
|||||||
title: "I am a new title",
|
title: "I am a new title",
|
||||||
content: {
|
content: {
|
||||||
type: TEST_NOTE.content.type,
|
type: TEST_NOTE.content.type,
|
||||||
data: "<p><br/></p>"
|
data: '<p data-block-id="p1"><br/></p>'
|
||||||
},
|
},
|
||||||
pinned: true,
|
pinned: true,
|
||||||
favorite: true
|
favorite: true
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const notebookTest = (notebook = TEST_NOTEBOOK) =>
|
|||||||
const TEST_NOTE: { content: NoteContent<false> } = {
|
const TEST_NOTE: { content: NoteContent<false> } = {
|
||||||
content: {
|
content: {
|
||||||
type: "tiptap",
|
type: "tiptap",
|
||||||
data: `<p>Hello <span style="color:#f00">This is colorful</span></p>`
|
data: `<p data-block-id="p1">Hello <span style="color:#f00">This is colorful</span></p>`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import { CachedCollection } from "../database/cached-collection";
|
|||||||
import { Vaults } from "../collections/vaults";
|
import { Vaults } from "../collections/vaults";
|
||||||
import { KVStorage } from "../database/kv";
|
import { KVStorage } from "../database/kv";
|
||||||
import { QueueValue } from "../utils/queue-value";
|
import { QueueValue } from "../utils/queue-value";
|
||||||
|
import { Sanitizer } from "../database/sanitizer";
|
||||||
|
|
||||||
type EventSourceConstructor = new (
|
type EventSourceConstructor = new (
|
||||||
uri: string,
|
uri: string,
|
||||||
@@ -178,10 +179,10 @@ class Database {
|
|||||||
vault = new Vault(this);
|
vault = new Vault(this);
|
||||||
lookup = new Lookup(this);
|
lookup = new Lookup(this);
|
||||||
backup = new Backup(this);
|
backup = new Backup(this);
|
||||||
settings = new Settings(this);
|
|
||||||
migrations = new Migrations(this);
|
migrations = new Migrations(this);
|
||||||
monographs = new Monographs(this);
|
monographs = new Monographs(this);
|
||||||
trash = new Trash(this);
|
trash = new Trash(this);
|
||||||
|
sanitizer = new Sanitizer(this.sql);
|
||||||
|
|
||||||
notebooks = new Notebooks(this);
|
notebooks = new Notebooks(this);
|
||||||
tags = new Tags(this);
|
tags = new Tags(this);
|
||||||
@@ -194,6 +195,7 @@ class Database {
|
|||||||
relations = new Relations(this);
|
relations = new Relations(this);
|
||||||
notes = new Notes(this);
|
notes = new Notes(this);
|
||||||
vaults = new Vaults(this);
|
vaults = new Vaults(this);
|
||||||
|
settings = new Settings(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated only kept here for migration purposes
|
* @deprecated only kept here for migration purposes
|
||||||
@@ -270,6 +272,8 @@ class Database {
|
|||||||
this.options.sqliteOptions
|
this.options.sqliteOptions
|
||||||
)) as unknown as Kysely<DatabaseSchema>;
|
)) as unknown as Kysely<DatabaseSchema>;
|
||||||
|
|
||||||
|
await this.sanitizer.init();
|
||||||
|
|
||||||
await this.initCollections();
|
await this.initCollections();
|
||||||
|
|
||||||
await this.migrations.init();
|
await this.migrations.init();
|
||||||
@@ -294,6 +298,7 @@ class Database {
|
|||||||
await this.reminders.init();
|
await this.reminders.init();
|
||||||
await this.relations.init();
|
await this.relations.init();
|
||||||
await this.notes.init();
|
await this.notes.init();
|
||||||
|
await this.vaults.init();
|
||||||
|
|
||||||
await this.trash.init();
|
await this.trash.init();
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export class Attachments implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"attachments",
|
"attachments",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
this.key = null;
|
this.key = null;
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export class Colors implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"colors",
|
"colors",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export class Content implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"content",
|
"content",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export class NoteHistory implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"notehistory",
|
"notehistory",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export class Notebooks implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"notebooks",
|
"notebooks",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ export class Notes implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"notes",
|
"notes",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,14 @@ export class Relations implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"relations",
|
"relations",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
// await this.buildCache();
|
// await this.buildCache();
|
||||||
// return this.collection.init();
|
await this.collection.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(from: ItemReference, to: ItemReference) {
|
async add(from: ItemReference, to: ItemReference) {
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export class Reminders implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"reminders",
|
"reminders",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export class SessionContent implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"sessioncontent",
|
"sessioncontent",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ export class Settings implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"settings",
|
"settings",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ export class Shortcuts implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"shortcuts",
|
"shortcuts",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export class Tags implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"tags",
|
"tags",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ export class Vaults implements ICollection {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
"vaults",
|
"vaults",
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {}
|
async init() {
|
||||||
|
await this.collection.init();
|
||||||
|
}
|
||||||
|
|
||||||
async add(item: Partial<Vault>) {
|
async add(item: Partial<Vault>) {
|
||||||
const id = item.id || getId();
|
const id = item.id || getId();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { test, expect } from "vitest";
|
|||||||
|
|
||||||
test("img src is empty after extract attachments", async () => {
|
test("img src is empty after extract attachments", async () => {
|
||||||
const tiptap = new Tiptap(IMG_CONTENT_WITHOUT_HASH);
|
const tiptap = new Tiptap(IMG_CONTENT_WITHOUT_HASH);
|
||||||
const result = await tiptap.extractAttachments(async () => {
|
const result = await tiptap.postProcess(async () => {
|
||||||
return "helloworld";
|
return "helloworld";
|
||||||
});
|
});
|
||||||
expect(result.hashes).toHaveLength(1);
|
expect(result.hashes).toHaveLength(1);
|
||||||
@@ -37,7 +37,7 @@ test("img src is empty after extract attachments", async () => {
|
|||||||
|
|
||||||
test("img src is present after insert attachments", async () => {
|
test("img src is present after insert attachments", async () => {
|
||||||
const tiptap = new Tiptap(IMG_CONTENT);
|
const tiptap = new Tiptap(IMG_CONTENT);
|
||||||
const result = await tiptap.extractAttachments(async () => {
|
const result = await tiptap.postProcess(async () => {
|
||||||
return { key: "hello", metadata: {} };
|
return { key: "hello", metadata: {} };
|
||||||
});
|
});
|
||||||
const tiptap2 = new Tiptap(result.data);
|
const tiptap2 = new Tiptap(result.data);
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ class Migrator {
|
|||||||
db.sql,
|
db.sql,
|
||||||
db.transaction,
|
db.transaction,
|
||||||
collection.table,
|
collection.table,
|
||||||
db.eventManager
|
db.eventManager,
|
||||||
|
db.sanitizer
|
||||||
);
|
);
|
||||||
if (version <= 5.9) {
|
if (version <= 5.9) {
|
||||||
if (collection.name === "settings") {
|
if (collection.name === "settings") {
|
||||||
|
|||||||
67
packages/core/src/database/sanitizer.ts
Normal file
67
packages/core/src/database/sanitizer.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
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 { DatabaseAccessor } from ".";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
|
||||||
|
export class Sanitizer {
|
||||||
|
tables: Record<string, Set<string>> = {};
|
||||||
|
logger = logger.scope("sanitizer");
|
||||||
|
constructor(private readonly db: DatabaseAccessor) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const metadata = await this.db().introspection.getTables({
|
||||||
|
withInternalKyselyTables: false
|
||||||
|
});
|
||||||
|
for (const table of metadata) {
|
||||||
|
this.tables[table.name] = new Set(table.columns.map((c) => c.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitization is done based on the latest table schema in the database. All
|
||||||
|
* unrecognized keys are removed
|
||||||
|
*/
|
||||||
|
sanitize(table: string, item: any) {
|
||||||
|
const schema = this.tables[table];
|
||||||
|
if (!schema) {
|
||||||
|
if (process.env.NODE_ENV === "test")
|
||||||
|
throw new Error(
|
||||||
|
`Invalid table: ${table} (expected one of ${Object.keys(
|
||||||
|
this.tables
|
||||||
|
).join(", ")})`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in item) {
|
||||||
|
if (schema.has(key)) continue;
|
||||||
|
if (process.env.NODE_ENV === "test")
|
||||||
|
throw new Error(`Found invalid key in item ${key} (${table})`);
|
||||||
|
else
|
||||||
|
this.logger.debug("Found invalid key in item", {
|
||||||
|
table,
|
||||||
|
key,
|
||||||
|
value: item[key]
|
||||||
|
});
|
||||||
|
delete item[key];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import EventManager from "../utils/event-manager";
|
|||||||
import { DatabaseAccessor, DatabaseCollection, DatabaseSchema } from ".";
|
import { DatabaseAccessor, DatabaseCollection, DatabaseSchema } from ".";
|
||||||
import { SQLCollection } from "./sql-collection";
|
import { SQLCollection } from "./sql-collection";
|
||||||
import { Transaction } from "kysely";
|
import { Transaction } from "kysely";
|
||||||
|
import { Sanitizer } from "./sanitizer";
|
||||||
|
|
||||||
export class SQLCachedCollection<
|
export class SQLCachedCollection<
|
||||||
TCollectionType extends keyof DatabaseSchema,
|
TCollectionType extends keyof DatabaseSchema,
|
||||||
@@ -38,13 +39,15 @@ export class SQLCachedCollection<
|
|||||||
executor: (tr: Transaction<DatabaseSchema>) => Promise<void>
|
executor: (tr: Transaction<DatabaseSchema>) => Promise<void>
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
type: TCollectionType,
|
type: TCollectionType,
|
||||||
eventManager: EventManager
|
eventManager: EventManager,
|
||||||
|
sanitizer: Sanitizer
|
||||||
) {
|
) {
|
||||||
this.collection = new SQLCollection(
|
this.collection = new SQLCollection(
|
||||||
sql,
|
sql,
|
||||||
startTransaction,
|
startTransaction,
|
||||||
type,
|
type,
|
||||||
eventManager
|
eventManager,
|
||||||
|
sanitizer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
|
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
|
||||||
import { groupArray } from "../utils/grouping";
|
import { groupArray } from "../utils/grouping";
|
||||||
import { toChunks } from "../utils/array";
|
import { toChunks } from "../utils/array";
|
||||||
|
import { Sanitizer } from "./sanitizer";
|
||||||
|
|
||||||
const formats = {
|
const formats = {
|
||||||
month: "%Y-%m",
|
month: "%Y-%m",
|
||||||
@@ -66,7 +67,8 @@ export class SQLCollection<
|
|||||||
executor: (tr: Transaction<DatabaseSchema>) => Promise<void>
|
executor: (tr: Transaction<DatabaseSchema>) => Promise<void>
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
private readonly type: TCollectionType,
|
private readonly type: TCollectionType,
|
||||||
private readonly eventManager: EventManager
|
private readonly eventManager: EventManager,
|
||||||
|
private readonly sanitizer: Sanitizer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
@@ -87,6 +89,8 @@ export class SQLCollection<
|
|||||||
// the item has become local now, so remove the flags
|
// the item has become local now, so remove the flags
|
||||||
delete item.remote;
|
delete item.remote;
|
||||||
|
|
||||||
|
if (!this.sanitizer.sanitize(this.type, item)) return;
|
||||||
|
|
||||||
await this.db()
|
await this.db()
|
||||||
.replaceInto<keyof DatabaseSchema>(this.type)
|
.replaceInto<keyof DatabaseSchema>(this.type)
|
||||||
.values(item)
|
.values(item)
|
||||||
@@ -163,6 +167,9 @@ export class SQLCollection<
|
|||||||
item.synced = false;
|
item.synced = false;
|
||||||
}
|
}
|
||||||
delete item.remote;
|
delete item.remote;
|
||||||
|
|
||||||
|
if (!this.sanitizer.sanitize(this.type, item)) return array;
|
||||||
|
|
||||||
array.push(item);
|
array.push(item);
|
||||||
return array;
|
return array;
|
||||||
}, [] as SQLiteItem<T>[]);
|
}, [] as SQLiteItem<T>[]);
|
||||||
@@ -185,6 +192,7 @@ export class SQLCollection<
|
|||||||
partial: Partial<SQLiteItem<T>>,
|
partial: Partial<SQLiteItem<T>>,
|
||||||
options: { sendEvent: boolean } = { sendEvent: true }
|
options: { sendEvent: boolean } = { sendEvent: true }
|
||||||
) {
|
) {
|
||||||
|
if (!this.sanitizer.sanitize(this.type, partial)) return;
|
||||||
await this.db()
|
await this.db()
|
||||||
.updateTable<keyof DatabaseSchema>(this.type)
|
.updateTable<keyof DatabaseSchema>(this.type)
|
||||||
.where("id", "in", ids)
|
.where("id", "in", ids)
|
||||||
|
|||||||
Reference in New Issue
Block a user