feat: #1047 add overseerr search (#1411)

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Manuel
2024-11-08 09:43:25 +01:00
committed by GitHub
parent 2a7d648049
commit aa503992af
25 changed files with 3661 additions and 52 deletions

View File

@@ -1,5 +1,6 @@
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";
@@ -12,7 +13,14 @@ import {
integrationUserPermissions,
} from "@homarr/db/schema/sqlite";
import type { IntegrationSecretKind } from "@homarr/definitions";
import { getPermissionsWithParents, integrationKinds, integrationSecretKindObject } from "@homarr/definitions";
import {
getPermissionsWithParents,
integrationDefs,
integrationKinds,
integrationSecretKindObject,
isIntegrationWithSearchSupport,
} from "@homarr/definitions";
import { integrationCreatorFromSecrets } from "@homarr/integrations";
import { validation, z } from "@homarr/validation";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
@@ -62,6 +70,54 @@ export const integrationRouter = createTRPCRouter({
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
);
}),
allThatSupportSearch: publicProcedure.query(async ({ ctx }) => {
const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({
where: eq(groupMembers.userId, ctx.session?.user.id ?? ""),
});
const integrationsFromDb = await ctx.db.query.integrations.findMany({
with: {
userPermissions: {
where: eq(integrationUserPermissions.userId, ctx.session?.user.id ?? ""),
},
groupPermissions: {
where: inArray(
integrationGroupPermissions.groupId,
groupsOfCurrentUser.map((group) => group.groupId),
),
},
},
where: inArray(
integrations.kind,
objectEntries(integrationDefs)
.filter(([_, integration]) => integration.supportsSearch)
.map(([kind, _]) => kind),
),
});
return integrationsFromDb
.map((integration) => {
const permissions = integration.userPermissions
.map(({ permission }) => permission)
.concat(integration.groupPermissions.map(({ permission }) => permission));
return {
id: integration.id,
name: integration.name,
kind: integration.kind,
url: integration.url,
permissions: {
hasUseAccess:
permissions.includes("use") || permissions.includes("interact") || permissions.includes("full"),
hasInteractAccess: permissions.includes("interact") || permissions.includes("full"),
hasFullAccess: permissions.includes("full"),
},
};
})
.sort(
(integrationA, integrationB) =>
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
);
}),
search: protectedProcedure
.input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) }))
.query(async ({ ctx, input }) => {
@@ -326,6 +382,33 @@ export const integrationRouter = createTRPCRouter({
);
});
}),
searchInIntegration: protectedProcedure
.input(z.object({ integrationId: z.string(), query: z.string() }))
.query(async ({ ctx, input }) => {
const integration = await ctx.db.query.integrations.findFirst({
where: eq(integrations.id, input.integrationId),
with: {
secrets: true,
},
});
if (!integration) {
throw new TRPCError({
code: "NOT_FOUND",
message: "The requested integration does not exist",
});
}
if (!isIntegrationWithSearchSupport(integration)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "The requested integration does not support searching",
});
}
const integrationInstance = integrationCreatorFromSecrets(integration);
return await integrationInstance.searchAsync(input.query);
}),
});
interface UpdateSecretInput {

View File

@@ -39,7 +39,19 @@ export const searchEngineRouter = createTRPCRouter({
});
}
return searchEngine;
return searchEngine.type === "fromIntegration"
? {
...searchEngine,
type: "fromIntegration" as const,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
integrationId: searchEngine.integrationId!,
}
: {
...searchEngine,
type: "generic" as const,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
urlTemplate: searchEngine.urlTemplate!,
};
}),
search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
return await ctx.db.query.searchEngines.findMany({
@@ -53,8 +65,10 @@ export const searchEngineRouter = createTRPCRouter({
name: input.name,
short: input.short.toLowerCase(),
iconUrl: input.iconUrl,
urlTemplate: input.urlTemplate,
urlTemplate: "urlTemplate" in input ? input.urlTemplate : null,
description: input.description,
type: input.type,
integrationId: "integrationId" in input ? input.integrationId : null,
});
}),
update: protectedProcedure.input(validation.searchEngine.edit).mutation(async ({ ctx, input }) => {
@@ -74,8 +88,10 @@ export const searchEngineRouter = createTRPCRouter({
.set({
name: input.name,
iconUrl: input.iconUrl,
urlTemplate: input.urlTemplate,
urlTemplate: "urlTemplate" in input ? input.urlTemplate : null,
description: input.description,
integrationId: "integrationId" in input ? input.integrationId : null,
type: input.type,
})
.where(eq(searchEngines.id, input.id));
}),