mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 06:34:57 +00:00
feature: server-side pagination/filtering/sorting for secrets overview and main pages
This commit is contained in:
@@ -5,6 +5,8 @@ import { TableName, TProjectEnvironments, TSecretFolders, TSecretFoldersUpdate }
|
||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||
import { groupBy, removeTrailingSlash } from "@app/lib/fn";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
|
||||
export const validateFolderName = (folderName: string) => {
|
||||
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
|
||||
@@ -83,7 +85,7 @@ const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: str
|
||||
.from<TSecretFolders & { depth: number; path: string }>("parent");
|
||||
};
|
||||
|
||||
const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environment: string, secretPath: string) => {
|
||||
const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: string[], secretPath: string) => {
|
||||
// this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2
|
||||
const formatedPath = secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath;
|
||||
// next goal to sanitize saw the raw sql query is safe
|
||||
@@ -111,7 +113,7 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environment: stri
|
||||
projectId,
|
||||
parentId: null
|
||||
})
|
||||
.where(`${TableName.Environment}.slug`, environment)
|
||||
.whereIn(`${TableName.Environment}.slug`, environments)
|
||||
.select(selectAllTableCols(TableName.SecretFolder))
|
||||
.union(
|
||||
(qb) =>
|
||||
@@ -139,14 +141,14 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environment: stri
|
||||
.from<TSecretFolders & { depth: number; path: string }>("parent")
|
||||
.leftJoin<TProjectEnvironments>(TableName.Environment, `${TableName.Environment}.id`, "parent.envId")
|
||||
.select<
|
||||
TSecretFolders & {
|
||||
(TSecretFolders & {
|
||||
depth: number;
|
||||
path: string;
|
||||
envId: string;
|
||||
envSlug: string;
|
||||
envName: string;
|
||||
projectId: string;
|
||||
}
|
||||
})[]
|
||||
>(
|
||||
selectAllTableCols("parent" as TableName.SecretFolder),
|
||||
db.ref("id").withSchema(TableName.Environment).as("envId"),
|
||||
@@ -214,7 +216,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
const folder = await sqlFindFolderByPathQuery(
|
||||
tx || db.replicaNode(),
|
||||
projectId,
|
||||
environment,
|
||||
[environment],
|
||||
removeTrailingSlash(path)
|
||||
)
|
||||
.orderBy("depth", "desc")
|
||||
@@ -230,6 +232,35 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// finds folders by path for multiple envs
|
||||
const findBySecretPathMultiEnv = async (projectId: string, environments: string[], path: string, tx?: Knex) => {
|
||||
try {
|
||||
const pathDepth = removeTrailingSlash(path).split("/").filter(Boolean).length + 1;
|
||||
|
||||
const folders = await sqlFindFolderByPathQuery(
|
||||
tx || db.replicaNode(),
|
||||
projectId,
|
||||
environments,
|
||||
removeTrailingSlash(path)
|
||||
)
|
||||
.orderBy("depth", "desc")
|
||||
.where("depth", pathDepth);
|
||||
|
||||
const firstFolder = folders[0];
|
||||
|
||||
if (firstFolder && firstFolder.path !== removeTrailingSlash(path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return folders.map((folder) => {
|
||||
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
|
||||
return { ...el, envId: id, environment: { id, name, slug } };
|
||||
});
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find folders by secret path multi env" });
|
||||
}
|
||||
};
|
||||
|
||||
// used in folder creation
|
||||
// even if its the original given /path1/path2
|
||||
// it will stop automatically at /path2
|
||||
@@ -238,7 +269,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
const folder = await sqlFindFolderByPathQuery(
|
||||
tx || db.replicaNode(),
|
||||
projectId,
|
||||
environment,
|
||||
[environment],
|
||||
removeTrailingSlash(path)
|
||||
)
|
||||
.orderBy("depth", "desc")
|
||||
@@ -352,14 +383,71 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// find project folders for multiple envs
|
||||
const findByMultiEnv = async (
|
||||
{
|
||||
environmentIds,
|
||||
parentIds,
|
||||
search,
|
||||
limit,
|
||||
offset = 0,
|
||||
orderBy = SecretsOrderBy.Name,
|
||||
orderDirection = OrderByDirection.ASC
|
||||
}: {
|
||||
environmentIds: string[];
|
||||
parentIds: string[];
|
||||
search?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
orderBy?: SecretsOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
},
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const query = (tx || db.replicaNode())(TableName.SecretFolder)
|
||||
.whereIn("parentId", parentIds)
|
||||
.whereIn("envId", environmentIds)
|
||||
.where("isReserved", false)
|
||||
.where((bd) => {
|
||||
if (search) {
|
||||
void bd.whereILike("name", `%${search}%`);
|
||||
}
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretFolder),
|
||||
db.raw(`DENSE_RANK() OVER (ORDER BY "name" ${orderDirection ?? OrderByDirection.ASC}) as rank`)
|
||||
)
|
||||
.orderBy(orderBy, orderDirection);
|
||||
|
||||
if (limit) {
|
||||
const rankOffset = offset + 1; // ranks start from 1
|
||||
return await (tx || db)
|
||||
.with("w", query)
|
||||
.select("*")
|
||||
.from<Awaited<typeof query>[number]>("w")
|
||||
.where("w.rank", ">=", rankOffset)
|
||||
.andWhere("w.rank", "<", rankOffset + limit);
|
||||
}
|
||||
|
||||
const folders = await query;
|
||||
|
||||
return folders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find folders multi env" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretFolderOrm,
|
||||
update,
|
||||
findBySecretPath,
|
||||
findBySecretPathMultiEnv,
|
||||
findById,
|
||||
findByManySecretPath,
|
||||
findSecretPathByFolderIds,
|
||||
findClosestFolder,
|
||||
findByProjectId
|
||||
findByProjectId,
|
||||
findByMultiEnv
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user