core: add tests for migrations

This commit is contained in:
Abdullah Atta
2023-09-18 11:26:30 +05:00
parent 588e37aa20
commit 86652230bc
9 changed files with 754 additions and 47 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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.

View File

@@ -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 &&

View File

@@ -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";
}