mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
core: add tests for migrations
This commit is contained in:
@@ -45,6 +45,14 @@ class Migrations {
|
||||
await this.db.notes.init();
|
||||
|
||||
const collections: MigratableCollections = [
|
||||
{
|
||||
items: () => [this.db.legacySettings.raw],
|
||||
type: "settings"
|
||||
},
|
||||
{
|
||||
items: () => this.db.settings.raw,
|
||||
type: "settingsv2"
|
||||
},
|
||||
{
|
||||
items: () => this.db.attachments.all,
|
||||
type: "attachments"
|
||||
@@ -65,10 +73,6 @@ class Migrations {
|
||||
iterate: true,
|
||||
type: "content"
|
||||
},
|
||||
{
|
||||
items: () => [this.db.settings.raw],
|
||||
type: "settings"
|
||||
},
|
||||
{
|
||||
items: () => this.db.shortcuts.raw,
|
||||
type: "shortcuts"
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import {
|
||||
checkSyncStatus,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
EV,
|
||||
EVENTS,
|
||||
sendSyncProgressEvent,
|
||||
@@ -43,19 +44,11 @@ import {
|
||||
Notebook,
|
||||
TrashOrItem
|
||||
} from "../../types";
|
||||
import { SyncableItemType, SyncTransferItem } from "./types";
|
||||
|
||||
const ITEM_TYPE_TO_COLLECTION_TYPE = {
|
||||
note: "notes",
|
||||
notebook: "notebooks",
|
||||
content: "content",
|
||||
attachment: "attachments",
|
||||
relation: "relations",
|
||||
reminder: "reminders",
|
||||
shortcut: "shortcuts",
|
||||
tag: "tags",
|
||||
color: "colors"
|
||||
};
|
||||
import {
|
||||
MERGE_COLLECTIONS_MAP,
|
||||
SyncableItemType,
|
||||
SyncTransferItem
|
||||
} from "./types";
|
||||
|
||||
export type SyncOptions = {
|
||||
type: "full" | "fetch" | "send";
|
||||
@@ -388,6 +381,9 @@ class Sync {
|
||||
dbLastSynced: number,
|
||||
notify = false
|
||||
) {
|
||||
const itemType = chunk.type;
|
||||
if (itemType === "settings") return;
|
||||
|
||||
const decrypted = await this.db.storage().decryptMulti(key, chunk.items);
|
||||
|
||||
const deserialized = await Promise.all(
|
||||
@@ -396,7 +392,6 @@ class Sync {
|
||||
)
|
||||
);
|
||||
|
||||
const itemType = chunk.type;
|
||||
let items: (
|
||||
| MaybeDeletedItem<
|
||||
ItemMap[SyncableItemType] | TrashOrItem<Note> | TrashOrItem<Notebook>
|
||||
@@ -412,9 +407,6 @@ class Sync {
|
||||
this.merger.mergeContent(item, localItems[item.id], dbLastSynced)
|
||||
)
|
||||
);
|
||||
} else if (itemType === "settings") {
|
||||
await this.merger.mergeItem(deserialized[0], itemType, dbLastSynced);
|
||||
return;
|
||||
} else {
|
||||
items = this.merger.isSyncCollection(itemType)
|
||||
? deserialized.map((item) =>
|
||||
@@ -427,7 +419,7 @@ class Sync {
|
||||
);
|
||||
}
|
||||
|
||||
const collectionType = ITEM_TYPE_TO_COLLECTION_TYPE[itemType];
|
||||
const collectionType = MERGE_COLLECTIONS_MAP[itemType];
|
||||
await this.db[collectionType].collection.setItems(items as any);
|
||||
|
||||
if (
|
||||
@@ -498,6 +490,7 @@ async function deserializeItem(
|
||||
await migrateItem(
|
||||
deserialized,
|
||||
version,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
deserialized.type,
|
||||
database,
|
||||
"sync"
|
||||
|
||||
@@ -48,6 +48,12 @@ export const SYNC_COLLECTIONS_MAP = {
|
||||
settingitem: "settings"
|
||||
} as const;
|
||||
|
||||
export const MERGE_COLLECTIONS_MAP = {
|
||||
...SYNC_COLLECTIONS_MAP,
|
||||
attachment: "attachments",
|
||||
content: "content"
|
||||
} as const;
|
||||
|
||||
export type SyncTransferItem = {
|
||||
items: SyncItem[];
|
||||
type: SyncableItemType;
|
||||
|
||||
@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { makeId } from "../utils/id";
|
||||
import Database from "../api";
|
||||
import {
|
||||
DefaultNotebook,
|
||||
GroupOptions,
|
||||
GroupingKey,
|
||||
SettingItem,
|
||||
@@ -138,7 +137,7 @@ export class Settings implements ICollection {
|
||||
return this.get("trashCleanupInterval");
|
||||
}
|
||||
|
||||
setDefaultNotebook(item: DefaultNotebook | undefined) {
|
||||
setDefaultNotebook(item: string | undefined) {
|
||||
return this.set("defaultNotebook", item);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ const itemTypeToCollectionKey = {
|
||||
notehistory: "notehistory",
|
||||
content: "content",
|
||||
shortcut: "shortcuts",
|
||||
settingitem: "settingsv2",
|
||||
|
||||
// to make ts happy
|
||||
topic: "topics"
|
||||
@@ -343,13 +344,21 @@ export default class Backup {
|
||||
if ("sessionContentId" in item && item.type !== "session")
|
||||
(item as any).type = "notehistory";
|
||||
|
||||
await migrateItem(item, version, item.type, this.db, "backup");
|
||||
await migrateItem(
|
||||
item,
|
||||
version,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
item.type,
|
||||
this.db,
|
||||
"backup"
|
||||
);
|
||||
// since items in trash can have their own set of migrations,
|
||||
// we have to run the migration again to account for that.
|
||||
if (item.type === "trash" && item.itemType)
|
||||
await migrateItem(
|
||||
item as unknown as Note | Notebook,
|
||||
version,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
item.itemType,
|
||||
this.db,
|
||||
"backup"
|
||||
|
||||
@@ -18,7 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Database from "../api";
|
||||
import { sendMigrationProgressEvent } from "../common";
|
||||
import {
|
||||
CURRENT_DATABASE_VERSION,
|
||||
sendMigrationProgressEvent
|
||||
} from "../common";
|
||||
import { migrateCollection, migrateItem } from "../migrations";
|
||||
import {
|
||||
CollectionType,
|
||||
@@ -99,17 +102,31 @@ class Migrator {
|
||||
}
|
||||
|
||||
const itemId = item.id;
|
||||
const migrated = await migrateItem(
|
||||
let migrated = await migrateItem(
|
||||
item,
|
||||
version,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
item.type || type,
|
||||
db,
|
||||
"local"
|
||||
);
|
||||
|
||||
// trash item is also a notebook or a note so we have to migrate it separately.
|
||||
if (isTrashItem(item)) {
|
||||
migrated = await migrateItem(
|
||||
item as any,
|
||||
version,
|
||||
CURRENT_DATABASE_VERSION,
|
||||
item.itemType,
|
||||
db,
|
||||
"local"
|
||||
);
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
if (item.type === "settings") {
|
||||
await db.settings.merge(item, Infinity);
|
||||
if (item.type !== "settings") {
|
||||
// we are removing the old settings.
|
||||
await db.storage().remove("settings");
|
||||
} else toAdd.push(item);
|
||||
|
||||
// if id changed after migration, we need to delete the old one.
|
||||
|
||||
@@ -76,7 +76,7 @@ const migrations: Migration[] = [
|
||||
notebook: replaceDateEditedWithDateModified(false),
|
||||
tag: replaceDateEditedWithDateModified(true),
|
||||
attachment: replaceDateEditedWithDateModified(true),
|
||||
trash: replaceDateEditedWithDateModified(),
|
||||
trash: replaceDateEditedWithDateModified(false),
|
||||
tiny: (item) => {
|
||||
replaceDateEditedWithDateModified(false)(item);
|
||||
|
||||
@@ -147,12 +147,10 @@ const migrations: Migration[] = [
|
||||
version: 5.7,
|
||||
items: {
|
||||
tiny: (item) => {
|
||||
if (!item.data || isCipher(item.data)) return false;
|
||||
item.type = "tiptap";
|
||||
return changeSessionContentType(item);
|
||||
},
|
||||
content: (item) => {
|
||||
if (!item.data || isCipher(item.data)) return false;
|
||||
const oldType = item.type;
|
||||
item.type = "tiptap";
|
||||
return oldType !== item.type;
|
||||
@@ -190,9 +188,18 @@ const migrations: Migration[] = [
|
||||
version: 5.9,
|
||||
items: {
|
||||
tag: async (item, db) => {
|
||||
const oldTagId = makeId(item.title);
|
||||
const alias = db.legacySettings.getAlias(item.id);
|
||||
item.title = alias || item.title;
|
||||
item.id = getId(item.dateCreated);
|
||||
if (
|
||||
!alias &&
|
||||
(db.tags.all.find(
|
||||
(t) => item.title === t.title && t.id !== oldTagId
|
||||
) ||
|
||||
db.colors.all.find(
|
||||
(t) => item.title === t.title && t.id !== oldTagId
|
||||
))
|
||||
)
|
||||
return false;
|
||||
|
||||
const colorCode = ColorToHexCode[item.title];
|
||||
if (colorCode) {
|
||||
@@ -200,6 +207,9 @@ const migrations: Migration[] = [
|
||||
(item as unknown as Color).colorCode = colorCode;
|
||||
}
|
||||
|
||||
item.title = alias || item.title;
|
||||
item.id = getId(item.dateCreated);
|
||||
|
||||
delete item.localOnly;
|
||||
delete item.noteIds;
|
||||
delete item.alias;
|
||||
@@ -229,9 +239,9 @@ const migrations: Migration[] = [
|
||||
|
||||
if (item.color) {
|
||||
const oldColorId = makeId(item.color);
|
||||
const oldColor = db.tags.tag(oldColorId);
|
||||
const oldColor = db.colors.color(oldColorId);
|
||||
const alias = db.legacySettings.getAlias(oldColorId);
|
||||
const newColor = db.tags.all.find(
|
||||
const newColor = db.colors.all.find(
|
||||
(t) => [alias, item.color].includes(t.title) && t.id !== oldColorId
|
||||
);
|
||||
const newColorId =
|
||||
@@ -257,6 +267,7 @@ const migrations: Migration[] = [
|
||||
}
|
||||
}
|
||||
|
||||
delete item.notebooks;
|
||||
delete item.tags;
|
||||
delete item.color;
|
||||
return true;
|
||||
@@ -296,7 +307,11 @@ const migrations: Migration[] = [
|
||||
if (item.trashCleanupInterval)
|
||||
await db.settings.setTrashCleanupInterval(item.trashCleanupInterval);
|
||||
if (item.defaultNotebook)
|
||||
await db.settings.setDefaultNotebook(item.defaultNotebook);
|
||||
await db.settings.setDefaultNotebook(
|
||||
item.defaultNotebook
|
||||
? item.defaultNotebook.topic || item.defaultNotebook.id
|
||||
: undefined
|
||||
);
|
||||
|
||||
if (item.titleFormat)
|
||||
await db.settings.setTitleFormat(item.titleFormat);
|
||||
@@ -332,15 +347,18 @@ const migrations: Migration[] = [
|
||||
|
||||
export async function migrateItem<TItemType extends MigrationItemType>(
|
||||
item: MigrationItemMap[TItemType],
|
||||
version: number,
|
||||
itemVersion: number,
|
||||
databaseVersion: number,
|
||||
type: TItemType,
|
||||
database: Database,
|
||||
migrationType: MigrationType
|
||||
) {
|
||||
let migrationStartIndex = migrations.findIndex((m) => m.version === version);
|
||||
let migrationStartIndex = migrations.findIndex(
|
||||
(m) => m.version === itemVersion
|
||||
);
|
||||
if (migrationStartIndex <= -1) {
|
||||
throw new Error(
|
||||
version > CURRENT_DATABASE_VERSION
|
||||
itemVersion > databaseVersion
|
||||
? `Please update the app to the latest version.`
|
||||
: `You seem to be on a very outdated version. Please update the app to the latest version.`
|
||||
);
|
||||
@@ -349,7 +367,7 @@ export async function migrateItem<TItemType extends MigrationItemType>(
|
||||
let count = 0;
|
||||
for (; migrationStartIndex < migrations.length; ++migrationStartIndex) {
|
||||
const migration = migrations[migrationStartIndex];
|
||||
if (migration.version === CURRENT_DATABASE_VERSION) break;
|
||||
if (migration.version === databaseVersion) break;
|
||||
|
||||
if (
|
||||
migration.items.all &&
|
||||
|
||||
@@ -86,7 +86,7 @@ export type GroupableItem = ValueOf<
|
||||
| "session"
|
||||
| "sessioncontent"
|
||||
| "settings"
|
||||
| "settingsv2"
|
||||
| "settingitem"
|
||||
>
|
||||
>;
|
||||
|
||||
@@ -324,7 +324,7 @@ export type DefaultNotebook = { id: string; topic?: string };
|
||||
*/
|
||||
export interface LegacySettingsItem extends BaseItem<"settings"> {
|
||||
groupOptions?: Partial<Record<GroupingKey, GroupOptions>>;
|
||||
toolbarConfig?: Record<ToolbarConfigPlatforms, ToolbarConfig>;
|
||||
toolbarConfig?: Partial<Record<ToolbarConfigPlatforms, ToolbarConfig>>;
|
||||
trashCleanupInterval?: TrashCleanupInterval;
|
||||
titleFormat?: string;
|
||||
timeFormat?: TimeFormat;
|
||||
@@ -350,7 +350,7 @@ export type SettingItemMap = {
|
||||
titleFormat: string;
|
||||
timeFormat: TimeFormat;
|
||||
dateFormat: string;
|
||||
defaultNotebook: DefaultNotebook | undefined;
|
||||
defaultNotebook: string | undefined;
|
||||
} & Record<`groupOptions:${GroupingKey}`, GroupOptions> &
|
||||
Record<`toolbarConfig:${ToolbarConfigPlatforms}`, ToolbarConfig | undefined>;
|
||||
|
||||
@@ -389,9 +389,7 @@ export function isDeleted<T extends BaseItem<ItemType>>(
|
||||
return "deleted" in item;
|
||||
}
|
||||
|
||||
export function isTrashItem(
|
||||
item: MaybeDeletedItem<TrashOrItem<BaseItem<"note" | "notebook">>>
|
||||
): item is TrashItem {
|
||||
export function isTrashItem(item: MaybeDeletedItem<Item>): item is TrashItem {
|
||||
return !isDeleted(item) && item.type === "trash";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user