feat(spotlight): add default search engine (#1807)

This commit is contained in:
Meier Lukas
2025-01-06 19:59:40 +01:00
committed by GitHub
parent 6a68ccfee4
commit 65befa22ba
24 changed files with 3849 additions and 88 deletions

View File

@@ -1,12 +1,13 @@
import { TRPCError } from "@trpc/server";
import { createId, eq, like, sql } from "@homarr/db";
import { searchEngines } from "@homarr/db/schema";
import { asc, createId, eq, like, sql } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import { searchEngines, users } from "@homarr/db/schema";
import { integrationCreator } from "@homarr/integrations";
import { validation } from "@homarr/validation";
import { validation, z } from "@homarr/validation";
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
export const searchEngineRouter = createTRPCRouter({
getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => {
@@ -29,6 +30,21 @@ export const searchEngineRouter = createTRPCRouter({
totalCount: searchEngineCount[0]?.count ?? 0,
};
}),
getSelectable: protectedProcedure
.input(z.object({ withIntegrations: z.boolean() }).default({ withIntegrations: true }))
.query(async ({ ctx, input }) => {
return await ctx.db.query.searchEngines
.findMany({
orderBy: asc(searchEngines.name),
where: input.withIntegrations ? undefined : eq(searchEngines.type, "generic"),
columns: {
id: true,
name: true,
},
})
.then((engines) => engines.map((engine) => ({ value: engine.id, label: engine.name })));
}),
byId: protectedProcedure.input(validation.common.byId).query(async ({ ctx, input }) => {
const searchEngine = await ctx.db.query.searchEngines.findFirst({
where: eq(searchEngines.id, input.id),
@@ -55,6 +71,54 @@ export const searchEngineRouter = createTRPCRouter({
urlTemplate: searchEngine.urlTemplate!,
};
}),
getDefaultSearchEngine: publicProcedure.query(async ({ ctx }) => {
const userDefaultId = ctx.session?.user.id
? ((await ctx.db.query.users
.findFirst({
where: eq(users.id, ctx.session.user.id),
columns: {
defaultSearchEngineId: true,
},
})
.then((user) => user?.defaultSearchEngineId)) ?? null)
: null;
if (userDefaultId) {
return await ctx.db.query.searchEngines.findFirst({
where: eq(searchEngines.id, userDefaultId),
with: {
integration: {
columns: {
kind: true,
url: true,
id: true,
},
},
},
});
}
const serverDefaultId = await getServerSettingByKeyAsync(ctx.db, "search").then(
(setting) => setting.defaultSearchEngineId,
);
if (serverDefaultId) {
return await ctx.db.query.searchEngines.findFirst({
where: eq(searchEngines.id, serverDefaultId),
with: {
integration: {
columns: {
kind: true,
url: true,
id: true,
},
},
},
});
}
return null;
}),
search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
return await ctx.db.query.searchEngines.findMany({
where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),

View File

@@ -211,6 +211,7 @@ export const userRouter = createTRPCRouter({
homeBoardId: true,
firstDayOfWeek: true,
pingIconsEnabled: true,
defaultSearchEngineId: true,
}),
)
.meta({ openapi: { method: "GET", path: "/api/users/{userId}", tags: ["users"], protect: true } })
@@ -233,6 +234,7 @@ export const userRouter = createTRPCRouter({
homeBoardId: true,
firstDayOfWeek: true,
pingIconsEnabled: true,
defaultSearchEngineId: true,
},
where: eq(users.id, input.userId),
});
@@ -406,6 +408,43 @@ export const userRouter = createTRPCRouter({
})
.where(eq(users.id, input.userId));
}),
changeDefaultSearchEngine: protectedProcedure
.input(
convertIntersectionToZodObject(validation.user.changeDefaultSearchEngine.and(z.object({ userId: z.string() }))),
)
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/changeSearchEngine", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
const user = ctx.session.user;
// Only admins can change other users passwords
if (!user.permissions.includes("admin") && user.id !== input.userId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
await ctx.db
.update(users)
.set({
defaultSearchEngineId: input.defaultSearchEngineId,
})
.where(eq(users.id, input.userId));
}),
changeColorScheme: protectedProcedure
.input(validation.user.changeColorScheme)
.output(z.void())