From 0eb8bf12a0b302cfec46aa95549665568e07d37b Mon Sep 17 00:00:00 2001 From: subnub Date: Sat, 7 Dec 2024 20:11:29 -0500 Subject: [PATCH] continued cleaning up events --- backend/controllers/file-controller.ts | 83 ++++------------- backend/controllers/folder-controller.ts | 2 +- .../services/chunk-service/chunk-service.ts | 86 +++--------------- .../chunk-service/utils/getBusboyData.ts | 21 ++++- .../chunk-service/utils/getFileData.ts | 88 +++++++++++++++++++ .../utils/getFolderUploadBusboyData.ts | 27 +++++- .../chunk-service/utils/getThumbnailData.ts | 77 ++++++++++++++++ 7 files changed, 234 insertions(+), 150 deletions(-) create mode 100644 backend/services/chunk-service/utils/getFileData.ts create mode 100644 backend/services/chunk-service/utils/getThumbnailData.ts diff --git a/backend/controllers/file-controller.ts b/backend/controllers/file-controller.ts index 62efe11..41805f3 100644 --- a/backend/controllers/file-controller.ts +++ b/backend/controllers/file-controller.ts @@ -7,14 +7,7 @@ import { } from "../cookies/create-cookies"; import ChunkService from "../services/chunk-service/chunk-service"; import streamToBuffer from "../utils/streamToBuffer"; -import env from "../enviroment/env"; -import getFileSize from "../services/chunk-service/utils/getFileSize"; -import File, { FileMetadateInterface } from "../models/file-model"; -import imageChecker from "../utils/imageChecker"; -import videoChecker from "../utils/videoChecker"; -import createVideoThumbnail from "../services/chunk-service/utils/createVideoThumbnail"; import NotAuthorizedError from "../utils/NotAuthorizedError"; -import createThumbnail from "../services/chunk-service/utils/createImageThumbnail"; import { FileListQueryType } from "../types/file-types"; const fileService = new FileService(); @@ -25,7 +18,7 @@ type userAccessType = { s3Enabled: boolean; }; -interface RequestTypeFullUser extends Request { +export interface RequestTypeFullUser extends Request { user?: UserInterface; encryptedToken?: string; accessTokenStreamVideo?: string; @@ -43,47 +36,23 @@ class FileController { this.chunkService = new ChunkService(); } - getThumbnail = async (req: RequestTypeFullUser, res: Response) => { + getThumbnail = async ( + req: RequestTypeFullUser, + res: Response, + next: NextFunction + ) => { if (!req.user) { return; } - let responseSent = false; try { const user = req.user; const id = req.params.id; - const { readStream, decipher } = await this.chunkService.getThumbnail( - user, - id - ); - - readStream.on("error", (e: Error) => { - console.log("Get thumbnail read stream error", e); - if (!responseSent) { - responseSent = true; - res.status(500).send("Server error getting thumbnail"); - } - }); - - decipher.on("error", (e: Error) => { - console.log("Get thumbnail decipher error", e); - if (!responseSent) { - responseSent = true; - res.status(500).send("Server error getting thumbnail"); - } - }); - - const bufferData = await streamToBuffer(readStream.pipe(decipher)); + const bufferData = await this.chunkService.getThumbnail(user, id); res.send(bufferData); } catch (e: unknown) { - if (e instanceof Error) { - console.log("\nGet Thumbnail Error File Route:", e.message); - } - if (!responseSent) { - responseSent = true; - res.status(500).send("Server error getting thumbnail"); - } + next(e); } }; @@ -144,12 +113,6 @@ class FileController { const user = req.user; const busboy = req.busboy; - req.on("error", (e: Error) => { - next(e); - }); - - req.pipe(busboy); - const file = await this.chunkService.uploadFile(user, busboy, req); res.send(file); @@ -496,7 +459,11 @@ class FileController { } }; - downloadFile = async (req: RequestTypeFullUser, res: Response) => { + downloadFile = async ( + req: RequestTypeFullUser, + res: Response, + next: NextFunction + ) => { if (!req.user) { return; } @@ -505,29 +472,9 @@ class FileController { const user = req.user; const fileID = req.params.id; - const { readStream, decipher, file } = - await this.chunkService.downloadFile(user, fileID, res); - - readStream.on("error", (e: Error) => { - console.log("read stream error", e); - }); - - decipher.on("error", (e: Error) => { - console.log("decipher stream errozr", e); - }); - - res.set("Content-Type", "binary/octet-stream"); - res.set( - "Content-Disposition", - 'attachment; filename="' + file.filename + '"' - ); - res.set("Content-Length", file.metadata.size.toString()); - - readStream.pipe(decipher).pipe(res); + await this.chunkService.downloadFile(user, fileID, res); } catch (e: unknown) { - if (e instanceof Error) { - console.log("\nDownload File Error File Route:", e.message); - } + next(e); } }; diff --git a/backend/controllers/folder-controller.ts b/backend/controllers/folder-controller.ts index dbbc999..f4070f3 100644 --- a/backend/controllers/folder-controller.ts +++ b/backend/controllers/folder-controller.ts @@ -85,7 +85,7 @@ class FolderController { const user = req.user; const busboy = req.busboy; - await chunkService.uploadFolder(user, busboy, req, next); + await chunkService.uploadFolder(user, busboy, req); res.send(); } catch (e) { diff --git a/backend/services/chunk-service/chunk-service.ts b/backend/services/chunk-service/chunk-service.ts index 2f7af34..860ce70 100644 --- a/backend/services/chunk-service/chunk-service.ts +++ b/backend/services/chunk-service/chunk-service.ts @@ -24,6 +24,8 @@ import archiver from "archiver"; import async from "async"; import getFolderBusboyData from "./utils/getFolderUploadBusboyData"; import { getStorageActions } from "./actions/helper-actions"; +import getThumbnailData from "./utils/getThumbnailData"; +import getFileData from "./utils/getFileData"; const fileDB = new FileDB(); const folderDB = new FolderDB(); @@ -36,7 +38,7 @@ class StorageService { constructor() {} uploadFile = async (user: UserInterface, busboy: any, req: Request) => { - const { parent, file } = await uploadFileToStorage(busboy, user); + const { parent, file } = await uploadFileToStorage(busboy, user, req); const parentList = []; @@ -61,25 +63,12 @@ class StorageService { return file; }; - uploadFolder = async ( - user: UserInterface, - busboy: any, - req: Request, - next: NextFunction - ) => { - req.on("error", (e: Error) => { - console.log("req error", e); - next(e); - }); - - busboy.on("error", (e: Error) => { - console.log("busboy error", e); - next(e); - }); - - req.pipe(busboy); - - const { fileDataMap, parent } = await getFolderBusboyData(busboy, user); + uploadFolder = async (user: UserInterface, busboy: any, req: Request) => { + const { fileDataMap, parent } = await getFolderBusboyData( + busboy, + user, + req + ); const keys = Object.keys(fileDataMap); @@ -179,32 +168,7 @@ class StorageService { }; downloadFile = async (user: UserInterface, fileID: string, res: Response) => { - const currentFile = await fileDB.getFileInfo(fileID, user._id.toString()); - - if (!currentFile) throw new NotFoundError("Download File Not Found"); - - const password = user.getEncryptionKey(); - - if (!password) throw new ForbiddenError("Invalid Encryption Key"); - - const IV = currentFile.metadata.IV; - - const readStreamParams = createGenericParams({ - filePath: currentFile.metadata.filePath, - Key: currentFile.metadata.s3ID, - }); - - const readStream = storageActions.createReadStream(readStreamParams); - - const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); - - const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); - - return { - readStream, - decipher, - file: currentFile, - }; + await getFileData(res, fileID, user); }; downloadZip = async ( @@ -384,34 +348,8 @@ class StorageService { }; getThumbnail = async (user: UserInterface, id: string) => { - const password = user.getEncryptionKey(); - - if (!password) throw new ForbiddenError("Invalid Encryption Key"); - - const thumbnail = await thumbnailDB.getThumbnailInfo( - user._id.toString(), - id - ); - - if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); - - const iv = thumbnail.IV; - - const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); - - const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); - - const readStreamParams = createGenericParams({ - filePath: thumbnail.path, - Key: thumbnail.s3ID, - }); - - const readStream = storageActions.createReadStream(readStreamParams); - - return { - readStream, - decipher, - }; + const bufferData = await getThumbnailData(id, user); + return bufferData; }; getFullThumbnail = async (user: UserInterface, fileID: string) => { diff --git a/backend/services/chunk-service/utils/getBusboyData.ts b/backend/services/chunk-service/utils/getBusboyData.ts index 72d88ef..e70aff8 100644 --- a/backend/services/chunk-service/utils/getBusboyData.ts +++ b/backend/services/chunk-service/utils/getBusboyData.ts @@ -14,6 +14,7 @@ import imageChecker from "../../../utils/imageChecker"; import videoChecker from "../../../utils/videoChecker"; import createVideoThumbnail from "./createVideoThumbnail"; import createThumbnail from "./createImageThumbnail"; +import { RequestTypeFullUser } from "../../../controllers/file-controller"; // TODO: We should stop using moongoose directly here, // Also in our fileDB make sure we are actually using File instead @@ -26,7 +27,11 @@ type FileInfo = { parent: string; }; -const processData = (busboy: any, user: UserInterface) => { +const processData = ( + busboy: any, + user: UserInterface, + req: RequestTypeFullUser +) => { const eventEmitter = new EventEmitter(); try { @@ -184,6 +189,12 @@ const processData = (busboy: any, user: UserInterface) => { busboy.on("error", (e: Error) => { eventEmitter.emit("error", e); }); + + req.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + req.pipe(busboy); } catch (e) { eventEmitter.emit("error", e); } @@ -191,9 +202,13 @@ const processData = (busboy: any, user: UserInterface) => { return eventEmitter; }; -const uploadFileToStorage = (busboy: any, user: UserInterface) => { +const uploadFileToStorage = ( + busboy: any, + user: UserInterface, + req: RequestTypeFullUser +) => { return new Promise((resolve, reject) => { - const eventEmitter = processData(busboy, user); + const eventEmitter = processData(busboy, user, req); eventEmitter.on("finish", (data) => { resolve(data); }); diff --git a/backend/services/chunk-service/utils/getFileData.ts b/backend/services/chunk-service/utils/getFileData.ts new file mode 100644 index 0000000..d45c901 --- /dev/null +++ b/backend/services/chunk-service/utils/getFileData.ts @@ -0,0 +1,88 @@ +import { EventEmitter } from "stream"; +import { UserInterface } from "../../../models/user-model"; +import { Response } from "express"; +import ForbiddenError from "../../../utils/ForbiddenError"; +import NotFoundError from "../../../utils/NotFoundError"; +import crypto from "crypto"; +import { createGenericParams } from "./storageHelper"; +import { getStorageActions } from "../actions/helper-actions"; +import FileDB from "../../../db/mongoDB/fileDB"; + +const fileDB = new FileDB(); + +const storageActions = getStorageActions(); + +const proccessData = (res: Response, fileID: string, user: UserInterface) => { + const eventEmitter = new EventEmitter(); + + const processFile = async () => { + try { + const currentFile = await fileDB.getFileInfo(fileID, user._id.toString()); + + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const IV = currentFile.metadata.IV; + + const readStreamParams = createGenericParams({ + filePath: currentFile.metadata.filePath, + Key: currentFile.metadata.s3ID, + }); + + const readStream = storageActions.createReadStream(readStreamParams); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); + + decipher.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + readStream.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + res.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + res.set("Content-Type", "binary/octet-stream"); + res.set( + "Content-Disposition", + 'attachment; filename="' + currentFile.filename + '"' + ); + res.set("Content-Length", currentFile.metadata.size.toString()); + + readStream + .pipe(decipher) + .pipe(res) + .on("finish", () => { + eventEmitter.emit("finish"); + }); + } catch (e) { + eventEmitter.emit("error", e); + } + }; + + processFile(); + + return eventEmitter; +}; + +const getFileData = (res: Response, fileID: string, user: UserInterface) => { + return new Promise((resolve, reject) => { + const eventEmitter = proccessData(res, fileID, user); + eventEmitter.on("finish", (data) => { + resolve(data); + }); + eventEmitter.on("error", (e) => { + reject(e); + }); + }); +}; + +export default getFileData; diff --git a/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts b/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts index 3b3a723..92a17df 100644 --- a/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts +++ b/backend/services/chunk-service/utils/getFolderUploadBusboyData.ts @@ -17,6 +17,7 @@ import createVideoThumbnail from "./createVideoThumbnail"; import createThumbnail from "./createImageThumbnail"; import { EventEmitter } from "events"; import { getStorageActions } from "../actions/helper-actions"; +import { RequestTypeFullUser } from "../../../controllers/file-controller"; type FileDataType = { name: string; @@ -32,7 +33,11 @@ const storageActions = getStorageActions(); type dataType = Record; -const processData = (busboy: any, user: UserInterface) => { +const processData = ( + busboy: any, + user: UserInterface, + req: RequestTypeFullUser +) => { const eventEmitter = new EventEmitter(); try { @@ -209,7 +214,7 @@ const processData = (busboy: any, user: UserInterface) => { busboy.on( "file", - async ( + ( _: string, file: Stream, fileData: { @@ -227,6 +232,16 @@ const processData = (busboy: any, user: UserInterface) => { busboy.on("error", (e: Error) => { eventEmitter.emit("error", e); }); + + req.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + busboy.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + req.pipe(busboy); } catch (e) { eventEmitter.emit("error", e); } @@ -234,10 +249,14 @@ const processData = (busboy: any, user: UserInterface) => { return eventEmitter; }; -const getFolderBusboyData = (busboy: any, user: UserInterface) => { +const getFolderBusboyData = ( + busboy: any, + user: UserInterface, + req: RequestTypeFullUser +) => { return new Promise<{ fileDataMap: dataType; parent: string }>( (resolve, reject) => { - const fileEventEmitter = processData(busboy, user); + const fileEventEmitter = processData(busboy, user, req); fileEventEmitter.on("finish", (data) => { resolve(data); }); diff --git a/backend/services/chunk-service/utils/getThumbnailData.ts b/backend/services/chunk-service/utils/getThumbnailData.ts new file mode 100644 index 0000000..c2fe9a6 --- /dev/null +++ b/backend/services/chunk-service/utils/getThumbnailData.ts @@ -0,0 +1,77 @@ +import { EventEmitter } from "stream"; +import { UserInterface } from "../../../models/user-model"; +import ForbiddenError from "../../../utils/ForbiddenError"; +import ThumbnailDB from "../../../db/mongoDB/thumbnailDB"; +import NotFoundError from "../../../utils/NotFoundError"; +import crypto from "crypto"; +import { createGenericParams } from "./storageHelper"; +import { getStorageActions } from "../actions/helper-actions"; +import streamToBuffer from "../../../utils/streamToBuffer"; + +const thumbnailDB = new ThumbnailDB(); + +const storageActions = getStorageActions(); + +const processData = (thumbnailID: string, user: UserInterface) => { + const eventEmitter = new EventEmitter(); + + const processThumbnail = async () => { + try { + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const thumbnail = await thumbnailDB.getThumbnailInfo( + user._id.toString(), + thumbnailID + ); + + if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); + + const iv = thumbnail.IV; + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); + + const readStreamParams = createGenericParams({ + filePath: thumbnail.path, + Key: thumbnail.s3ID, + }); + + const readStream = storageActions.createReadStream(readStreamParams); + + decipher.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + readStream.on("error", (e: Error) => { + eventEmitter.emit("error", e); + }); + + const bufferData = await streamToBuffer(readStream.pipe(decipher)); + + eventEmitter.emit("finish", bufferData); + } catch (e) { + eventEmitter.emit("error", e); + } + }; + + processThumbnail(); + + return eventEmitter; +}; + +const getThumbnailData = (thumbnailID: string, user: UserInterface) => { + return new Promise((resolve, reject) => { + const eventEmitter = processData(thumbnailID, user); + eventEmitter.on("finish", (data) => { + resolve(data); + }); + eventEmitter.on("error", (e) => { + reject(e); + }); + }); +}; + +export default getThumbnailData;