mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
🏗️ Migrate overseerr search to tRPC
This commit is contained in:
@@ -13,10 +13,9 @@ import {
|
|||||||
import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
|
import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconBrandYoutube, IconDownload, IconMovie, IconSearch } from '@tabler/icons-react';
|
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 { useTranslation } from 'next-i18next';
|
||||||
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
import { useConfigContext } from '../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { OverseerrMediaDisplay } from '../../../modules/common';
|
import { OverseerrMediaDisplay } from '../../../modules/common';
|
||||||
import { IModule } from '../../../modules/ModuleTypes';
|
import { IModule } from '../../../modules/ModuleTypes';
|
||||||
@@ -141,26 +140,12 @@ export function Search() {
|
|||||||
const openTarget = getOpenTarget(config);
|
const openTarget = getOpenTarget(config);
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
const {
|
const isOverseerrSearchEnabled =
|
||||||
data: OverseerrResults,
|
isOverseerrEnabled === true &&
|
||||||
isLoading,
|
selectedSearchEngine.value === 'overseerr' &&
|
||||||
error,
|
debounced.length > 3;
|
||||||
} = useQuery(
|
|
||||||
['overseerr', debounced],
|
const { data: overseerrResults } = useOverseerrSearchQuery(debounced, isOverseerrSearchEnabled);
|
||||||
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 isModuleEnabled = config?.settings.customization.layout.enabledSearchbar;
|
const isModuleEnabled = config?.settings.customization.layout.enabledSearchbar;
|
||||||
if (!isModuleEnabled) {
|
if (!isModuleEnabled) {
|
||||||
@@ -173,7 +158,7 @@ export function Search() {
|
|||||||
<Box style={{ width: '100%', maxWidth: 400 }}>
|
<Box style={{ width: '100%', maxWidth: 400 }}>
|
||||||
<Popover
|
<Popover
|
||||||
opened={
|
opened={
|
||||||
(OverseerrResults && OverseerrResults.length > 0 && opened && searchQuery.length > 3) ??
|
(overseerrResults && overseerrResults.length > 0 && opened && searchQuery.length > 3) ??
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
@@ -224,11 +209,11 @@ export function Search() {
|
|||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
<ScrollArea style={{ height: '80vh', maxWidth: '90vw' }} offsetScrollbars>
|
<ScrollArea style={{ height: '80vh', maxWidth: '90vw' }} offsetScrollbars>
|
||||||
{OverseerrResults &&
|
{overseerrResults &&
|
||||||
OverseerrResults.slice(0, 4).map((result: any, index: number) => (
|
overseerrResults.slice(0, 4).map((result: any, index: number) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<OverseerrMediaDisplay key={result.id} media={result} />
|
<OverseerrMediaDisplay key={result.id} media={result} />
|
||||||
{index < OverseerrResults.length - 1 && index < 3 && (
|
{index < overseerrResults.length - 1 && index < 3 && (
|
||||||
<Divider variant="dashed" my="xs" />
|
<Divider variant="dashed" my="xs" />
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@@ -312,3 +297,19 @@ const getOpenTarget = (config: ConfigType | undefined): '_blank' | '_self' => {
|
|||||||
|
|
||||||
return config.settings.common.searchEngine.properties.openInNewTab ? '_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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { dnsHoleRouter } from './routers/dns-hole';
|
|||||||
import { downloadRouter } from './routers/download';
|
import { downloadRouter } from './routers/download';
|
||||||
import { mediaRequestsRouter } from './routers/media-request';
|
import { mediaRequestsRouter } from './routers/media-request';
|
||||||
import { mediaServerRouter } from './routers/media-server';
|
import { mediaServerRouter } from './routers/media-server';
|
||||||
|
import { overseerrRouter } from './routers/overseerr';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -26,6 +27,7 @@ export const rootRouter = createTRPCRouter({
|
|||||||
download: downloadRouter,
|
download: downloadRouter,
|
||||||
mediaRequest: mediaRequestsRouter,
|
mediaRequest: mediaRequestsRouter,
|
||||||
mediaServer: mediaServerRouter,
|
mediaServer: mediaServerRouter,
|
||||||
|
overseerr: overseerrRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
44
src/server/api/routers/overseerr.ts
Normal file
44
src/server/api/routers/overseerr.ts
Normal file
@@ -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;
|
||||||
|
}),
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user