diff --git a/backend/controllers/folder-controller.ts b/backend/controllers/folder-controller.ts index cdd4001..44c96a2 100644 --- a/backend/controllers/folder-controller.ts +++ b/backend/controllers/folder-controller.ts @@ -34,7 +34,7 @@ class FolderController { try { const userID = req.user._id; const name = req.body.name; - const parent = req.body.parent; + const parent = req.body.parent || "/"; const folder = await folderService.createFolder(userID, name, parent); @@ -56,7 +56,6 @@ class FolderController { try { const userID = req.user._id; const folderID = req.body.id; - const parentList = req.body.parentList; await chunkService.deleteFolder(userID, folderID); diff --git a/backend/db/mongoDB/fileDB.ts b/backend/db/mongoDB/fileDB.ts index c9ce940..256f617 100644 --- a/backend/db/mongoDB/fileDB.ts +++ b/backend/db/mongoDB/fileDB.ts @@ -301,6 +301,16 @@ class DbUtil { { new: true } ); }; + + // DELETE + + deleteFile = async (fileID: string, userID: string) => { + const result = await File.deleteOne({ + _id: new ObjectId(fileID), + "metadata.owner": userID, + }); + return result; + }; } export default DbUtil; diff --git a/backend/db/mongoDB/folderDB.ts b/backend/db/mongoDB/folderDB.ts index 1a8ef16..ab58793 100644 --- a/backend/db/mongoDB/folderDB.ts +++ b/backend/db/mongoDB/folderDB.ts @@ -76,6 +76,8 @@ class DbUtil { query.name = new RegExp(search, "i"); } + query.trashed = null; + const result = await Folder.find(query).sort({ createdAt: -1 }); return result; @@ -209,10 +211,37 @@ class DbUtil { owner: string; }) => { const folder = new Folder(folderData); - await await folder.save(); + + await folder.save(); return folder; }; + + // DELETE + + deleteFolder = async (folderID: string, userID: string) => { + const result = await Folder.deleteOne({ + _id: new ObjectId(folderID), + owner: userID, + }); + return result; + }; + + deleteFoldersByParentList = async ( + parentList: (string | ObjectId)[], + userID: string + ) => { + const result = await Folder.deleteMany({ + owner: userID, + parentList: { $all: parentList }, + }); + return result; + }; + + deleteFoldersByOwner = async (userID: string) => { + const result = await Folder.deleteMany({ owner: userID }); + return result; + }; } export default DbUtil; diff --git a/backend/db/mongoDB/thumbnailDB.ts b/backend/db/mongoDB/thumbnailDB.ts new file mode 100644 index 0000000..474d910 --- /dev/null +++ b/backend/db/mongoDB/thumbnailDB.ts @@ -0,0 +1,28 @@ +import Thumbnail from "../../models/thumbnail-model"; +import { ObjectId } from "mongodb"; + +class ThumbnailDB { + constructor() {} + + // READ + + getThumbnailInfo = async (userID: string, thumbnailID: string) => { + const thumbnail = await Thumbnail.findOne({ + _id: new ObjectId(thumbnailID), + owner: userID, + }); + return thumbnail; + }; + + // DELETE + + removeThumbnail = async (userID: string, thumbnailID: ObjectId) => { + const result = await Thumbnail.deleteOne({ + _id: thumbnailID, + owner: userID, + }); + return result; + }; +} + +export default ThumbnailDB; diff --git a/backend/db/mongoDB/userDB.ts b/backend/db/mongoDB/userDB.ts new file mode 100644 index 0000000..eae997e --- /dev/null +++ b/backend/db/mongoDB/userDB.ts @@ -0,0 +1,15 @@ +import { ObjectId } from "mongodb"; +import User from "../../models/user-model"; + +// READ + +class UserDB { + constructor() {} + + getUserInfo = async (userID: string) => { + const user = await User.findOne({ _id: new ObjectId(userID) }); + return user; + }; +} + +export default UserDB; diff --git a/backend/express-routers/folder-router.ts b/backend/express-routers/folder-router.ts index b4f6443..c6178f4 100644 --- a/backend/express-routers/folder-router.ts +++ b/backend/express-routers/folder-router.ts @@ -2,8 +2,15 @@ import { Router } from "express"; import auth from "../middleware/auth"; import FolderController from "../controllers/folder-controller"; import { + createFolderValidationRules, + deleteFolderValidationRules, getFolderInfoValidationRules, + getFolderListValidationRules, moveFolderListValidationRules, + moveFolderValidationRules, + renameFolderValidationRules, + restoreFolderValidationRules, + trashFolderValidationRules, } from "../middleware/folders/folder-middleware"; const folderController = new FolderController(); @@ -18,7 +25,12 @@ router.get( folderController.getInfo ); -router.get("/folder-service/list", auth, folderController.getFolderList); +router.get( + "/folder-service/list", + auth, + getFolderListValidationRules, + folderController.getFolderList +); router.get( "/folder-service/move-folder-list", @@ -29,22 +41,52 @@ router.get( // PATCH -router.patch("/folder-service/rename", auth, folderController.renameFolder); +router.patch( + "/folder-service/rename", + auth, + renameFolderValidationRules, + folderController.renameFolder +); -router.patch("/folder-service/move", auth, folderController.moveFolder); +router.patch( + "/folder-service/move", + auth, + moveFolderValidationRules, + folderController.moveFolder +); -router.patch("/folder-service/trash", auth, folderController.trashFolder); +router.patch( + "/folder-service/trash", + auth, + trashFolderValidationRules, + folderController.trashFolder +); -router.patch("/folder-service/restore", auth, folderController.restoreFolder); +router.patch( + "/folder-service/restore", + auth, + restoreFolderValidationRules, + folderController.restoreFolder +); // DELETE -router.delete("/folder-service/remove", auth, folderController.deleteFolder); +router.delete( + "/folder-service/remove", + auth, + deleteFolderValidationRules, + folderController.deleteFolder +); router.delete("/folder-service/remove-all", auth, folderController.deleteAll); // POST -router.post("/folder-service/create", auth, folderController.createFolder); +router.post( + "/folder-service/create", + auth, + createFolderValidationRules, + folderController.createFolder +); export default router; diff --git a/backend/middleware/folders/folder-middleware.ts b/backend/middleware/folders/folder-middleware.ts index 2c4fc06..00f011d 100644 --- a/backend/middleware/folders/folder-middleware.ts +++ b/backend/middleware/folders/folder-middleware.ts @@ -2,6 +2,8 @@ import { body, param, query, validationResult } from "express-validator"; import { Request, Response, NextFunction } from "express"; import { middlewareValidationFunction } from "../utils/middleware-utils"; +// GET + export const moveFolderListValidationRules = [ query("parent").optional().isString().withMessage("Parent must be a string"), query("search").optional().isString().withMessage("Search must be a string"), @@ -13,9 +15,52 @@ export const getFolderInfoValidationRules = [ middlewareValidationFunction, ]; -const getFolderListValidationRules = [ +export const getFolderListValidationRules = [ query("search").optional().isString().withMessage("Search must be a string"), query("parent").optional().isString().withMessage("Parent must be a string"), query("sortBy").optional().isString().withMessage("Sort By must be a string"), - query("type").optional().isString().withMessage("Type must be a string"), + query("trashMode") + .optional() + .isBoolean() + .withMessage("Trash Mode must be a boolean"), + middlewareValidationFunction, +]; + +// PATCH + +export const renameFolderValidationRules = [ + body("id").isString().withMessage("ID must be a string"), + body("title").isString().withMessage("Title must be a string"), + middlewareValidationFunction, +]; + +export const moveFolderValidationRules = [ + body("parentID").isString().withMessage("Parent must be a string"), + body("id").isString().withMessage("ID must be a string"), + middlewareValidationFunction, +]; + +export const trashFolderValidationRules = [ + body("id").isString().withMessage("ID must be a string"), + middlewareValidationFunction, +]; + +export const restoreFolderValidationRules = [ + body("id").isString().withMessage("ID must be a string"), + middlewareValidationFunction, +]; + +// DELETE + +export const deleteFolderValidationRules = [ + body("id").isString().withMessage("ID must be a string"), + middlewareValidationFunction, +]; + +// POST + +export const createFolderValidationRules = [ + body("name").isString().withMessage("Name must be a string"), + body("parent").optional().isString().withMessage("Parent must be a string"), + middlewareValidationFunction, ]; diff --git a/backend/services/chunk-service/actions/S3-actions.ts b/backend/services/chunk-service/actions/S3-actions.ts index 3cc5f06..736d0d9 100644 --- a/backend/services/chunk-service/actions/S3-actions.ts +++ b/backend/services/chunk-service/actions/S3-actions.ts @@ -76,7 +76,7 @@ class S3Actions implements IStorageActions { s3Storage.upload({ Bucket: bucket, Body: stream, Key: randomID }, (err) => { if (err) { - console.log("Amazon upload err", err); + console.log("S3 upload err", err); pass.emit("error", err); } }); diff --git a/backend/services/chunk-service/chunk-service.ts b/backend/services/chunk-service/chunk-service.ts index a696a0e..3728412 100644 --- a/backend/services/chunk-service/chunk-service.ts +++ b/backend/services/chunk-service/chunk-service.ts @@ -6,27 +6,24 @@ import crypto from "crypto"; import getBusboyData from "./utils/getBusboyData"; import videoChecker from "../../utils/videoChecker"; import uuid from "uuid"; -import File, { - FileInterface, - FileMetadateInterface, -} from "../../models/file-model"; +import { FileInterface, FileMetadateInterface } from "../../models/file-model"; import FileDB from "../../db/mongoDB/fileDB"; import FolderDB from "../../db/mongoDB/folderDB"; -import Thumbnail, { ThumbnailInterface } from "../../models/thumbnail-model"; -import User from "../../models/user-model"; import env from "../../enviroment/env"; import fixStartChunkLength from "./utils/fixStartChunkLength"; -import Folder, { FolderInterface } from "../../models/folder-model"; +import { FolderInterface } from "../../models/folder-model"; import ForbiddenError from "../../utils/ForbiddenError"; -import { ObjectId } from "mongodb"; import { S3Actions } from "./actions/S3-actions"; import { FilesystemActions } from "./actions/file-system-actions"; import { createGenericParams } from "./utils/storageHelper"; -import { check } from "express-validator"; import { Readable } from "stream"; +import ThumbnailDB from "../../db/mongoDB/thumbnailDB"; +import UserDB from "../../db/mongoDB/userDB"; const fileDB = new FileDB(); const folderDB = new FolderDB(); +const thumbnailDB = new ThumbnailDB(); +const userDB = new UserDB(); const storageActions = env.dbType === "s3" ? new S3Actions() : new FilesystemActions(); @@ -162,13 +159,12 @@ class StorageService { if (!password) throw new ForbiddenError("Invalid Encryption Key"); - const thumbnail = (await Thumbnail.findById( - new ObjectId(id) - )) as ThumbnailInterface; + const thumbnail = await thumbnailDB.getThumbnailInfo( + user._id.toString(), + id + ); - if (thumbnail.owner !== user._id.toString()) { - throw new ForbiddenError("Thumbnail Unauthorized Error"); - } + if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); const iv = thumbnail.IV; @@ -305,7 +301,9 @@ class StorageService { await fileDB.removeOneTimePublicLink(fileID); } - const user = (await User.findById(file.metadata.owner)) as UserInterface; + const user = await userDB.getUserInfo(file.metadata.owner); + + if (!user) throw new NotFoundError("User Not Found"); const password = user.getEncryptionKey(); @@ -364,9 +362,12 @@ class StorageService { if (!file) throw new NotFoundError("Delete File Not Found Error"); if (file.metadata.thumbnailID) { - const thumbnail = (await Thumbnail.findById( + const thumbnail = await thumbnailDB.getThumbnailInfo( + userID, file.metadata.thumbnailID - )) as ThumbnailInterface; + ); + + if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); const removeChunksParams = createGenericParams({ filePath: thumbnail.path, @@ -375,7 +376,7 @@ class StorageService { await storageActions.removeChunks(removeChunksParams); - await Thumbnail.deleteOne({ _id: file.metadata.thumbnailID }); + await thumbnailDB.removeThumbnail(userID, thumbnail._id); } const removeChunksParams = createGenericParams({ @@ -384,7 +385,7 @@ class StorageService { }); await storageActions.removeChunks(removeChunksParams); - await File.deleteOne({ _id: file._id }); + await fileDB.deleteFile(fileID, userID); }; deleteFolder = async (userID: string, folderID: string) => { @@ -394,11 +395,8 @@ class StorageService { const parentList = [...folder.parentList, folder._id]; - await Folder.deleteMany({ - owner: userID, - parentList: { $all: parentList }, - }); - await Folder.deleteMany({ owner: userID, _id: folderID }); + await folderDB.deleteFoldersByParentList(parentList, userID); + await folderDB.deleteFolder(folderID, userID); const fileList = await fileDB.getFileListByIncludedParent( userID, @@ -412,9 +410,12 @@ class StorageService { try { if (currentFile.metadata.thumbnailID) { - const thumbnail = (await Thumbnail.findById( + const thumbnail = await thumbnailDB.getThumbnailInfo( + userID, currentFile.metadata.thumbnailID - )) as ThumbnailInterface; + ); + + if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); const removeChunksParams = createGenericParams({ filePath: thumbnail.path, @@ -423,7 +424,7 @@ class StorageService { await storageActions.removeChunks(removeChunksParams); - await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID }); + await thumbnailDB.removeThumbnail(userID, thumbnail._id); } const removeChunksParams = createGenericParams({ @@ -432,7 +433,7 @@ class StorageService { }); await storageActions.removeChunks(removeChunksParams); - await File.deleteOne({ _id: currentFile._id }); + await fileDB.deleteFile(currentFile._id.toString(), userID); } catch (e) { console.log( "Could not delete file", @@ -444,7 +445,7 @@ class StorageService { }; deleteAll = async (userID: string) => { - await Folder.deleteMany({ owner: userID }); + await folderDB.deleteFoldersByOwner(userID); const fileList = await fileDB.getFileListByOwner(userID); @@ -456,9 +457,13 @@ class StorageService { try { if (currentFile.metadata.thumbnailID) { - const thumbnail = (await Thumbnail.findById( + const thumbnail = await thumbnailDB.getThumbnailInfo( + userID, currentFile.metadata.thumbnailID - )) as ThumbnailInterface; + ); + + if (!thumbnail) throw new NotFoundError("Thumbnail Not Found"); + const removeChunksParams = createGenericParams({ filePath: thumbnail.path, Key: thumbnail.s3ID, @@ -466,7 +471,7 @@ class StorageService { await storageActions.removeChunks(removeChunksParams); - await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID }); + await thumbnailDB.removeThumbnail(userID, thumbnail._id); } const removeChunksParams = createGenericParams({ @@ -475,7 +480,7 @@ class StorageService { }); await storageActions.removeChunks(removeChunksParams); - await File.deleteOne({ _id: currentFile._id }); + await fileDB.deleteFile(currentFile._id.toString(), userID); } catch (e) { console.log( "Could Not Remove File", diff --git a/backend/services/folder-service/folder-service.ts b/backend/services/folder-service/folder-service.ts index b311092..ebc34de 100644 --- a/backend/services/folder-service/folder-service.ts +++ b/backend/services/folder-service/folder-service.ts @@ -20,7 +20,7 @@ const folderDB = new FolderDB(); class FolderService { createFolder = async (userID: string, name: string, parent: string) => { const newFolderParentList = []; - if (parent && parent !== "/") { + if (parent !== "/") { const parentFolder = await folderDB.getFolderInfo(parent, userID); if (!parentFolder) throw new Error("Parent not found"); diff --git a/src/components/ContextMenu/ContextMenu.tsx b/src/components/ContextMenu/ContextMenu.tsx index d9fcc6f..11defdb 100644 --- a/src/components/ContextMenu/ContextMenu.tsx +++ b/src/components/ContextMenu/ContextMenu.tsx @@ -230,6 +230,7 @@ const ContextMenu: React.FC = memo((props) => { const reloadItems = () => { invalidateFilesCache(); invalidateQuickFilesCache(); + invalidateFoldersCache(); dispatch(resetSelected()); }; diff --git a/src/components/LeftSection/LeftSection.tsx b/src/components/LeftSection/LeftSection.tsx index fa80003..ee524ed 100644 --- a/src/components/LeftSection/LeftSection.tsx +++ b/src/components/LeftSection/LeftSection.tsx @@ -14,7 +14,7 @@ import HomeIconOutline from "../../icons/HomeIconOutline"; const LeftSection = () => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const leftSectionOpen = useAppSelector((state) => state.leftSection.drawOpen); - const { isHome, isTrash, isMedia, isSettings } = useUtils(); + const { isHome, isHomeFolder, isTrash, isMedia, isSettings } = useUtils(); const dispatch = useAppDispatch(); const navigate = useNavigate(); const addNewDisabled = useRef(false); @@ -100,7 +100,9 @@ const LeftSection = () => {
diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 81d9cd8..ade118c 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -27,7 +27,11 @@ export const useUtils = () => { return location.pathname === "/settings"; }, [location.pathname]); - return { isHome, isTrash, isMedia, isSettings }; + const isHomeFolder = useMemo(() => { + return location.pathname.includes("/folder/"); + }, [location.pathname]); + + return { isHome, isTrash, isMedia, isSettings, isHomeFolder }; }; export const useClickOutOfBounds = (