mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 15:40:47 +00:00
feat: completed api for new search identities
This commit is contained in:
@@ -66,6 +66,17 @@ export const IDENTITIES = {
|
||||
},
|
||||
LIST: {
|
||||
orgId: "The ID of the organization to list identities."
|
||||
},
|
||||
SEARCH: {
|
||||
search: {
|
||||
desc: "The filters to apply to the search.",
|
||||
name: "The name of the identity to filter by.",
|
||||
role: "The organizational role of the identity to filter by."
|
||||
},
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th identity.",
|
||||
limit: "The number of identities to return.",
|
||||
orderBy: "The column to order identities by.",
|
||||
orderDirection: "The direction to order identities in."
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SearchResourceOperators, TSearchResourceOperator } from "./search";
|
||||
|
||||
const buildKnexQuery = (
|
||||
query: Knex.QueryBuilder,
|
||||
// when it's multiple table field means it's field1 or field2
|
||||
fields: string | string[],
|
||||
operator: SearchResourceOperators,
|
||||
value: unknown
|
||||
) => {
|
||||
switch (operator) {
|
||||
case SearchResourceOperators.$eq: {
|
||||
if (typeof value !== "string" && typeof value !== "number")
|
||||
throw new Error("Invalid value type for $eq operator");
|
||||
|
||||
if (typeof fields === "string") {
|
||||
return void query.where(fields, "=", value);
|
||||
}
|
||||
|
||||
return void query.where((qb) => {
|
||||
return fields.forEach((el, index) => {
|
||||
if (index === 0) {
|
||||
return void qb.where(el, "=", value);
|
||||
}
|
||||
return void qb.orWhere(el, "=", value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
case SearchResourceOperators.$neq: {
|
||||
if (typeof value !== "string" && typeof value !== "number")
|
||||
throw new Error("Invalid value type for $neq operator");
|
||||
|
||||
if (typeof fields === "string") {
|
||||
return void query.where(fields, "<>", value);
|
||||
}
|
||||
|
||||
return void query.where((qb) => {
|
||||
return fields.forEach((el, index) => {
|
||||
if (index === 0) {
|
||||
return void qb.where(el, "<>", value);
|
||||
}
|
||||
return void qb.orWhere(el, "<>", value);
|
||||
});
|
||||
});
|
||||
}
|
||||
case SearchResourceOperators.$in: {
|
||||
if (!Array.isArray(value)) throw new Error("Invalid value type for $in operator");
|
||||
|
||||
if (typeof fields === "string") {
|
||||
return void query.whereIn(fields, value);
|
||||
}
|
||||
|
||||
return void query.where((qb) => {
|
||||
return fields.forEach((el, index) => {
|
||||
if (index === 0) {
|
||||
return void qb.whereIn(el, value);
|
||||
}
|
||||
return void qb.orWhereIn(el, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
case SearchResourceOperators.$contains: {
|
||||
if (typeof value !== "string") throw new Error("Invalid value type for $contains operator");
|
||||
|
||||
if (typeof fields === "string") {
|
||||
return void query.whereILike(fields, `%${value}%`);
|
||||
}
|
||||
|
||||
return void query.where((qb) => {
|
||||
return fields.forEach((el, index) => {
|
||||
if (index === 0) {
|
||||
return void qb.whereILike(el, `%${value}%`);
|
||||
}
|
||||
return void qb.orWhereILike(el, `%${value}%`);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const buildKnexFilterForSearchResource = <T extends { [K: string]: TSearchResourceOperator }, K extends keyof T>(
|
||||
rootQuery: Knex.QueryBuilder,
|
||||
searchFilter: T & { $or?: T[] },
|
||||
getAttributeField: (attr: K) => string | string[] | null
|
||||
) => {
|
||||
const { $or: orFilters = [] } = searchFilter;
|
||||
(Object.keys(searchFilter) as K[]).forEach((key) => {
|
||||
// akhilmhdh: yes, we could have split in top. This is done to satisfy ts type error
|
||||
if (key === "$or") return;
|
||||
|
||||
const dbField = getAttributeField(key);
|
||||
if (!dbField) throw new Error(`DB field not found for ${String(key)}`);
|
||||
|
||||
const dbValue = searchFilter[key];
|
||||
if (typeof dbValue === "string" || typeof dbValue === "number") {
|
||||
buildKnexQuery(rootQuery, dbField, SearchResourceOperators.$eq, dbValue);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(dbValue as Record<string, unknown>).forEach((el) => {
|
||||
buildKnexQuery(
|
||||
rootQuery,
|
||||
dbField,
|
||||
el as SearchResourceOperators,
|
||||
(dbValue as Record<SearchResourceOperators, unknown>)[el as SearchResourceOperators]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (orFilters.length) {
|
||||
void rootQuery.andWhere((andQb) => {
|
||||
return orFilters.forEach((orFilter) => {
|
||||
return void andQb.orWhere((qb) => {
|
||||
(Object.keys(orFilter) as K[]).forEach((key) => {
|
||||
const dbField = getAttributeField(key);
|
||||
if (!dbField) throw new Error(`DB field not found for ${String(key)}`);
|
||||
|
||||
const dbValue = orFilter[key];
|
||||
if (typeof dbValue === "string" || typeof dbValue === "number") {
|
||||
buildKnexQuery(qb, dbField, SearchResourceOperators.$eq, dbValue);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(dbValue as Record<string, unknown>).forEach((el) => {
|
||||
buildKnexQuery(
|
||||
qb,
|
||||
dbField,
|
||||
el as SearchResourceOperators,
|
||||
(dbValue as Record<SearchResourceOperators, unknown>)[el as SearchResourceOperators]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum SearchResourceOperators {
|
||||
$eq = "$eq",
|
||||
$neq = "$neq",
|
||||
$in = "$in",
|
||||
$contains = "$contains"
|
||||
}
|
||||
|
||||
export const SearchResourceOperatorSchema = z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z
|
||||
.object({
|
||||
[SearchResourceOperators.$eq]: z.string().optional(),
|
||||
[SearchResourceOperators.$neq]: z.string().optional(),
|
||||
[SearchResourceOperators.$in]: z.string().array().optional(),
|
||||
[SearchResourceOperators.$contains]: z.string().array().optional()
|
||||
})
|
||||
.partial()
|
||||
]);
|
||||
|
||||
export type TSearchResourceOperator = z.infer<typeof SearchResourceOperatorSchema>;
|
||||
|
||||
export type TSearchResource = {
|
||||
[k: string]: z.ZodOptional<
|
||||
z.ZodUnion<
|
||||
[
|
||||
z.ZodEffects<z.ZodString | z.ZodNumber>,
|
||||
z.ZodObject<{
|
||||
[SearchResourceOperators.$eq]?: z.ZodOptional<z.ZodEffects<z.ZodString | z.ZodNumber>>;
|
||||
[SearchResourceOperators.$neq]?: z.ZodOptional<z.ZodEffects<z.ZodString | z.ZodNumber>>;
|
||||
[SearchResourceOperators.$in]?: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString | z.ZodNumber>>>;
|
||||
[SearchResourceOperators.$contains]?: z.ZodOptional<z.ZodEffects<z.ZodString>>;
|
||||
}>
|
||||
]
|
||||
>
|
||||
>;
|
||||
};
|
||||
|
||||
export const buildSearchZodSchema = <T extends TSearchResource>(schema: z.ZodObject<T>) => {
|
||||
return schema.extend({ $or: schema.array().optional() }).optional();
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum CharacterType {
|
||||
Alphabets = "alphabets",
|
||||
Numbers = "numbers",
|
||||
@@ -101,3 +103,10 @@ export const characterValidator = (allowedCharacters: CharacterType[]) => {
|
||||
return regex.test(input);
|
||||
};
|
||||
};
|
||||
|
||||
export const zodValidateCharacters = (allowedCharacters: CharacterType[]) => {
|
||||
const validator = characterValidator(allowedCharacters);
|
||||
return (schema: z.ZodString, fieldName: string) => {
|
||||
return schema.refine(validator, { message: `${fieldName} can only contain ${allowedCharacters.join(",")}` });
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user