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 = {}; 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; };