Skip to content

Commit

Permalink
fix: integration kind by category function to return at least one ele…
Browse files Browse the repository at this point in the history
…ment
  • Loading branch information
Meierschlumpf committed Aug 15, 2024
1 parent 653d2e4 commit ba63f27
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 11 deletions.
10 changes: 4 additions & 6 deletions packages/api/src/middlewares/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ export type IntegrationAction = "query" | "interact";
/**
* Creates a middleware that provides the integration in the context that is of the specified kinds
* @param action query for showing data or interact for mutating data
* @param kinds kinds of integrations that are supported, as kinds and/or array of kinds
* @param kinds kinds of integrations that are supported
* @returns middleware that can be used with trpc
* @example publicProcedure.unstable_concat(createOneIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @throws TRPCError NOT_FOUND if the integration was not found
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on the specified integration
*/
export const createOneIntegrationMiddleware = <TKind extends IntegrationKind>(
action: IntegrationAction,
...kindsOrArrayOfKinds: [TKind | TKind[], ...(TKind | TKind[])[]] // Ensure at least one kind is provided, and accept an array of kinds as well
...kinds: [TKind, ...TKind[]] // Ensure at least one kind is provided
) => {
const kinds = kindsOrArrayOfKinds.flat();
return publicProcedure.input(z.object({ integrationId: z.string() })).use(async ({ input, ctx, next }) => {
const integration = await ctx.db.query.integrations.findFirst({
where: and(eq(integrations.id, input.integrationId), inArray(integrations.kind, kinds)),
Expand Down Expand Up @@ -88,17 +87,16 @@ export const createOneIntegrationMiddleware = <TKind extends IntegrationKind>(
* Creates a middleware that provides the integrations in the context that are of the specified kinds and have the specified item
* It also ensures that the user has permission to perform the specified action on the integrations
* @param action query for showing data or interact for mutating data
* @param kinds kinds of integrations that are supported, as kinds and/or array of kinds
* @param kinds kinds of integrations that are supported
* @returns middleware that can be used with trpc
* @example publicProcedure.unstable_concat(createManyIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @throws TRPCError NOT_FOUND if the integration was not found
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on at least one of the specified integrations
*/
export const createManyIntegrationMiddleware = <TKind extends IntegrationKind>(
action: IntegrationAction,
...kindsOrArrayOfKinds: [TKind | TKind[], ...(TKind | TKind[])[]] // Ensure at least one kind is provided, and accept an array of kinds as well
...kinds: [TKind, ...TKind[]] // Ensure at least one kind is provided, and accept an array of kinds as well
) => {
const kinds = kindsOrArrayOfKinds.flat();
return publicProcedure
.input(z.object({ integrationIds: z.array(z.string()).min(1) }))
.use(async ({ ctx, input, next }) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/router/widgets/downloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { createManyIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";

const createDownloadClientIntegrationMiddleware = (action: IntegrationAction) =>
createManyIntegrationMiddleware(action, getIntegrationKindsByCategory("downloadClient"));
createManyIntegrationMiddleware(action, ...getIntegrationKindsByCategory("downloadClient"));

export const downloadsRouter = createTRPCRouter({
getJobsAndStatuses: publicProcedure
Expand Down
8 changes: 7 additions & 1 deletion packages/common/src/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export type MaybePromise<T> = T | Promise<T>;
export type MaybePromise<T> = T | Promise<T>;

export type IsUnion<T, U extends T = T> = (T extends unknown ? (U extends T ? false : true) : never) extends false
? false
: true;

export type UnionArrayWithAtLeastOneElement<T> = IsUnion<T> extends true ? [T, ...T[]] : [T];
8 changes: 5 additions & 3 deletions packages/definitions/src/integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { objectKeys } from "@homarr/common";
import type { UnionArrayWithAtLeastOneElement } from "@homarr/common/types";

export const integrationSecretKindObject = {
apiKey: { isPublic: false },
Expand Down Expand Up @@ -136,8 +137,8 @@ export type IntegrationKindByCategory<TCategory extends IntegrationCategory> = {
? Key
: never;
}[keyof typeof integrationDefs] extends infer U
//Needed to simplify the type when using it
? U
? //Needed to simplify the type when using it
U
: never;

/**
Expand All @@ -148,7 +149,8 @@ export type IntegrationKindByCategory<TCategory extends IntegrationCategory> = {
export const getIntegrationKindsByCategory = <TCategory extends IntegrationCategory>(category: TCategory) => {
return integrationKinds.filter((integration) =>
integrationDefs[integration].category.some((defCategory) => defCategory === category),
) as IntegrationKindByCategory<TCategory>[];
) as unknown as UnionArrayWithAtLeastOneElement<IntegrationKindByCategory<TCategory>>;
// The cast allows us to return a list with at least one element, which allows the usage with integration middlewares
};

export type IntegrationSecretKind = (typeof integrationSecretKinds)[number];
Expand Down

0 comments on commit ba63f27

Please sign in to comment.