continued dep injectiong
This commit is contained in:
+92
-15
@@ -9,6 +9,7 @@ import {
|
||||
removeStreamVideoCookie,
|
||||
} from "../cookies/createCookies";
|
||||
import ChunkService from "../services/ChunkService";
|
||||
import streamToBuffer from "../utils/streamToBuffer";
|
||||
|
||||
const fileService = new FileService();
|
||||
|
||||
@@ -30,10 +31,8 @@ interface RequestType extends Request {
|
||||
encryptedToken?: string;
|
||||
}
|
||||
|
||||
type ChunkServiceType = FileSystemService | S3Service;
|
||||
|
||||
class FileController {
|
||||
chunkService: ChunkServiceType;
|
||||
chunkService;
|
||||
|
||||
constructor() {
|
||||
this.chunkService = new ChunkService();
|
||||
@@ -43,19 +42,43 @@ class FileController {
|
||||
if (!req.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
let responseSent = false;
|
||||
try {
|
||||
const user = req.user;
|
||||
const id = req.params.id;
|
||||
|
||||
const decryptedThumbnail = await this.chunkService.getThumbnail(user, id);
|
||||
const { readStream, decipher } = await this.chunkService.getThumbnail(
|
||||
user,
|
||||
id
|
||||
);
|
||||
|
||||
res.send(decryptedThumbnail);
|
||||
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));
|
||||
|
||||
res.send(bufferData);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nGet Thumbnail Error File Route:", e.message);
|
||||
}
|
||||
res.status(500).send("Server error getting thumbnail");
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error getting thumbnail");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,12 +86,38 @@ class FileController {
|
||||
if (!req.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
let responseSent = false;
|
||||
try {
|
||||
const user = req.user;
|
||||
const fileID = req.params.id;
|
||||
|
||||
await this.chunkService.getFullThumbnail(user, fileID, res);
|
||||
const { decipher, readStream, file } =
|
||||
await this.chunkService.getFullThumbnail(user, fileID);
|
||||
|
||||
readStream.on("error", (e: Error) => {
|
||||
console.log("Get full thumbnail read stream error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error getting full thumbnail");
|
||||
}
|
||||
});
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
console.log("Get full thumbnail decipher error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error gettingfull thumbnail");
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nGet Thumbnail Full Error File Route:", e.message);
|
||||
@@ -379,18 +428,46 @@ class FileController {
|
||||
if (!req.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
let responseSent = false;
|
||||
try {
|
||||
const user = req.user;
|
||||
const fileID = req.params.id;
|
||||
|
||||
await this.chunkService.downloadFile(user, fileID, res);
|
||||
const { readStream, decipher, file } =
|
||||
await this.chunkService.downloadFile(user, fileID, res);
|
||||
|
||||
readStream.on("error", (e: Error) => {
|
||||
console.log("read stream error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error downloading file");
|
||||
}
|
||||
});
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
console.log("decipher stream error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error downloading file");
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nDownload File Error File Route:", e.message);
|
||||
}
|
||||
|
||||
res.status(500).send("Server error downloading file");
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error downloading file");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -586,7 +663,7 @@ class FileController {
|
||||
const userID = req.user._id;
|
||||
const items = req.body.items;
|
||||
|
||||
await this.chunkService.trashMulti(userID, items);
|
||||
await fileService.trashMulti(userID, items);
|
||||
|
||||
res.send();
|
||||
} catch (e: unknown) {
|
||||
@@ -607,7 +684,7 @@ class FileController {
|
||||
const userID = req.user._id;
|
||||
const items = req.body.items;
|
||||
|
||||
await this.chunkService.restoreMulti(userID, items);
|
||||
await fileService.restoreMulti(userID, items);
|
||||
|
||||
res.send();
|
||||
} catch (e: unknown) {
|
||||
|
||||
@@ -12,6 +12,22 @@ class FilesystemActions implements IStorageActions {
|
||||
const fsReadableStream = fs.createReadStream(params.filePath);
|
||||
return fsReadableStream;
|
||||
}
|
||||
async removeChunks(params: AuthParams) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!params.filePath) {
|
||||
reject("File path not configured");
|
||||
return;
|
||||
}
|
||||
fs.unlink(params.filePath, (err) => {
|
||||
if (err) {
|
||||
reject("Error removing file");
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { FilesystemActions };
|
||||
|
||||
@@ -17,6 +17,23 @@ class S3Actions implements IStorageActions {
|
||||
.createReadStream();
|
||||
return s3ReadableStream;
|
||||
}
|
||||
|
||||
removeChunks(params: AuthParams) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!params.Key) {
|
||||
reject("S3 not configured");
|
||||
return;
|
||||
}
|
||||
const { s3Storage, bucket } = this.getAuth();
|
||||
s3Storage.deleteObject({ Key: params.Key, Bucket: bucket }, (err) => {
|
||||
if (err) {
|
||||
reject("Error removing file");
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { S3Actions };
|
||||
|
||||
@@ -12,4 +12,5 @@ export interface IStorageActions {
|
||||
createReadStream(
|
||||
params: AuthParams
|
||||
): NodeJS.ReadableStream | internal.Readable;
|
||||
removeChunks(params: AuthParams): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -34,16 +34,15 @@ import MongoFileService from "../FileService";
|
||||
import createVideoThumbnailFS from "./utils/createVideoThumbnailFS";
|
||||
import { S3Actions } from "./S3Actions";
|
||||
import { FilesystemActions } from "./FileSystemActions";
|
||||
import { createGenericParams } from "./utils/storageHelper";
|
||||
|
||||
const dbUtilsFile = new DbUtilFile();
|
||||
const dbUtilsFolder = new DbUtilFolder();
|
||||
const folderService = new FolderService();
|
||||
const fileService = new MongoFileService();
|
||||
|
||||
const storageActions =
|
||||
env.dbType === "s3" ? new S3Actions() : new FilesystemActions();
|
||||
|
||||
class StorageService implements ChunkInterface {
|
||||
class StorageService {
|
||||
constructor() {}
|
||||
|
||||
uploadFile = async (user: UserInterface, busboy: any, req: Request) => {
|
||||
@@ -145,56 +144,13 @@ class StorageService implements ChunkInterface {
|
||||
}
|
||||
};
|
||||
|
||||
getFileWriteStream = async (
|
||||
user: UserInterface,
|
||||
file: FileInterface,
|
||||
parentFolder: FolderInterface
|
||||
) => {
|
||||
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 filename = file.filename;
|
||||
const parent = parentFolder._id;
|
||||
const parentList = [...parentFolder.parentList, parentFolder._id];
|
||||
const size = file.metadata.size;
|
||||
const personalFile = file.metadata.personalFile ? true : false;
|
||||
let hasThumbnail = file.metadata.hasThumbnail;
|
||||
let thumbnailID = file.metadata.thumbnailID;
|
||||
const isVideo = file.metadata.isVideo;
|
||||
|
||||
const systemFileName = uuid.v4();
|
||||
|
||||
const metadata = {
|
||||
owner: user._id,
|
||||
parent,
|
||||
parentList,
|
||||
hasThumbnail,
|
||||
thumbnailID,
|
||||
isVideo,
|
||||
size,
|
||||
IV: file.metadata.IV,
|
||||
filePath: env.fsDirectory + systemFileName,
|
||||
};
|
||||
|
||||
const fileWriteStream = fs.createWriteStream(metadata.filePath);
|
||||
|
||||
return fileWriteStream;
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
downloadFile = async (user: UserInterface, fileID: string, res: Response) => {
|
||||
console.log("DEP INJECT :)");
|
||||
const currentFile = await dbUtilsFile.getFileInfo(
|
||||
fileID,
|
||||
user._id.toString()
|
||||
);
|
||||
console.log(fileID, user._id);
|
||||
|
||||
if (!currentFile) throw new NotFoundError("Download File Not Found");
|
||||
|
||||
const password = user.getEncryptionKey();
|
||||
@@ -208,12 +164,10 @@ class StorageService implements ChunkInterface {
|
||||
IV = IV.buffer;
|
||||
}
|
||||
|
||||
const readStreamParams = {} as any;
|
||||
if (env.dbType === "fs") {
|
||||
readStreamParams.filePath = currentFile.metadata.filePath!;
|
||||
} else {
|
||||
readStreamParams.Key = currentFile.metadata.s3ID!;
|
||||
}
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: currentFile.metadata.filePath,
|
||||
Key: currentFile.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
@@ -221,43 +175,14 @@ class StorageService implements ChunkInterface {
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + currentFile.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", currentFile.metadata.size.toString());
|
||||
|
||||
const allStreamsToErrorCatch = [readStream, decipher];
|
||||
|
||||
await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch);
|
||||
};
|
||||
|
||||
getFileReadStream = async (user: UserInterface, fileID: string) => {
|
||||
const currentFile = await dbUtilsFile.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 filePath = currentFile.metadata.filePath!;
|
||||
|
||||
const IV = currentFile.metadata.IV.buffer as Buffer;
|
||||
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
return readStream;
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
file: currentFile,
|
||||
};
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
getThumbnail = async (user: UserInterface, id: string) => {
|
||||
const password = user.getEncryptionKey();
|
||||
|
||||
@@ -281,25 +206,21 @@ class StorageService implements ChunkInterface {
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv);
|
||||
|
||||
const readStreamParams = {} as any;
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: thumbnail.path,
|
||||
Key: thumbnail.s3ID,
|
||||
});
|
||||
|
||||
const readStream = fs.createReadStream(thumbnail.path!);
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
const allStreamsToErrorCatch = [readStream, decipher];
|
||||
|
||||
const bufferData = await streamToBuffer(
|
||||
readStream.pipe(decipher),
|
||||
allStreamsToErrorCatch
|
||||
);
|
||||
|
||||
return bufferData;
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
};
|
||||
};
|
||||
|
||||
getFullThumbnail = async (
|
||||
user: UserInterface,
|
||||
fileID: string,
|
||||
res: Response
|
||||
) => {
|
||||
// INJECTED
|
||||
getFullThumbnail = async (user: UserInterface, fileID: string) => {
|
||||
const userID = user._id;
|
||||
|
||||
const file = await dbUtilsFile.getFileInfo(fileID, userID.toString());
|
||||
@@ -307,26 +228,31 @@ class StorageService implements ChunkInterface {
|
||||
if (!file) throw new NotFoundError("File Thumbnail Not Found");
|
||||
|
||||
const password = user.getEncryptionKey();
|
||||
const IV = file.metadata.IV;
|
||||
|
||||
// TODO: Fix this type
|
||||
let IV = file.metadata.IV as any;
|
||||
if (!(IV instanceof Buffer)) {
|
||||
IV = IV.buffer;
|
||||
}
|
||||
|
||||
if (!password) throw new ForbiddenError("Invalid Encryption Key");
|
||||
|
||||
const readStream = fs.createReadStream(file.metadata.filePath!);
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: file.metadata.filePath,
|
||||
Key: file.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + file.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", file.metadata.size.toString());
|
||||
|
||||
const allStreamsToErrorCatch = [readStream, decipher];
|
||||
|
||||
await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch);
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
file,
|
||||
};
|
||||
};
|
||||
|
||||
streamVideo = async (
|
||||
@@ -490,60 +416,7 @@ class StorageService implements ChunkInterface {
|
||||
}
|
||||
};
|
||||
|
||||
trashMulti = async (
|
||||
userID: string,
|
||||
items: {
|
||||
type: "file" | "folder" | "quick-item";
|
||||
id: string;
|
||||
file?: FileInterface;
|
||||
folder?: FolderInterface;
|
||||
}[]
|
||||
) => {
|
||||
const fileList = items.filter(
|
||||
(item) => item.type === "file" || item.type === "quick-item"
|
||||
);
|
||||
const folderList = items
|
||||
.filter((item) => item.type === "folder")
|
||||
.sort((a, b) => {
|
||||
if (!a.folder || !b.folder) return 0;
|
||||
return b.folder.parentList.length - a.folder.parentList.length;
|
||||
});
|
||||
|
||||
for (const file of fileList) {
|
||||
await fileService.trashFile(userID, file.id);
|
||||
}
|
||||
for (const folder of folderList) {
|
||||
await folderService.trashFolder(userID, folder.id);
|
||||
}
|
||||
};
|
||||
|
||||
restoreMulti = async (
|
||||
userID: string,
|
||||
items: {
|
||||
type: "file" | "folder" | "quick-item";
|
||||
id: string;
|
||||
file?: FileInterface;
|
||||
folder?: FolderInterface;
|
||||
}[]
|
||||
) => {
|
||||
const fileList = items.filter(
|
||||
(item) => item.type === "file" || item.type === "quick-item"
|
||||
);
|
||||
const folderList = items
|
||||
.filter((item) => item.type === "folder")
|
||||
.sort((a, b) => {
|
||||
if (!a.folder || !b.folder) return 0;
|
||||
return b.folder.parentList.length - a.folder.parentList.length;
|
||||
});
|
||||
|
||||
for (const file of fileList) {
|
||||
await fileService.restoreFile(userID, file.id);
|
||||
}
|
||||
for (const folder of folderList) {
|
||||
await folderService.restoreFolder(userID, folder.id);
|
||||
}
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
deleteMulti = async (
|
||||
userID: string,
|
||||
items: {
|
||||
@@ -571,6 +444,7 @@ class StorageService implements ChunkInterface {
|
||||
}
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
deleteFile = async (userID: string, fileID: string) => {
|
||||
const file = await dbUtilsFile.getFileInfo(fileID, userID);
|
||||
|
||||
@@ -580,21 +454,27 @@ class StorageService implements ChunkInterface {
|
||||
const thumbnail = (await Thumbnail.findById(
|
||||
file.metadata.thumbnailID
|
||||
)) as ThumbnailInterface;
|
||||
const thumbnailPath = thumbnail.path!;
|
||||
await removeChunksFS(thumbnailPath);
|
||||
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: thumbnail.path,
|
||||
Key: thumbnail.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
|
||||
await Thumbnail.deleteOne({ _id: file.metadata.thumbnailID });
|
||||
}
|
||||
|
||||
await removeChunksFS(file.metadata.filePath!);
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: file.metadata.filePath,
|
||||
Key: file.metadata.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
await File.deleteOne({ _id: file._id });
|
||||
await subtractFromStorageSize(
|
||||
userID,
|
||||
file.length,
|
||||
file.metadata.personalFile!
|
||||
);
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
deleteFolder = async (userID: string, folderID: string) => {
|
||||
const folder = await dbUtilsFolder.getFolderInfo(folderID, userID);
|
||||
|
||||
@@ -623,13 +503,23 @@ class StorageService implements ChunkInterface {
|
||||
const thumbnail = (await Thumbnail.findById(
|
||||
currentFile.metadata.thumbnailID
|
||||
)) as ThumbnailInterface;
|
||||
const thumbnailPath = thumbnail.path!;
|
||||
await removeChunksFS(thumbnailPath);
|
||||
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: thumbnail.path,
|
||||
Key: thumbnail.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
|
||||
await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID });
|
||||
}
|
||||
|
||||
await removeChunksFS(currentFile.metadata.filePath!);
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: currentFile.metadata.filePath,
|
||||
Key: currentFile.metadata.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
await File.deleteOne({ _id: currentFile._id });
|
||||
} catch (e) {
|
||||
console.log(
|
||||
@@ -641,6 +531,7 @@ class StorageService implements ChunkInterface {
|
||||
}
|
||||
};
|
||||
|
||||
// INJECTED
|
||||
deleteAll = async (userID: string) => {
|
||||
await Folder.deleteMany({ owner: userID });
|
||||
|
||||
@@ -657,13 +548,22 @@ class StorageService implements ChunkInterface {
|
||||
const thumbnail = (await Thumbnail.findById(
|
||||
currentFile.metadata.thumbnailID
|
||||
)) as ThumbnailInterface;
|
||||
const thumbnailPath = thumbnail.path!;
|
||||
await removeChunksFS(thumbnailPath);
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: thumbnail.path,
|
||||
Key: thumbnail.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
|
||||
await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID });
|
||||
}
|
||||
|
||||
await removeChunksFS(currentFile.metadata.filePath!);
|
||||
const removeChunksParams = createGenericParams({
|
||||
filePath: currentFile.metadata.filePath,
|
||||
Key: currentFile.metadata.s3ID,
|
||||
});
|
||||
|
||||
await storageActions.removeChunks(removeChunksParams);
|
||||
await File.deleteOne({ _id: currentFile._id });
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { UserInterface } from "../../../models/user";
|
||||
import { FileInterface } from "../../../models/file";
|
||||
import { Request, Response } from "express";
|
||||
import { FolderInterface } from "../../../models/folder";
|
||||
import crypto from "crypto";
|
||||
|
||||
interface ChunkInterface {
|
||||
uploadFile: (
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
const awaitStream = <T>(inputSteam: any, outputStream: any, allStreamsToErrorCatch: any[]) => {
|
||||
const awaitStream = <T>(
|
||||
inputSteam: any,
|
||||
outputStream: any,
|
||||
allStreamsToErrorCatch: any[]
|
||||
) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
allStreamsToErrorCatch.forEach((currentStream: any) => {
|
||||
currentStream.on("error", (e: Error) => {
|
||||
reject({
|
||||
message: "Await Stream Input Error",
|
||||
code: 500,
|
||||
error: e,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
inputSteam.pipe(outputStream).on("finish", (data: T) => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
allStreamsToErrorCatch.forEach((currentStream: any) => {
|
||||
|
||||
currentStream.on("error", (e: Error) => {
|
||||
reject({
|
||||
message: "Await Stream Input Error",
|
||||
code: 500,
|
||||
error: e
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
inputSteam.pipe(outputStream).on("finish", (data: T) => {
|
||||
resolve(data);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default awaitStream;
|
||||
export default awaitStream;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ManagedUpload } from "aws-sdk/clients/s3";
|
||||
import s3 from "../../../db/s3";
|
||||
|
||||
const awaitUploadStreamS3 = (
|
||||
@@ -6,7 +7,7 @@ const awaitUploadStreamS3 = (
|
||||
s3Data: { id: string; key: string; bucket: string }
|
||||
) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
s3.upload(params, (err: any, data: any) => {
|
||||
s3.upload(params, (err: any, data: ManagedUpload.SendData) => {
|
||||
if (err) {
|
||||
console.log("Amazon upload err", err);
|
||||
reject("Amazon upload error");
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import env from "../../../enviroment/env";
|
||||
|
||||
type GenericParmasType = {
|
||||
filePath?: string;
|
||||
Key?: string;
|
||||
Bucket?: string;
|
||||
};
|
||||
|
||||
export const createGenericParams = ({ filePath, Key }: GenericParmasType) => {
|
||||
// TODO: Remove file split after migration
|
||||
if (env.dbType === "fs") {
|
||||
const filePathSplit = filePath!.split("/");
|
||||
const fileName = filePathSplit[filePathSplit.length - 1];
|
||||
return {
|
||||
filePath: env.fsDirectory + fileName,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
Key,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -10,9 +10,11 @@ import DbUtilFolder from "../../db/utils/folderUtils";
|
||||
import { UserInterface } from "../../models/user";
|
||||
import { FileInterface } from "../../models/file";
|
||||
import tempStorage from "../../tempStorage/tempStorage";
|
||||
import FolderService from "../FolderService";
|
||||
|
||||
const dbUtilsFile = new DbUtilFile();
|
||||
const dbUtilsFolder = new DbUtilFolder();
|
||||
const folderService = new FolderService();
|
||||
|
||||
type userAccessType = {
|
||||
_id: string;
|
||||
@@ -249,12 +251,66 @@ class MongoFileService {
|
||||
return trashedFile;
|
||||
};
|
||||
|
||||
trashMulti = async (
|
||||
userID: string,
|
||||
items: {
|
||||
type: "file" | "folder" | "quick-item";
|
||||
id: string;
|
||||
file?: FileInterface;
|
||||
folder?: FolderInterface;
|
||||
}[]
|
||||
) => {
|
||||
const fileList = items.filter(
|
||||
(item) => item.type === "file" || item.type === "quick-item"
|
||||
);
|
||||
const folderList = items
|
||||
.filter((item) => item.type === "folder")
|
||||
.sort((a, b) => {
|
||||
if (!a.folder || !b.folder) return 0;
|
||||
return b.folder.parentList.length - a.folder.parentList.length;
|
||||
});
|
||||
|
||||
for (const file of fileList) {
|
||||
await this.trashFile(userID, file.id);
|
||||
}
|
||||
for (const folder of folderList) {
|
||||
await folderService.trashFolder(userID, folder.id);
|
||||
}
|
||||
};
|
||||
|
||||
restoreFile = async (userID: string, fileID: string) => {
|
||||
const restoredFile = await dbUtilsFile.restoreFile(fileID, userID);
|
||||
if (!restoredFile) throw new NotFoundError("Restore File Not Found Error");
|
||||
return restoredFile;
|
||||
};
|
||||
|
||||
restoreMulti = async (
|
||||
userID: string,
|
||||
items: {
|
||||
type: "file" | "folder" | "quick-item";
|
||||
id: string;
|
||||
file?: FileInterface;
|
||||
folder?: FolderInterface;
|
||||
}[]
|
||||
) => {
|
||||
const fileList = items.filter(
|
||||
(item) => item.type === "file" || item.type === "quick-item"
|
||||
);
|
||||
const folderList = items
|
||||
.filter((item) => item.type === "folder")
|
||||
.sort((a, b) => {
|
||||
if (!a.folder || !b.folder) return 0;
|
||||
return b.folder.parentList.length - a.folder.parentList.length;
|
||||
});
|
||||
|
||||
for (const file of fileList) {
|
||||
await this.restoreFile(userID, file.id);
|
||||
}
|
||||
for (const folder of folderList) {
|
||||
await folderService.restoreFolder(userID, folder.id);
|
||||
}
|
||||
};
|
||||
|
||||
renameFile = async (userID: string, fileID: string, title: string) => {
|
||||
const file = await dbUtilsFile.renameFile(fileID, userID, title);
|
||||
|
||||
|
||||
@@ -1,27 +1,10 @@
|
||||
const streamToBuffer = (stream: any, allStreamsToErrorCatch: any[]) => {
|
||||
|
||||
const chunks: any[] = []
|
||||
const streamToBuffer = (stream: any) => {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const chunks: any[] = [];
|
||||
stream.on("data", (chunk: any) => chunks.push(chunk));
|
||||
stream.on("error", reject);
|
||||
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
};
|
||||
|
||||
allStreamsToErrorCatch.forEach((currentStream) => {
|
||||
|
||||
currentStream.on("error", (e: Error) => {
|
||||
|
||||
console.log("Stream To Buffer Error", e);
|
||||
reject({
|
||||
message: "stream to buffer error",
|
||||
code: 500,
|
||||
error: e
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
stream.on('data', (chunk: any) => chunks.push(chunk));
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export default streamToBuffer;
|
||||
export default streamToBuffer;
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "../../api/foldersAPI";
|
||||
import { useClickOutOfBounds, useUtils } from "../../hooks/utils";
|
||||
import { useAppDispatch } from "../../hooks/store";
|
||||
import { setMultiSelectMode } from "../../reducers/selected";
|
||||
import { resetSelected, setMultiSelectMode } from "../../reducers/selected";
|
||||
import TrashIcon from "../../icons/TrashIcon";
|
||||
import MultiSelectIcon from "../../icons/MultiSelectIcon";
|
||||
import RenameIcon from "../../icons/RenameIcon";
|
||||
@@ -98,6 +98,7 @@ const ContextMenu = (props) => {
|
||||
await trashFileAPI(props.file._id);
|
||||
invalidateFilesCache();
|
||||
invalidateQuickFilesCache();
|
||||
dispatch(resetSelected());
|
||||
}
|
||||
} else {
|
||||
const result = await Swal.fire({
|
||||
@@ -112,6 +113,7 @@ const ContextMenu = (props) => {
|
||||
if (result.value) {
|
||||
await trashFolderAPI(props.folder._id);
|
||||
invalidateFoldersCache();
|
||||
dispatch(resetSelected());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -133,6 +135,7 @@ const ContextMenu = (props) => {
|
||||
await deleteFileAPI(props.file._id);
|
||||
invalidateFilesCache();
|
||||
invalidateQuickFilesCache();
|
||||
dispatch(resetSelected());
|
||||
}
|
||||
} else {
|
||||
const result = await Swal.fire({
|
||||
@@ -147,6 +150,7 @@ const ContextMenu = (props) => {
|
||||
if (result.value) {
|
||||
await deleteFolder(props.folder._id);
|
||||
invalidateFoldersCache();
|
||||
dispatch(resetSelected());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,8 +6,11 @@ import UploadOverlay from "../UploadOverlay";
|
||||
import HomepageSpinner from "../HomepageSpinner";
|
||||
import MobileContextMenuContainer from "../MobileContextMenu";
|
||||
import ShareModelWrapper from "../ShareModelWrapper";
|
||||
import PhotoViewer from "../PhotoViewer";
|
||||
import { useAppSelector } from "../../hooks/store";
|
||||
|
||||
const Homepage2 = () => {
|
||||
const Homepage = () => {
|
||||
const photoID = useAppSelector((state) => state.photoViewer.id);
|
||||
return (
|
||||
<div>
|
||||
<HomepageSpinner />
|
||||
@@ -17,7 +20,7 @@ const Homepage2 = () => {
|
||||
<div className="flex space-between">
|
||||
<MainSection />
|
||||
<Uploader />
|
||||
{/* {photoID.length === 0 ? undefined : <PhotoViewer />} */}
|
||||
{photoID.length === 0 ? undefined : <PhotoViewer />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,4 +30,4 @@ const Homepage2 = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Homepage2;
|
||||
export default Homepage;
|
||||
|
||||
@@ -2,20 +2,21 @@ import React from "react";
|
||||
import Spinner from "../Spinner";
|
||||
|
||||
const PhotoViewer = (props) => (
|
||||
<div className="photoviewer">
|
||||
<div className="photoviewer z-50">
|
||||
<img
|
||||
className="photoviewer__close-button"
|
||||
onClick={props.closePhotoViewer}
|
||||
src="/images/close-white-png.png"
|
||||
/>
|
||||
|
||||
<img className="photoviewer__close-button" onClick={props.closePhotoViewer} src="/images/close-white-png.png"/>
|
||||
|
||||
{props.state.image === "" ?
|
||||
<div className="photoviewer__loader">
|
||||
{props.state.image === "" ? (
|
||||
<div className="photoviewer__loader">
|
||||
<Spinner />
|
||||
</div>
|
||||
:
|
||||
<img className="photoviewer__image" src={props.state.image}/>
|
||||
}
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<img className="photoviewer__image" src={props.state.image} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
export default PhotoViewer;
|
||||
export default PhotoViewer;
|
||||
|
||||
@@ -1,58 +1,55 @@
|
||||
import PhotoViewer from "./PhotoViewer";
|
||||
import {connect} from "react-redux";
|
||||
import { connect } from "react-redux";
|
||||
import env from "../../enviroment/envFrontEnd";
|
||||
import React from "react";
|
||||
import {resetPhotoID} from "../../actions/photoViewer";
|
||||
import { resetPhotoID } from "../../actions/photoViewer";
|
||||
import axios from "../../axiosInterceptor";
|
||||
|
||||
class PhotoViewerContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
image: "",
|
||||
};
|
||||
}
|
||||
|
||||
this.state = {
|
||||
image: ""
|
||||
}
|
||||
}
|
||||
closePhotoViewer = () => {
|
||||
this.props.dispatch(resetPhotoID());
|
||||
};
|
||||
|
||||
closePhotoViewer = () => {
|
||||
componentDidMount = () => {
|
||||
const config = {
|
||||
responseType: "arraybuffer",
|
||||
};
|
||||
|
||||
this.props.dispatch(resetPhotoID())
|
||||
}
|
||||
// TODO: Fix URL
|
||||
const url = `http://localhost:5173/api/file-service/full-thumbnail/${this.props.photoID}`;
|
||||
axios.get(url, config).then((response) => {
|
||||
const imgFile = new Blob([response.data]);
|
||||
const imgUrl = URL.createObjectURL(imgFile);
|
||||
|
||||
componentDidMount = () => {
|
||||
this.setState(() => ({
|
||||
...this.state,
|
||||
image: imgUrl,
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
const config = {
|
||||
responseType: 'arraybuffer'
|
||||
};
|
||||
|
||||
const url = this.props.isGoogle ? `/file-service-google/full-thumbnail/${this.props.photoID}`
|
||||
: !this.props.isPersonal ? `/file-service/full-thumbnail/${this.props.photoID}` : `/file-service-personal/full-thumbnail/${this.props.photoID}`;
|
||||
|
||||
axios.get(url, config).then((response) => {
|
||||
|
||||
const imgFile = new Blob([response.data]);
|
||||
const imgUrl = URL.createObjectURL(imgFile);
|
||||
|
||||
this.setState(() => ({
|
||||
...this.state,
|
||||
image: imgUrl
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return <PhotoViewer
|
||||
closePhotoViewer={this.closePhotoViewer}
|
||||
state={this.state}/>
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<PhotoViewer
|
||||
closePhotoViewer={this.closePhotoViewer}
|
||||
state={this.state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const connectStateToProp = (state) => ({
|
||||
photoID: state.photoViewer.id,
|
||||
isGoogle: state.photoViewer.isGoogle,
|
||||
isPersonal: state.photoViewer.isPersonal,
|
||||
})
|
||||
photoID: state.photoViewer.id,
|
||||
isGoogle: state.photoViewer.isGoogle,
|
||||
isPersonal: state.photoViewer.isPersonal,
|
||||
});
|
||||
|
||||
export default connect(connectStateToProp)(PhotoViewerContainer);
|
||||
export default connect(connectStateToProp)(PhotoViewerContainer);
|
||||
|
||||
@@ -1,41 +1,39 @@
|
||||
.photoviewer {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 5;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.photoviewer__close-button {
|
||||
width: 22px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
width: 22px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.photoviewer__image {
|
||||
|
||||
width: 95%;
|
||||
height: 90%;
|
||||
margin-left: 2.5%;
|
||||
// background: blue;
|
||||
object-fit: contain;
|
||||
width: 95%;
|
||||
height: 90%;
|
||||
margin-left: 2.5%;
|
||||
// background: blue;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.photoviewer__loader {
|
||||
width: 98%;
|
||||
height: 88%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 98%;
|
||||
height: 88%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: $desktop-breakpoint) {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
@media (max-width: $desktop-breakpoint) {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user