From 8a5ba2e6467c3fac46d11f7083a620ae51f86dde Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:59:24 +0100 Subject: [PATCH] fix: search engine name autogeneration --- .../router/integration/integration-router.ts | 38 ++++++++++++++++--- packages/db/schema/mysql.ts | 2 +- packages/db/schema/sqlite.ts | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 581584705f..48e8f39d92 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -3,7 +3,7 @@ import { TRPCError } from "@trpc/server"; import { objectEntries } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; import type { Database } from "@homarr/db"; -import { and, asc, createId, eq, inArray, like } from "@homarr/db"; +import { and, asc, createId, db, eq, inArray, like } from "@homarr/db"; import { groupMembers, groupPermissions, @@ -202,10 +202,8 @@ export const integrationRouter = createTRPCRouter({ name: input.name, integrationId, type: "fromIntegration", - iconUrl: - icon?.url ?? - "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/homarr.svg", - short: input.name.substring(0, 1), + iconUrl: icon?.url ?? "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/homarr.svg", + short: await getNextValidShortNameForSearchEngineAsync(input.name), }); } }), @@ -427,6 +425,36 @@ interface AddSecretInput { value: string; kind: IntegrationSecretKind; } + +const getNextValidShortNameForSearchEngineAsync = async (integrationName: string) => { + const searchEngines = await db.query.searchEngines.findMany({ + columns: { + short: true, + }, + }); + + const usedShortNames = searchEngines.flatMap((searchEngine) => searchEngine.short); + const nameByIntegrationName = integrationName.slice(0, 1).toLowerCase(); + + if (!usedShortNames.includes(nameByIntegrationName)) { + return nameByIntegrationName; + } + + // 8 is max length constraint + for (let i = 2; i < 9999999; i++) { + const generatedName = `${nameByIntegrationName}${i}`; + if (usedShortNames.includes(generatedName)) { + continue; + } + + return generatedName; + } + + throw new Error( + `Unable to automatically generate a short name. All possible variations were exhausted. Please disable the automatic creation and choose one later yourself.`, + ); +}; + const addSecretAsync = async (db: Database, input: AddSecretInput) => { await db.insert(integrationSecrets).values({ kind: input.kind, diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index b3ccabcc13..eee05d8015 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -389,7 +389,7 @@ export const searchEngines = mysqlTable("search_engine", { id: varchar({ length: 64 }).notNull().primaryKey(), iconUrl: text().notNull(), name: varchar({ length: 64 }).notNull(), - short: varchar({ length: 8 }).notNull(), + short: varchar({ length: 8 }).unique().notNull(), description: text(), urlTemplate: text(), type: varchar({ length: 64 }).$type().notNull().default("generic"), diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index 6a24a87ed3..e88a92297b 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -375,7 +375,7 @@ export const searchEngines = sqliteTable("search_engine", { id: text().notNull().primaryKey(), iconUrl: text().notNull(), name: text().notNull(), - short: text().notNull(), + short: text().unique().notNull(), description: text(), urlTemplate: text(), type: text().$type().notNull().default("generic"),