From 2ef5aaef23de28e3c8e355780a94ec9f7defd302 Mon Sep 17 00:00:00 2001 From: subnub <44621867+subnub@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:11:36 -0500 Subject: [PATCH] started folder upload changes --- backend/controllers/folder-controller.ts | 34 +++++++++ backend/express-routers/folder-router.ts | 7 ++ .../services/chunk-service/chunk-service.ts | 17 ++++- .../utils/getFolderUploadBusboyData.ts | 62 ++++++++++++++++ src/api/foldersAPI.ts | 6 ++ .../AddNewDropdown/AddNewDropdown.tsx | 41 ++++++++++- src/hooks/files.ts | 72 ++++++++++++++++++- 7 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 backend/services/chunk-service/utils/getFolderUploadBusboyData.ts diff --git a/backend/controllers/folder-controller.ts b/backend/controllers/folder-controller.ts index 73888ce..d08c967 100644 --- a/backend/controllers/folder-controller.ts +++ b/backend/controllers/folder-controller.ts @@ -17,6 +17,12 @@ interface RequestType extends Request { encryptedToken?: string; } +interface RequestTypeFullUser extends Request { + user?: UserInterface; + encryptedToken?: string; + accessTokenStreamVideo?: string; +} + const chunkService = new ChunkService(); class FolderController { @@ -65,6 +71,34 @@ class FolderController { } }; + uploadFolder = async ( + req: RequestTypeFullUser, + res: Response, + next: NextFunction + ) => { + if (!req.user) { + return; + } + + try { + const user = req.user; + const busboy = req.busboy; + + console.log("upload folder request"); + + busboy.on("error", (e: Error) => { + console.log("busboy error", e); + // handleError(); + }); + + req.pipe(busboy); + + await chunkService.uploadFolder(user, busboy, req); + } catch (e) { + console.log("upload folder error", e); + } + }; + deleteAll = async (req: RequestType, res: Response, next: NextFunction) => { if (!req.user) { return; diff --git a/backend/express-routers/folder-router.ts b/backend/express-routers/folder-router.ts index 41b6b34..7a1553d 100644 --- a/backend/express-routers/folder-router.ts +++ b/backend/express-routers/folder-router.ts @@ -13,6 +13,7 @@ import { restoreFolderValidationRules, trashFolderValidationRules, } from "../middleware/folders/folder-middleware"; +import authFullUser from "../middleware/authFullUser"; const folderController = new FolderController(); const router = Router(); @@ -97,4 +98,10 @@ router.post( folderController.createFolder ); +router.post( + "/folder-service/upload", + authFullUser, + folderController.uploadFolder +); + export default router; diff --git a/backend/services/chunk-service/chunk-service.ts b/backend/services/chunk-service/chunk-service.ts index 7009218..8d5a28d 100644 --- a/backend/services/chunk-service/chunk-service.ts +++ b/backend/services/chunk-service/chunk-service.ts @@ -22,6 +22,7 @@ import UserDB from "../../db/mongoDB/userDB"; import fixEndChunkLength from "./utils/fixEndChunkLength"; import archiver from "archiver"; import async from "async"; +import getFolderBusboyData from "./utils/getFolderUploadBusboyData"; const fileDB = new FileDB(); const folderDB = new FolderDB(); @@ -129,7 +130,21 @@ class StorageService { }; }; - uploadFolder = async (user: UserInterface, busboy: any, req: Request) => {}; + uploadFolder = async (user: UserInterface, busboy: any, req: Request) => { + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const cipher = crypto.createCipheriv("aes256", CIPHER_KEY, initVect); + + const fileData = await getFolderBusboyData(busboy); + + console.log("file data", fileData); + }; downloadFile = async (user: UserInterface, fileID: string, res: Response) => { const currentFile = await fileDB.getFileInfo(fileID, user._id.toString()); diff --git a/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts b/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts new file mode 100644 index 0000000..8f7fa4d --- /dev/null +++ b/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts @@ -0,0 +1,62 @@ +import { Stream } from "stream"; + +const getFolderBusboyData = (busboy: any) => { + type dataType = Record; + + return new Promise((resolve, reject) => { + const formData = new Map(); + + let filesProcessed = 0; + let filesToProcess = 0; + + const fileDataMap: dataType = {}; + + busboy.on("field", (field: any, val: any) => { + if (typeof val !== "string" || val !== "undefined") { + formData.set(field, val); + if (field === "file-data") { + const fileData = JSON.parse(val); + console.log("file data", fileData); + fileDataMap[fileData.path] = { + ...fileData, + }; + } + if (field === "total-files") { + filesToProcess = +val; + console.log("total files", filesToProcess); + } + } + }); + + busboy.on( + "file", + async ( + data: string, + file: Stream, + fileData: { + filename: string; + } + ) => { + const path = fileData.filename; + fileDataMap[path] = { + ...fileDataMap[path], + file, + }; + + filesProcessed++; + + if (filesProcessed === filesToProcess) { + resolve(fileDataMap); + } + // resolve({ + // file, + // filename, + // formData, + // }); + console.log("flr", fileData); + } + ); + }); +}; + +export default getFolderBusboyData; diff --git a/src/api/foldersAPI.ts b/src/api/foldersAPI.ts index 1a3b726..7b57719 100644 --- a/src/api/foldersAPI.ts +++ b/src/api/foldersAPI.ts @@ -97,6 +97,12 @@ export const createFolderAPI = async (name: string, parent?: string) => { return response.data; }; +export const uploadFolderAPI = async (data: FormData, config: any) => { + const url = "/folder-service/upload"; + const response = await axios.post(url, data, config); + return response.data; +}; + // PATCH export const renameFolder = async (folderID: string, name: string) => { diff --git a/src/components/AddNewDropdown/AddNewDropdown.tsx b/src/components/AddNewDropdown/AddNewDropdown.tsx index c2852e6..1c3a8ee 100644 --- a/src/components/AddNewDropdown/AddNewDropdown.tsx +++ b/src/components/AddNewDropdown/AddNewDropdown.tsx @@ -1,5 +1,5 @@ import { useParams } from "react-router-dom"; -import { createFolderAPI } from "../../api/foldersAPI"; +import { createFolderAPI, uploadFolderAPI } from "../../api/foldersAPI"; import { useFoldersClient } from "../../hooks/folders"; import { useClickOutOfBounds } from "../../hooks/utils"; import { showCreateFolderPopup } from "../../popups/folder"; @@ -17,7 +17,8 @@ const AddNewDropdown: React.FC = (props) => { const { invalidateFoldersCache } = useFoldersClient(); const { wrapperRef } = useClickOutOfBounds(props.closeDropdown); const uploadRef: RefObject = useRef(null); - const { uploadFiles } = useUploader(); + const uploadFolderRef: RefObject = useRef(null); + const { uploadFiles, uploadFolder } = useUploader(); const createFolder = async () => { props.closeDropdown(); @@ -41,12 +42,31 @@ const AddNewDropdown: React.FC = (props) => { uploadFiles(files); }; + const handleFolderUpload = (e: React.FormEvent) => { + e.preventDefault(); + props.closeDropdown(); + + const items = uploadFolderRef.current?.files; + + if (!items) return; + + console.log("items", items); + + uploadFolder(items); + }; + const triggerFileUpload = () => { if (uploadRef.current) { uploadRef.current.click(); } }; + const triggerFolderUpload = () => { + if (uploadFolderRef.current) { + uploadFolderRef.current.click(); + } + }; + return (
= (props) => { multiple={true} onChange={handleUpload} /> +
  • @@ -77,6 +105,15 @@ const AddNewDropdown: React.FC = (props) => {

    Create Folder

  • +
  • + + +

    Upload Folder

    +
    +
); diff --git a/src/hooks/files.ts b/src/hooks/files.ts index 063bac3..0c029bb 100644 --- a/src/hooks/files.ts +++ b/src/hooks/files.ts @@ -26,6 +26,7 @@ import { } from "../utils/cancelTokenManager"; import { debounce } from "lodash"; import { addUpload, editUpload } from "../reducers/uploader"; +import { uploadFolderAPI } from "../api/foldersAPI"; export const useFiles = (enabled = true) => { const params = useParams(); @@ -279,5 +280,74 @@ export const useUploader = () => { } }; - return { uploadFiles }; + const uploadFolder = (files: FileList) => { + const data = new FormData(); + + const parent = params.id || "/"; + + data.append("parent", parent); + + for (let i = 0; i < files.length; i++) { + const currentFile = files[i]; + data.append( + "file-data", + JSON.stringify({ + name: currentFile.name, + size: currentFile.size, + type: currentFile.type, + path: currentFile.webkitRelativePath, + }) + ); + } + + data.append("total-files", files.length.toString()); + + for (let i = 0; i < files.length; i++) { + const currentFile = files[i]; + console.log("current file", currentFile.webkitRelativePath); + data.append("file", currentFile, "test"); + } + + const CancelToken = axiosNonInterceptor.CancelToken; + const source = CancelToken.source(); + + const config = { + headers: { + "Content-Type": "multipart/form-data", + "Transfere-Encoding": "chunked", + }, + onUploadProgress: (progressEvent: ProgressEvent) => { + const currentProgress = Math.round( + (progressEvent.loaded / progressEvent.total) * 100 + ); + + console.log("progress", currentProgress); + }, + cancelToken: source.token, + }; + + uploadFolderAPI(data, config) + .then(() => { + // dispatch( + // editUpload({ + // id: currentID, + // updateData: { completed: true, progress: 100 }, + // }) + // ); + // removeFileUploadCancelToken(currentID); + console.log("uploaded folder"); + }) + .catch((e) => { + console.log("Error uploading folder", e); + // dispatch( + // editUpload({ + // id: currentID, + // updateData: { canceled: true }, + // }) + // ); + // removeFileUploadCancelToken(currentID); + }); + }; + + return { uploadFiles, uploadFolder }; };