mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-11-01 16:15:24 +00:00
fix: add missing sync ids to old snippet tables so snippets sync again (Support#1419)
This commit is contained in:
1 parent
f783e6d51d
commit
679b61da9b
3 files changed
+86
-15
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
import { repairSnippetsNoteColumn } from "../../upgrade/snippets-schema-migration.js";
|
import { repairSnippetsColumns } from "../../upgrade/snippets-schema-migration.js";
|
||||||
import { getErrorMessage } from "../../utils/error-message.js";
|
import { getErrorMessage } from "../../utils/error-message.js";
|
||||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
@@ -928,7 +928,7 @@ const dropRetiredColumns = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const migrateSchema = () => {
|
const migrateSchema = () => {
|
||||||
repairSnippetsNoteColumn(sqlite);
|
repairSnippetsColumns(sqlite);
|
||||||
addColumnIfNotExists("user_preferences", "theme", "TEXT");
|
addColumnIfNotExists("user_preferences", "theme", "TEXT");
|
||||||
addColumnIfNotExists("user_preferences", "font_size", "TEXT");
|
addColumnIfNotExists("user_preferences", "font_size", "TEXT");
|
||||||
addColumnIfNotExists("user_preferences", "accent_color", "TEXT");
|
addColumnIfNotExists("user_preferences", "accent_color", "TEXT");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { repairSnippetsNoteColumn } from "../../upgrade/snippets-schema-migration.js";
|
import { repairSnippetsColumns } from "../../upgrade/snippets-schema-migration.js";
|
||||||
|
|
||||||
let sqlite: Database.Database;
|
let sqlite: Database.Database;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -10,7 +10,7 @@ afterEach(() => {
|
|||||||
sqlite.close();
|
sqlite.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("snippet note column upgrade", () => {
|
describe("snippet column upgrade", () => {
|
||||||
it.each(["snippets", "p_snippets_snippets"])(
|
it.each(["snippets", "p_snippets_snippets"])(
|
||||||
"repairs %s without losing rows",
|
"repairs %s without losing rows",
|
||||||
(table) => {
|
(table) => {
|
||||||
@@ -23,7 +23,7 @@ describe("snippet note column upgrade", () => {
|
|||||||
expect(() => sqlite.prepare(`SELECT "is_note" FROM "${table}"`)).toThrow(
|
expect(() => sqlite.prepare(`SELECT "is_note" FROM "${table}"`)).toThrow(
|
||||||
/is_note/,
|
/is_note/,
|
||||||
);
|
);
|
||||||
repairSnippetsNoteColumn(sqlite);
|
repairSnippetsColumns(sqlite);
|
||||||
if (table === "snippets") {
|
if (table === "snippets") {
|
||||||
// What the plugin migration does to the legacy table.
|
// What the plugin migration does to the legacy table.
|
||||||
sqlite.exec(`ALTER TABLE "snippets" RENAME TO "p_snippets_snippets"`);
|
sqlite.exec(`ALTER TABLE "snippets" RENAME TO "p_snippets_snippets"`);
|
||||||
@@ -34,7 +34,7 @@ describe("snippet note column upgrade", () => {
|
|||||||
.get(),
|
.get(),
|
||||||
).toEqual({ id: 7, content: "echo hello", is_note: 0 });
|
).toEqual({ id: 7, content: "echo hello", is_note: 0 });
|
||||||
sqlite.exec("UPDATE p_snippets_snippets SET is_note = 1");
|
sqlite.exec("UPDATE p_snippets_snippets SET is_note = 1");
|
||||||
repairSnippetsNoteColumn(sqlite);
|
repairSnippetsColumns(sqlite);
|
||||||
expect(
|
expect(
|
||||||
sqlite.prepare("SELECT is_note FROM p_snippets_snippets").get(),
|
sqlite.prepare("SELECT is_note FROM p_snippets_snippets").get(),
|
||||||
).toEqual({ is_note: 1 });
|
).toEqual({ is_note: 1 });
|
||||||
@@ -42,11 +42,53 @@ describe("snippet note column upgrade", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it("does not create tables on a fresh install", () => {
|
it("does not create tables on a fresh install", () => {
|
||||||
repairSnippetsNoteColumn(sqlite);
|
repairSnippetsColumns(sqlite);
|
||||||
expect(
|
expect(
|
||||||
sqlite
|
sqlite
|
||||||
.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
|
.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||||
.all(),
|
.all(),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("adds and fills sync ids on old snippet and folder tables", () => {
|
||||||
|
sqlite.exec(
|
||||||
|
`CREATE TABLE "p_snippets_snippets" (id INTEGER PRIMARY KEY, user_id TEXT, name TEXT, content TEXT)`,
|
||||||
|
);
|
||||||
|
sqlite.exec(
|
||||||
|
`CREATE TABLE "snippet_folders" (id INTEGER PRIMARY KEY, user_id TEXT, name TEXT)`,
|
||||||
|
);
|
||||||
|
sqlite.exec(
|
||||||
|
`INSERT INTO "p_snippets_snippets" VALUES (1, 'u', 'a', 'ls'), (2, 'u', 'b', 'pwd')`,
|
||||||
|
);
|
||||||
|
sqlite.exec(`INSERT INTO "snippet_folders" VALUES (1, 'u', 'ops')`);
|
||||||
|
repairSnippetsColumns(sqlite);
|
||||||
|
|
||||||
|
const snippets = sqlite
|
||||||
|
.prepare(
|
||||||
|
`SELECT "sync_id", "folder", "order", "host_filter" FROM "p_snippets_snippets"`,
|
||||||
|
)
|
||||||
|
.all() as Array<{ sync_id: string; order: number }>;
|
||||||
|
expect(snippets).toHaveLength(2);
|
||||||
|
expect(snippets[0].sync_id).toMatch(/^[0-9a-f]{32}$/);
|
||||||
|
expect(snippets[0].sync_id).not.toBe(snippets[1].sync_id);
|
||||||
|
expect(snippets[0].order).toBe(0);
|
||||||
|
expect(
|
||||||
|
sqlite
|
||||||
|
.prepare(`SELECT "sync_id", "color", "icon" FROM "snippet_folders"`)
|
||||||
|
.get(),
|
||||||
|
).toMatchObject({ color: null, icon: null });
|
||||||
|
expect(() =>
|
||||||
|
sqlite.exec(`UPDATE "p_snippets_snippets" SET "sync_id" = 'same'`),
|
||||||
|
).toThrow(/UNIQUE/);
|
||||||
|
|
||||||
|
const before = sqlite
|
||||||
|
.prepare(`SELECT "sync_id" FROM "p_snippets_snippets" ORDER BY id`)
|
||||||
|
.all();
|
||||||
|
repairSnippetsColumns(sqlite);
|
||||||
|
expect(
|
||||||
|
sqlite
|
||||||
|
.prepare(`SELECT "sync_id" FROM "p_snippets_snippets" ORDER BY id`)
|
||||||
|
.all(),
|
||||||
|
).toEqual(before);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,19 +1,48 @@
|
|||||||
import type Database from "better-sqlite3";
|
import type Database from "better-sqlite3";
|
||||||
|
|
||||||
|
// Columns old SQLite snippet tables can be missing, for tables made before
|
||||||
|
// they were added, or adopted by the plugin before core added them.
|
||||||
|
const SNIPPET_COLUMNS: Array<[string, string]> = [
|
||||||
|
["folder", "TEXT"],
|
||||||
|
["order", "INTEGER NOT NULL DEFAULT 0"],
|
||||||
|
["host_filter", "TEXT"],
|
||||||
|
["is_note", "INTEGER NOT NULL DEFAULT 0"],
|
||||||
|
["sync_id", "TEXT"],
|
||||||
|
];
|
||||||
|
const FOLDER_COLUMNS: Array<[string, string]> = [
|
||||||
|
["color", "TEXT"],
|
||||||
|
["icon", "TEXT"],
|
||||||
|
["sync_id", "TEXT"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const TABLES: Array<[string, Array<[string, string]>]> = [
|
||||||
|
["snippets", SNIPPET_COLUMNS],
|
||||||
|
["p_snippets_snippets", SNIPPET_COLUMNS],
|
||||||
|
["snippet_folders", FOLDER_COLUMNS],
|
||||||
|
["p_snippets_snippet_folders", FOLDER_COLUMNS],
|
||||||
|
];
|
||||||
|
|
||||||
/** Repair older SQLite tables before adoption, or after an incomplete 2.9 upgrade. */
|
/** Repair older SQLite tables before adoption, or after an incomplete 2.9 upgrade. */
|
||||||
export function repairSnippetsNoteColumn(sqlite: Database.Database): void {
|
export function repairSnippetsColumns(sqlite: Database.Database): void {
|
||||||
for (const table of ["snippets", "p_snippets_snippets"]) {
|
for (const [table, wanted] of TABLES) {
|
||||||
const columns = sqlite.pragma(`table_info("${table}")`) as Array<{
|
const columns = sqlite.pragma(`table_info("${table}")`) as Array<{
|
||||||
name: string;
|
name: string;
|
||||||
}>;
|
}>;
|
||||||
if (
|
if (columns.length === 0) continue;
|
||||||
columns.length === 0 ||
|
const have = new Set(columns.map(({ name }) => name));
|
||||||
columns.some(({ name }) => name === "is_note")
|
for (const [column, type] of wanted) {
|
||||||
) {
|
if (!have.has(column)) {
|
||||||
continue;
|
sqlite.exec(`ALTER TABLE "${table}" ADD COLUMN "${column}" ${type}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Sync matches rows by sync_id, so every row needs one.
|
||||||
sqlite.exec(
|
sqlite.exec(
|
||||||
`ALTER TABLE "${table}" ADD COLUMN "is_note" INTEGER NOT NULL DEFAULT 0`,
|
`UPDATE "${table}" SET "sync_id" = lower(hex(randomblob(16))) WHERE "sync_id" IS NULL`,
|
||||||
|
);
|
||||||
|
if (!have.has("sync_id")) {
|
||||||
|
sqlite.exec(
|
||||||
|
`CREATE UNIQUE INDEX IF NOT EXISTS "idx_${table}_sync_id" ON "${table}" ("sync_id")`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user