From 1a0ae504abc101cf6c454e7d22a79b80cf2b984a Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Tue, 24 Jun 2025 22:38:36 +0200 Subject: [PATCH] poc: widget options migration --- ...0000_release_widget_provider_to_options.ts | 62 +++++++++++++++++++ packages/db/migrations/custom/index.ts | 6 ++ packages/db/migrations/custom/run-custom.ts | 12 ++++ packages/db/migrations/mysql/migrate.ts | 2 + packages/db/migrations/sqlite/migrate.ts | 2 + packages/db/package.json | 5 +- 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 packages/db/migrations/custom/0000_release_widget_provider_to_options.ts create mode 100644 packages/db/migrations/custom/index.ts create mode 100644 packages/db/migrations/custom/run-custom.ts diff --git a/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts b/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts new file mode 100644 index 000000000..b77146056 --- /dev/null +++ b/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts @@ -0,0 +1,62 @@ +// TODO: add dependency +import SuperJSON from "superjson"; + +import type { IntegrationKind } from "@homarr/definitions"; + +import { eq, or } from "../.."; +import type { Database } from "../.."; +import { items } from "../../schema"; + +export async function migrateReleaseWidgetProviderToOptionsAsync(db: Database) { + const existingItems = await db.query.items.findMany({ + where: (items, { eq }) => eq(items.kind, "releases"), + }); + + const providerIntegrations = await db.query.integrations.findMany({ + where: (integrations, { eq }) => or(eq(integrations.kind, "github"), eq(integrations.kind, "gitlab")), + columns: { + id: true, + kind: true, + }, + }); + + const providerIntegrationMap = new Map( + providerIntegrations.map((integration) => [integration.kind, integration.id]), + ); + + const updates: { + id: string; + options: object; + }[] = []; + for (const item of existingItems) { + const options = SuperJSON.parse(item.options); + if (!("repositories" in options)) continue; + if (!Array.isArray(options.repositories)) continue; + if (options.repositories.length === 0) continue; + if (!options.repositories.some((repository) => "providerKey" in repository)) continue; + + const updatedRepositories = options.repositories.map(({ providerKey, ...otherFields }) => ({ + providerIntegrationId: providerIntegrationMap.get(providerKey) ?? null, // Or do custom mapping if needed + ...otherFields, + })); + + updates.push({ + id: item.id, + options: { + ...options, + repositories: updatedRepositories, + }, + }); + } + + for (const update of updates) { + await db + .update(items) + .set({ + options: SuperJSON.stringify(update.options), + }) + .where(eq(items.id, update.id)); + } + + console.log(`Migrated release widget providers to integrations count="${updates.length}"`); +} diff --git a/packages/db/migrations/custom/index.ts b/packages/db/migrations/custom/index.ts new file mode 100644 index 000000000..e60a52c5e --- /dev/null +++ b/packages/db/migrations/custom/index.ts @@ -0,0 +1,6 @@ +import type { Database } from "../.."; +import { migrateReleaseWidgetProviderToOptionsAsync } from "./0000_release_widget_provider_to_options"; + +export const applyCustomMigrationsAsync = async (db: Database) => { + await migrateReleaseWidgetProviderToOptionsAsync(db); +}; diff --git a/packages/db/migrations/custom/run-custom.ts b/packages/db/migrations/custom/run-custom.ts new file mode 100644 index 000000000..659a9d6c3 --- /dev/null +++ b/packages/db/migrations/custom/run-custom.ts @@ -0,0 +1,12 @@ +import { applyCustomMigrationsAsync } from "."; +import { database } from "../../driver"; + +applyCustomMigrationsAsync(database) + .then(() => { + console.log("Custom migrations applied successfully"); + process.exit(0); + }) + .catch((err) => { + console.log("Failed to apply custom migrations\n\t", err); + process.exit(1); + }); diff --git a/packages/db/migrations/mysql/migrate.ts b/packages/db/migrations/mysql/migrate.ts index 4b2616c49..afca2d7d4 100644 --- a/packages/db/migrations/mysql/migrate.ts +++ b/packages/db/migrations/mysql/migrate.ts @@ -5,6 +5,7 @@ import mysql from "mysql2"; import type { Database } from "../.."; import { env } from "../../env"; import * as mysqlSchema from "../../schema/mysql"; +import { applyCustomMigrationsAsync } from "../custom"; import { seedDataAsync } from "../seed"; const migrationsFolder = process.argv[2] ?? "."; @@ -30,6 +31,7 @@ const migrateAsync = async () => { await migrate(db, { migrationsFolder }); await seedDataAsync(db as unknown as Database); + await applyCustomMigrationsAsync(db as unknown as Database); }; migrateAsync() diff --git a/packages/db/migrations/sqlite/migrate.ts b/packages/db/migrations/sqlite/migrate.ts index e075a8d14..90d745746 100644 --- a/packages/db/migrations/sqlite/migrate.ts +++ b/packages/db/migrations/sqlite/migrate.ts @@ -4,6 +4,7 @@ import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { env } from "../../env"; import * as sqliteSchema from "../../schema/sqlite"; +import { applyCustomMigrationsAsync } from "../custom"; import { seedDataAsync } from "../seed"; const migrationsFolder = process.argv[2] ?? "."; @@ -16,6 +17,7 @@ const migrateAsync = async () => { migrate(db, { migrationsFolder }); await seedDataAsync(db); + await applyCustomMigrationsAsync(db); }; migrateAsync() diff --git a/packages/db/package.json b/packages/db/package.json index 9ce835207..d497aa3c7 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -23,12 +23,13 @@ "clean": "rm -rf .turbo node_modules", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", + "migration:custom": "pnpm with-env tsx ./migrations/custom/run-custom.ts", "migration:mysql:drop": "pnpm with-env drizzle-kit drop --config ./configs/mysql.config.ts", "migration:mysql:generate": "pnpm with-env drizzle-kit generate --config ./configs/mysql.config.ts", - "migration:mysql:run": "pnpm with-env drizzle-kit migrate --config ./configs/mysql.config.ts && pnpm run seed", + "migration:mysql:run": "pnpm with-env drizzle-kit migrate --config ./configs/mysql.config.ts && pnpm run seed && pnpm run migration:custom", "migration:sqlite:drop": "pnpm with-env drizzle-kit drop --config ./configs/sqlite.config.ts", "migration:sqlite:generate": "pnpm with-env drizzle-kit generate --config ./configs/sqlite.config.ts", - "migration:sqlite:run": "pnpm with-env drizzle-kit migrate --config ./configs/sqlite.config.ts && pnpm run seed", + "migration:sqlite:run": "pnpm with-env drizzle-kit migrate --config ./configs/sqlite.config.ts && pnpm run seed && pnpm run migration:custom", "push:mysql": "pnpm with-env drizzle-kit push --config ./configs/mysql.config.ts", "push:sqlite": "pnpm with-env drizzle-kit push --config ./configs/sqlite.config.ts", "seed": "pnpm with-env tsx ./migrations/run-seed.ts",