mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
179 lines
4.8 KiB
TypeScript
179 lines
4.8 KiB
TypeScript
import { nanoid } from "nanoid";
|
|
import { TFolderSchema } from "../models/folder";
|
|
|
|
type TAppendFolderDTO = {
|
|
folderName: string;
|
|
parentFolderId?: string;
|
|
};
|
|
|
|
type TRenameFolderDTO = {
|
|
folderName: string;
|
|
folderId: string;
|
|
};
|
|
|
|
export const validateFolderName = (folderName: string) => {
|
|
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
|
|
return validNameRegex.test(folderName);
|
|
};
|
|
|
|
export const generateFolderId = (): string => nanoid(12);
|
|
|
|
// simple bfs search
|
|
export const searchByFolderId = (
|
|
root: TFolderSchema,
|
|
folderId: string
|
|
): TFolderSchema | undefined => {
|
|
const queue = [root];
|
|
while (queue.length) {
|
|
const folder = queue.pop() as TFolderSchema;
|
|
if (folder.id === folderId) {
|
|
return folder;
|
|
}
|
|
queue.push(...folder.children);
|
|
}
|
|
};
|
|
|
|
// bfs and then append to the folder
|
|
const appendChild = (folders: TFolderSchema, folderName: string) => {
|
|
const folder = folders.children.find(({ name }) => name === folderName);
|
|
if (folder) {
|
|
throw new Error("Folder already exists");
|
|
}
|
|
const id = generateFolderId();
|
|
folders.version += 1;
|
|
folders.children.push({
|
|
id,
|
|
name: folderName,
|
|
children: [],
|
|
version: 1,
|
|
});
|
|
return { id, name: folderName };
|
|
};
|
|
|
|
// root of append child wrapper
|
|
export const appendFolder = (
|
|
folders: TFolderSchema,
|
|
{ folderName, parentFolderId }: TAppendFolderDTO
|
|
) => {
|
|
const isRoot = !parentFolderId;
|
|
|
|
if (isRoot) {
|
|
return appendChild(folders, folderName);
|
|
}
|
|
const folder = searchByFolderId(folders, parentFolderId);
|
|
if (!folder) {
|
|
throw new Error("Parent Folder not found");
|
|
}
|
|
return appendChild(folder, folderName);
|
|
};
|
|
|
|
export const renameFolder = (
|
|
folders: TFolderSchema,
|
|
{ folderName, folderId }: TRenameFolderDTO
|
|
) => {
|
|
const folder = searchByFolderId(folders, folderId);
|
|
if (!folder) {
|
|
throw new Error("Folder doesn't exist");
|
|
}
|
|
|
|
folder.name = folderName;
|
|
};
|
|
|
|
// bfs but stops on parent folder
|
|
// Then unmount the required child and then return both
|
|
export const deleteFolderById = (folders: TFolderSchema, folderId: string) => {
|
|
const queue = [folders];
|
|
while (queue.length) {
|
|
const folder = queue.pop() as TFolderSchema;
|
|
folder.version += 1;
|
|
const index = folder.children.findIndex(({ id }) => folderId === id);
|
|
if (index !== -1) {
|
|
const deletedFolder = folder.children.splice(index, 1);
|
|
return { deletedNode: deletedFolder[0], parent: folder };
|
|
}
|
|
queue.push(...folder.children);
|
|
}
|
|
};
|
|
|
|
// bfs but return parent of the folderID
|
|
export const getParentFromFolderId = (
|
|
folders: TFolderSchema,
|
|
folderId: string
|
|
) => {
|
|
const queue = [folders];
|
|
while (queue.length) {
|
|
const folder = queue.pop() as TFolderSchema;
|
|
const index = folder.children.findIndex(({ id }) => folderId === id);
|
|
if (index !== -1) return folder;
|
|
|
|
queue.push(...folder.children);
|
|
}
|
|
};
|
|
|
|
// to get all folders ids from everything from below nodes
|
|
export const getAllFolderIds = (folders: TFolderSchema) => {
|
|
const folderIds: Array<{ id: string; name: string }> = [];
|
|
const queue = [folders];
|
|
while (queue.length) {
|
|
const folder = queue.pop() as TFolderSchema;
|
|
folderIds.push({ id: folder.id, name: folder.name });
|
|
queue.push(...folder.children);
|
|
}
|
|
return folderIds;
|
|
};
|
|
|
|
// To get the path of a folder from the root. Used for breadcrumbs
|
|
// LOGIC: We do dfs instead if bfs
|
|
// Each time we go down we record the current node
|
|
// We then record the number of childs of each root node
|
|
// When we reach leaf node or when all childs of a root node are visited
|
|
// We remove it from path recorded by using the total child record
|
|
export const searchByFolderIdWithDir = (
|
|
folders: TFolderSchema,
|
|
folderId: string
|
|
) => {
|
|
const stack = [folders];
|
|
const dir: Array<{ id: string; name: string }> = [];
|
|
const hits: Record<string, number> = {};
|
|
|
|
while (stack.length) {
|
|
const folder = stack.shift() as TFolderSchema;
|
|
// score the hit
|
|
hits[folder.id] = folder.children.length;
|
|
const parent = dir[dir.length - 1];
|
|
if (parent) hits[parent.id] -= 1;
|
|
|
|
if (folder.id === folderId) {
|
|
dir.push({ name: folder.name, id: folder.id });
|
|
return { folder, dir };
|
|
}
|
|
|
|
if (folder.children.length) {
|
|
dir.push({ name: folder.name, id: folder.id });
|
|
stack.unshift(...folder.children);
|
|
} else {
|
|
if (!hits[parent.id]) {
|
|
dir.pop();
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
};
|
|
|
|
// to get folder of a path given
|
|
// Like /frontend/folder#1
|
|
export const getFolderByPath = (folders: TFolderSchema, searchPath: string) => {
|
|
const path = searchPath.split("/").filter(Boolean);
|
|
const queue = [folders];
|
|
let segment: TFolderSchema | undefined;
|
|
while (queue.length && path.length) {
|
|
const folder = queue.pop();
|
|
const segmentPath = path.shift();
|
|
segment = folder?.children.find(({ name }) => name === segmentPath);
|
|
if (!segment) return;
|
|
|
|
queue.push(segment);
|
|
}
|
|
return segment;
|
|
};
|