diff --git a/src/components/layout/header/Search.tsx b/src/components/layout/header/Search.tsx index 40d6202cb..1327d617e 100644 --- a/src/components/layout/header/Search.tsx +++ b/src/components/layout/header/Search.tsx @@ -13,10 +13,9 @@ import { import { useDebouncedValue, useHotkeys } from '@mantine/hooks'; import { showNotification } from '@mantine/notifications'; import { IconBrandYoutube, IconDownload, IconMovie, IconSearch } from '@tabler/icons-react'; -import { useQuery } from '@tanstack/react-query'; -import axios from 'axios'; import { useTranslation } from 'next-i18next'; import React, { forwardRef, useEffect, useRef, useState } from 'react'; +import { api } from '~/utils/api'; import { useConfigContext } from '../../../config/provider'; import { OverseerrMediaDisplay } from '../../../modules/common'; import { IModule } from '../../../modules/ModuleTypes'; @@ -141,26 +140,12 @@ export function Search() { const openTarget = getOpenTarget(config); const [opened, setOpened] = useState(false); - const { - data: OverseerrResults, - isLoading, - error, - } = useQuery( - ['overseerr', debounced], - async () => { - const res = await axios.get(`/api/modules/overseerr?query=${debounced}`); - return res.data.results ?? []; - }, - { - enabled: - isOverseerrEnabled === true && - selectedSearchEngine.value === 'overseerr' && - debounced.length > 3, - refetchOnWindowFocus: false, - refetchOnMount: false, - refetchInterval: false, - } - ); + const isOverseerrSearchEnabled = + isOverseerrEnabled === true && + selectedSearchEngine.value === 'overseerr' && + debounced.length > 3; + + const { data: overseerrResults } = useOverseerrSearchQuery(debounced, isOverseerrSearchEnabled); const isModuleEnabled = config?.settings.customization.layout.enabledSearchbar; if (!isModuleEnabled) { @@ -173,7 +158,7 @@ export function Search() { 0 && opened && searchQuery.length > 3) ?? + (overseerrResults && overseerrResults.length > 0 && opened && searchQuery.length > 3) ?? false } position="bottom" @@ -224,11 +209,11 @@ export function Search() { - {OverseerrResults && - OverseerrResults.slice(0, 4).map((result: any, index: number) => ( + {overseerrResults && + overseerrResults.slice(0, 4).map((result: any, index: number) => ( - {index < OverseerrResults.length - 1 && index < 3 && ( + {index < overseerrResults.length - 1 && index < 3 && ( )} @@ -312,3 +297,19 @@ const getOpenTarget = (config: ConfigType | undefined): '_blank' | '_self' => { return config.settings.common.searchEngine.properties.openInNewTab ? '_blank' : '_self'; }; + +const useOverseerrSearchQuery = (query: string, isEnabled: boolean) => { + const { name: configName } = useConfigContext(); + return api.overseerr.all.useQuery( + { + query, + configName: configName!, + }, + { + enabled: isEnabled, + refetchOnWindowFocus: false, + refetchOnMount: false, + refetchInterval: false, + } + ); +}; diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 9d94f4de4..3dd246da3 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -9,6 +9,7 @@ import { dnsHoleRouter } from './routers/dns-hole'; import { downloadRouter } from './routers/download'; import { mediaRequestsRouter } from './routers/media-request'; import { mediaServerRouter } from './routers/media-server'; +import { overseerrRouter } from './routers/overseerr'; /** * This is the primary router for your server. @@ -26,6 +27,7 @@ export const rootRouter = createTRPCRouter({ download: downloadRouter, mediaRequest: mediaRequestsRouter, mediaServer: mediaServerRouter, + overseerr: overseerrRouter, }); // export type definition of API diff --git a/src/server/api/routers/overseerr.ts b/src/server/api/routers/overseerr.ts new file mode 100644 index 000000000..67e117ed4 --- /dev/null +++ b/src/server/api/routers/overseerr.ts @@ -0,0 +1,44 @@ +import { TRPCError } from '@trpc/server'; +import axios from 'axios'; +import { z } from 'zod'; +import { getConfig } from '~/tools/config/getConfig'; +import { createTRPCRouter, publicProcedure } from '../trpc'; + +export const overseerrRouter = createTRPCRouter({ + all: publicProcedure + .input( + z.object({ + configName: z.string(), + query: z.string().or(z.undefined()), + }) + ) + .query(async ({ input }) => { + const config = getConfig(input.configName); + + const app = config.apps.find( + (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr' + ); + + if (input.query === '' || input.query === undefined) { + return []; + } + + const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value; + if (!app || !apiKey) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'Wrong request', + }); + } + const appUrl = new URL(app.url); + const data = await axios + .get(`${appUrl.origin}/api/v1/search?query=${input.query}`, { + headers: { + // Set X-Api-Key to the value of the API key + 'X-Api-Key': apiKey, + }, + }) + .then((res) => res.data); + return data; + }), +});