feat: add default search engines seeding / set homarr docs as global server search engine (#2663)
* feat: add default search engines seeding * feat: set Homarr Docs as the default search Set the default search engine in server settings during seeding * refactor: use typed methods to define settings * feat: add insertServerSettingByKeyAsync * feat: update seeding logic for server settings * fix: format file using prettier * fix: disable eslint for `urlTemplate` * refactor: remove never happning else * feat: enhance createDocumentationLink - Updated createDocumentationLink to accept query parameters * test: add unit tests for createDocumentationLink * fix: update urlTemplate for Homarr documentation
This commit is contained in:
@@ -1,18 +1,22 @@
|
|||||||
import SuperJSON from "superjson";
|
|
||||||
|
|
||||||
import { objectKeys } from "@homarr/common";
|
import { objectKeys } from "@homarr/common";
|
||||||
import { everyoneGroup } from "@homarr/definitions";
|
import { createDocumentationLink, everyoneGroup } from "@homarr/definitions";
|
||||||
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
|
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||||
|
|
||||||
import { createId, eq } from "..";
|
|
||||||
import type { Database } from "..";
|
import type { Database } from "..";
|
||||||
import { onboarding, serverSettings } from "../schema";
|
import { createId, eq } from "..";
|
||||||
|
import {
|
||||||
|
getServerSettingByKeyAsync,
|
||||||
|
insertServerSettingByKeyAsync,
|
||||||
|
updateServerSettingByKeyAsync,
|
||||||
|
} from "../queries/server-setting";
|
||||||
|
import { onboarding, searchEngines } from "../schema";
|
||||||
import { groups } from "../schema/mysql";
|
import { groups } from "../schema/mysql";
|
||||||
|
|
||||||
export const seedDataAsync = async (db: Database) => {
|
export const seedDataAsync = async (db: Database) => {
|
||||||
await seedEveryoneGroupAsync(db);
|
await seedEveryoneGroupAsync(db);
|
||||||
await seedOnboardingAsync(db);
|
await seedOnboardingAsync(db);
|
||||||
await seedServerSettingsAsync(db);
|
await seedServerSettingsAsync(db);
|
||||||
|
await seedDefaultSearchEnginesAsync(db);
|
||||||
};
|
};
|
||||||
|
|
||||||
const seedEveryoneGroupAsync = async (db: Database) => {
|
const seedEveryoneGroupAsync = async (db: Database) => {
|
||||||
@@ -48,21 +52,73 @@ const seedOnboardingAsync = async (db: Database) => {
|
|||||||
console.log("Created onboarding step through seed");
|
console.log("Created onboarding step through seed");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const seedDefaultSearchEnginesAsync = async (db: Database) => {
|
||||||
|
const existingSearchEngines = await db.$count(searchEngines);
|
||||||
|
|
||||||
|
if (existingSearchEngines > 0) {
|
||||||
|
console.log("Skipping seeding of default search engines as some already exists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const homarrId = createId();
|
||||||
|
const defaultSearchEngines = [
|
||||||
|
{
|
||||||
|
id: createId(),
|
||||||
|
name: "Google",
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/google.svg",
|
||||||
|
short: "g",
|
||||||
|
description: "Search the web with Google",
|
||||||
|
urlTemplate: "https://www.google.com/search?q=%s",
|
||||||
|
type: "generic" as const,
|
||||||
|
integrationId: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: createId(),
|
||||||
|
name: "YouTube",
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/youtube.svg",
|
||||||
|
short: "yt",
|
||||||
|
description: "Search for videos on YouTube",
|
||||||
|
urlTemplate: "https://www.youtube.com/results?search_query=%s",
|
||||||
|
type: "generic" as const,
|
||||||
|
integrationId: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: homarrId,
|
||||||
|
name: "Homarr Docs",
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||||
|
short: "docs",
|
||||||
|
description: "Search the Homarr documentation",
|
||||||
|
urlTemplate: createDocumentationLink("/search", undefined, { q: "%s" }),
|
||||||
|
type: "generic" as const,
|
||||||
|
integrationId: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await db.insert(searchEngines).values(defaultSearchEngines);
|
||||||
|
console.log(`Created ${defaultSearchEngines.length} default search engines through seeding process`);
|
||||||
|
|
||||||
|
// Set Homarr docs as the default search engine in server settings
|
||||||
|
const searchSettings = await getServerSettingByKeyAsync(db, "search");
|
||||||
|
|
||||||
|
await updateServerSettingByKeyAsync(db, "search", {
|
||||||
|
...searchSettings,
|
||||||
|
defaultSearchEngineId: homarrId,
|
||||||
|
});
|
||||||
|
console.log("Set Homarr docs as the default search engine");
|
||||||
|
};
|
||||||
|
|
||||||
const seedServerSettingsAsync = async (db: Database) => {
|
const seedServerSettingsAsync = async (db: Database) => {
|
||||||
const serverSettingsData = await db.query.serverSettings.findMany();
|
const serverSettingsData = await db.query.serverSettings.findMany();
|
||||||
|
|
||||||
for (const settingsKey of defaultServerSettingsKeys) {
|
for (const settingsKey of defaultServerSettingsKeys) {
|
||||||
const currentDbEntry = serverSettingsData.find((setting) => setting.settingKey === settingsKey);
|
const currentDbEntry = serverSettingsData.find((setting) => setting.settingKey === settingsKey);
|
||||||
if (!currentDbEntry) {
|
if (!currentDbEntry) {
|
||||||
await db.insert(serverSettings).values({
|
await insertServerSettingByKeyAsync(db, settingsKey, defaultServerSettings[settingsKey]);
|
||||||
settingKey: settingsKey,
|
|
||||||
value: SuperJSON.stringify(defaultServerSettings[settingsKey]),
|
|
||||||
});
|
|
||||||
console.log(`Created serverSetting through seed key=${settingsKey}`);
|
console.log(`Created serverSetting through seed key=${settingsKey}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSettings = SuperJSON.parse<Record<string, unknown>>(currentDbEntry.value);
|
const currentSettings = await getServerSettingByKeyAsync(db, settingsKey);
|
||||||
const defaultSettings = defaultServerSettings[settingsKey];
|
const defaultSettings = defaultServerSettings[settingsKey];
|
||||||
const missingKeys = objectKeys(defaultSettings).filter((key) => !(key in currentSettings));
|
const missingKeys = objectKeys(defaultSettings).filter((key) => !(key in currentSettings));
|
||||||
|
|
||||||
@@ -71,12 +127,7 @@ const seedServerSettingsAsync = async (db: Database) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await updateServerSettingByKeyAsync(db, settingsKey, { ...defaultSettings, ...currentSettings });
|
||||||
.update(serverSettings)
|
|
||||||
.set({
|
|
||||||
value: SuperJSON.stringify({ ...defaultSettings, ...currentSettings }), // Add missing keys
|
|
||||||
})
|
|
||||||
.where(eq(serverSettings.settingKey, settingsKey));
|
|
||||||
console.log(`Updated serverSetting through seed key=${settingsKey}`);
|
console.log(`Updated serverSetting through seed key=${settingsKey}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,3 +50,14 @@ export const updateServerSettingByKeyAsync = async <TKey extends keyof ServerSet
|
|||||||
})
|
})
|
||||||
.where(eq(serverSettings.settingKey, key));
|
.where(eq(serverSettings.settingKey, key));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertServerSettingByKeyAsync = async <TKey extends keyof ServerSettings>(
|
||||||
|
db: Database,
|
||||||
|
key: TKey,
|
||||||
|
value: ServerSettings[TKey],
|
||||||
|
) => {
|
||||||
|
await db.insert(serverSettings).values({
|
||||||
|
settingKey: key,
|
||||||
|
value: SuperJSON.stringify(value),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,5 +3,12 @@ import type { HomarrDocumentationPath } from "./homarr-docs-sitemap";
|
|||||||
const documentationBaseUrl = "https://homarr.dev";
|
const documentationBaseUrl = "https://homarr.dev";
|
||||||
|
|
||||||
// Please use the method so the path can be checked!
|
// Please use the method so the path can be checked!
|
||||||
export const createDocumentationLink = (path: HomarrDocumentationPath, hashTag?: `#${string}`) =>
|
export const createDocumentationLink = (
|
||||||
`${documentationBaseUrl}${path}${hashTag ?? ""}`;
|
path: HomarrDocumentationPath,
|
||||||
|
hashTag?: `#${string}`,
|
||||||
|
queryParams?: Record<string, string>,
|
||||||
|
) => {
|
||||||
|
const url = `${documentationBaseUrl}${path}`;
|
||||||
|
const params = queryParams ? `?${new URLSearchParams(queryParams)}` : "";
|
||||||
|
return `${url}${params}${hashTag ?? ""}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { createDocumentationLink } from "../docs";
|
||||||
|
import type { HomarrDocumentationPath } from "../docs/homarr-docs-sitemap";
|
||||||
|
|
||||||
|
describe("createDocumentationLink should generate correct URLs", () => {
|
||||||
|
test.each([
|
||||||
|
["/docs/getting-started", undefined, undefined, "https://homarr.dev/docs/getting-started"],
|
||||||
|
["/blog", undefined, undefined, "https://homarr.dev/blog"],
|
||||||
|
["/docs/widgets/weather", "#configuration", undefined, "https://homarr.dev/docs/widgets/weather#configuration"],
|
||||||
|
[
|
||||||
|
"/docs/advanced/environment-variables",
|
||||||
|
undefined,
|
||||||
|
{ lang: "en" },
|
||||||
|
"https://homarr.dev/docs/advanced/environment-variables?lang=en",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/docs/widgets/bookmarks",
|
||||||
|
"#sorting",
|
||||||
|
{ lang: "fr", theme: "dark" },
|
||||||
|
"https://homarr.dev/docs/widgets/bookmarks?lang=fr&theme=dark#sorting",
|
||||||
|
],
|
||||||
|
] satisfies [HomarrDocumentationPath, `#${string}` | undefined, Record<string, string> | undefined, string][])(
|
||||||
|
"should create correct URL for path %s with hash %s and params %o",
|
||||||
|
(path, hashTag, queryParams, expected) => {
|
||||||
|
expect(createDocumentationLink(path, hashTag, queryParams)).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createDocumentationLink parameter validation", () => {
|
||||||
|
test("should work with only path parameter", () => {
|
||||||
|
const result = createDocumentationLink("/docs/getting-started");
|
||||||
|
expect(result).toBe("https://homarr.dev/docs/getting-started");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should work with path and hashtag", () => {
|
||||||
|
const result = createDocumentationLink("/docs/getting-started", "#installation");
|
||||||
|
expect(result).toBe("https://homarr.dev/docs/getting-started#installation");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should work with path and query params", () => {
|
||||||
|
const result = createDocumentationLink("/docs/getting-started", undefined, { version: "1.0" });
|
||||||
|
expect(result).toBe("https://homarr.dev/docs/getting-started?version=1.0");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user