refactor: Move integration search flag to categories (#1637)
This commit is contained in:
@@ -14,15 +14,16 @@ import {
|
|||||||
} from "@homarr/db/schema/sqlite";
|
} from "@homarr/db/schema/sqlite";
|
||||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||||
import {
|
import {
|
||||||
|
getIntegrationKindsByCategory,
|
||||||
getPermissionsWithParents,
|
getPermissionsWithParents,
|
||||||
integrationDefs,
|
integrationDefs,
|
||||||
integrationKinds,
|
integrationKinds,
|
||||||
integrationSecretKindObject,
|
integrationSecretKindObject,
|
||||||
isIntegrationWithSearchSupport,
|
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import { integrationCreatorFromSecrets } from "@homarr/integrations";
|
import { integrationCreator } from "@homarr/integrations";
|
||||||
import { validation, z } from "@homarr/validation";
|
import { validation, z } from "@homarr/validation";
|
||||||
|
|
||||||
|
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||||
import { throwIfActionForbiddenAsync } from "./integration-access";
|
import { throwIfActionForbiddenAsync } from "./integration-access";
|
||||||
import { testConnectionAsync } from "./integration-test-connection";
|
import { testConnectionAsync } from "./integration-test-connection";
|
||||||
@@ -90,7 +91,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
where: inArray(
|
where: inArray(
|
||||||
integrations.kind,
|
integrations.kind,
|
||||||
objectEntries(integrationDefs)
|
objectEntries(integrationDefs)
|
||||||
.filter(([_, integration]) => integration.supportsSearch)
|
.filter(([_, integration]) => [...integration.category].includes("search"))
|
||||||
.map(([kind, _]) => kind),
|
.map(([kind, _]) => kind),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -383,31 +384,11 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
searchInIntegration: protectedProcedure
|
searchInIntegration: protectedProcedure
|
||||||
|
.unstable_concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("search")))
|
||||||
.input(z.object({ integrationId: z.string(), query: z.string() }))
|
.input(z.object({ integrationId: z.string(), query: z.string() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const integration = await ctx.db.query.integrations.findFirst({
|
const integrationInstance = integrationCreator(ctx.integration);
|
||||||
where: eq(integrations.id, input.integrationId),
|
return await integrationInstance.searchAsync(encodeURI(input.query));
|
||||||
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);
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ interface integrationDefinition {
|
|||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
secretKinds: AtLeastOneOf<IntegrationSecretKind[]>; // at least one secret kind set is required
|
secretKinds: AtLeastOneOf<IntegrationSecretKind[]>; // at least one secret kind set is required
|
||||||
category: AtLeastOneOf<IntegrationCategory>;
|
category: AtLeastOneOf<IntegrationCategory>;
|
||||||
supportsSearch: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const integrationDefs = {
|
export const integrationDefs = {
|
||||||
@@ -23,140 +22,120 @@ export const integrationDefs = {
|
|||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sabnzbd.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sabnzbd.png",
|
||||||
category: ["downloadClient", "usenet"],
|
category: ["downloadClient", "usenet"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
nzbGet: {
|
nzbGet: {
|
||||||
name: "NZBGet",
|
name: "NZBGet",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/nzbget.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/nzbget.png",
|
||||||
category: ["downloadClient", "usenet"],
|
category: ["downloadClient", "usenet"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
deluge: {
|
deluge: {
|
||||||
name: "Deluge",
|
name: "Deluge",
|
||||||
secretKinds: [["password"]],
|
secretKinds: [["password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/deluge.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/deluge.png",
|
||||||
category: ["downloadClient", "torrent"],
|
category: ["downloadClient", "torrent"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
transmission: {
|
transmission: {
|
||||||
name: "Transmission",
|
name: "Transmission",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/transmission.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/transmission.png",
|
||||||
category: ["downloadClient", "torrent"],
|
category: ["downloadClient", "torrent"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
qBittorrent: {
|
qBittorrent: {
|
||||||
name: "qBittorrent",
|
name: "qBittorrent",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/qbittorrent.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/qbittorrent.png",
|
||||||
category: ["downloadClient", "torrent"],
|
category: ["downloadClient", "torrent"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
sonarr: {
|
sonarr: {
|
||||||
name: "Sonarr",
|
name: "Sonarr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sonarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sonarr.png",
|
||||||
category: ["calendar"],
|
category: ["calendar"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
radarr: {
|
radarr: {
|
||||||
name: "Radarr",
|
name: "Radarr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/radarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/radarr.png",
|
||||||
category: ["calendar"],
|
category: ["calendar"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
lidarr: {
|
lidarr: {
|
||||||
name: "Lidarr",
|
name: "Lidarr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/lidarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/lidarr.png",
|
||||||
category: ["calendar"],
|
category: ["calendar"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
readarr: {
|
readarr: {
|
||||||
name: "Readarr",
|
name: "Readarr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png",
|
||||||
category: ["calendar"],
|
category: ["calendar"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
prowlarr: {
|
prowlarr: {
|
||||||
name: "Prowlarr",
|
name: "Prowlarr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prowlarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prowlarr.png",
|
||||||
category: ["indexerManager"],
|
category: ["indexerManager"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
jellyfin: {
|
jellyfin: {
|
||||||
name: "Jellyfin",
|
name: "Jellyfin",
|
||||||
secretKinds: [["username", "password"], ["apiKey"]],
|
secretKinds: [["username", "password"], ["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyfin.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyfin.png",
|
||||||
category: ["mediaService"],
|
category: ["mediaService"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
name: "Plex",
|
name: "Plex",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/plex.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/plex.png",
|
||||||
category: ["mediaService"],
|
category: ["mediaService"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
jellyseerr: {
|
jellyseerr: {
|
||||||
name: "Jellyseerr",
|
name: "Jellyseerr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyseerr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyseerr.png",
|
||||||
category: ["mediaSearch", "mediaRequest"],
|
category: ["mediaSearch", "mediaRequest", "search"],
|
||||||
supportsSearch: true,
|
|
||||||
},
|
},
|
||||||
overseerr: {
|
overseerr: {
|
||||||
name: "Overseerr",
|
name: "Overseerr",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/overseerr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/overseerr.png",
|
||||||
category: ["mediaSearch", "mediaRequest"],
|
category: ["mediaSearch", "mediaRequest", "search"],
|
||||||
supportsSearch: true,
|
|
||||||
},
|
},
|
||||||
piHole: {
|
piHole: {
|
||||||
name: "Pi-hole",
|
name: "Pi-hole",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/pi-hole.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/pi-hole.png",
|
||||||
category: ["dnsHole"],
|
category: ["dnsHole"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
adGuardHome: {
|
adGuardHome: {
|
||||||
name: "AdGuard Home",
|
name: "AdGuard Home",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/adguard-home.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/adguard-home.png",
|
||||||
category: ["dnsHole"],
|
category: ["dnsHole"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
homeAssistant: {
|
homeAssistant: {
|
||||||
name: "Home Assistant",
|
name: "Home Assistant",
|
||||||
secretKinds: [["apiKey"]],
|
secretKinds: [["apiKey"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/home-assistant.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/home-assistant.png",
|
||||||
category: ["smartHomeServer"],
|
category: ["smartHomeServer"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
openmediavault: {
|
openmediavault: {
|
||||||
name: "OpenMediaVault",
|
name: "OpenMediaVault",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/openmediavault.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/openmediavault.png",
|
||||||
category: ["healthMonitoring"],
|
category: ["healthMonitoring"],
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
dashDot: {
|
dashDot: {
|
||||||
name: "Dash.",
|
name: "Dash.",
|
||||||
secretKinds: [[]],
|
secretKinds: [[]],
|
||||||
category: ["healthMonitoring"],
|
category: ["healthMonitoring"],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/dashdot.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/dashdot.png",
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
tdarr: {
|
tdarr: {
|
||||||
name: "Tdarr",
|
name: "Tdarr",
|
||||||
secretKinds: [[]],
|
secretKinds: [[]],
|
||||||
category: ["mediaTranscoding"],
|
category: ["mediaTranscoding"],
|
||||||
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/tdarr.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/tdarr.png",
|
||||||
supportsSearch: false,
|
|
||||||
},
|
},
|
||||||
} as const satisfies Record<string, integrationDefinition>;
|
} as const satisfies Record<string, integrationDefinition>;
|
||||||
|
|
||||||
@@ -195,22 +174,6 @@ export type IntegrationKindByCategory<TCategory extends IntegrationCategory> = {
|
|||||||
U
|
U
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if search is supported by the integration
|
|
||||||
* Uses a typescript guard with is to allow only integrations with search support within if statement
|
|
||||||
* @param integration integration with kind
|
|
||||||
* @returns true if the integration supports search
|
|
||||||
*/
|
|
||||||
export const isIntegrationWithSearchSupport = (integration: {
|
|
||||||
kind: IntegrationKind;
|
|
||||||
}): integration is { kind: IntegrationWithSearchSupport } => {
|
|
||||||
return integrationDefs[integration.kind].supportsSearch;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IntegrationWithSearchSupport = {
|
|
||||||
[Key in keyof typeof integrationDefs]: true extends (typeof integrationDefs)[Key]["supportsSearch"] ? Key : never;
|
|
||||||
}[keyof typeof integrationDefs];
|
|
||||||
|
|
||||||
export type IntegrationSecretKind = keyof typeof integrationSecretKindObject;
|
export type IntegrationSecretKind = keyof typeof integrationSecretKindObject;
|
||||||
export type IntegrationKind = keyof typeof integrationDefs;
|
export type IntegrationKind = keyof typeof integrationDefs;
|
||||||
export type IntegrationCategory =
|
export type IntegrationCategory =
|
||||||
@@ -225,4 +188,5 @@ export type IntegrationCategory =
|
|||||||
| "smartHomeServer"
|
| "smartHomeServer"
|
||||||
| "indexerManager"
|
| "indexerManager"
|
||||||
| "healthMonitoring"
|
| "healthMonitoring"
|
||||||
|
| "search"
|
||||||
| "mediaTranscoding";
|
| "mediaTranscoding";
|
||||||
|
|||||||
Reference in New Issue
Block a user