diff --git a/.gitignore b/.gitignore index 0769eae..6ec0a93 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ lerna-debug.log* backend/.vscode/* !backend/.vscode/launch.json -backend/uploads/* -!backend/uploads/.gitkeep +backend/public/uploads/* +!backend/public/uploads/.gitkeep .env node_modules dist diff --git a/backend/config/db.js b/backend/app/config/db.config.js similarity index 100% rename from backend/config/db.js rename to backend/app/config/db.config.js diff --git a/backend/app/controllers/fileSystem.controller.js b/backend/app/controllers/fileSystem.controller.js new file mode 100644 index 0000000..fb133b8 --- /dev/null +++ b/backend/app/controllers/fileSystem.controller.js @@ -0,0 +1,17 @@ +const createFolder = require("./handlers/createFolder.handler"); +const uploadFile = require("./handlers/uploadFile.handler"); +const getItems = require("./handlers/getItems.handler"); +const copyItem = require("./handlers/copyItem.handler"); +const moveItem = require("./handlers/moveItem.handler"); +const renameItem = require("./handlers/renameItem.handler"); +const deleteItem = require("./handlers/deleteItem.handler"); + +module.exports = { + createFolder, + uploadFile, + getItems, + copyItem, + moveItem, + renameItem, + deleteItem, +}; diff --git a/backend/app/controllers/handlers/copyItem.handler.js b/backend/app/controllers/handlers/copyItem.handler.js new file mode 100644 index 0000000..be6419d --- /dev/null +++ b/backend/app/controllers/handlers/copyItem.handler.js @@ -0,0 +1,65 @@ +const FileSystem = require("../../models/FileSystem.model"); +const fs = require("fs"); +const path = require("path"); + +const recursiveCopy = async (sourceItem, destinationFolder) => { + const copyItem = new FileSystem({ + name: sourceItem.name, + isDirectory: sourceItem.isDirectory, + path: `${destinationFolder?.path ?? ""}/${sourceItem.name}`, + parentId: destinationFolder?._id || null, + size: sourceItem.size, + mimeType: sourceItem.mimeType, + }); + + await copyItem.save(); + + const children = await FileSystem.find({ parentId: sourceItem._id }); + + for (const child of children) { + await recursiveCopy(child, copyItem); + } +}; + +const copyItem = async (req, res) => { + try { + const { sourceId, destinationId } = req.body; + const isRootDestination = !destinationId; + + if (!sourceId) { + return res.status(400).json({ error: "sourceId is required!" }); + } + + const sourceItem = await FileSystem.findById(sourceId); + if (!sourceItem) { + return res.status(404).json({ error: "Source File/Folder not found!" }); + } + + const srcFullPath = path.join(__dirname, "../../../public/uploads", sourceItem.path); + + if (isRootDestination) { + const destFullPath = path.join(__dirname, "../../../public/uploads", sourceItem.name); + await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); + await recursiveCopy(sourceItem, null); // Destination Folder -> Root Folder + } else { + const destinationFolder = await FileSystem.findById(destinationId); + if (!destinationFolder || !destinationFolder.isDirectory) { + return res.status(400).json({ error: "Invalid destinationId!" }); + } + const destFullPath = path.join( + __dirname, + "../../../public/uploads", + destinationFolder.path, + sourceItem.name + ); + await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); + await recursiveCopy(sourceItem, destinationFolder); + } + + res.status(200).json({ message: "Item(s) copied successfully!" }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = copyItem; diff --git a/backend/app/controllers/handlers/createFolder.handler.js b/backend/app/controllers/handlers/createFolder.handler.js new file mode 100644 index 0000000..cc8d5ef --- /dev/null +++ b/backend/app/controllers/handlers/createFolder.handler.js @@ -0,0 +1,46 @@ +const FileSystem = require("../../models/FileSystem.model"); +const fs = require("fs"); +const path = require("path"); + +const createFolder = async (req, res) => { + try { + const { name, parentId } = req.body; + + // Path calculation + let folderPath = ""; + if (parentId) { + const parentFolder = await FileSystem.findById(parentId); + if (!parentFolder || !parentFolder.isDirectory) { + return res.status(400).json({ error: "Invalid parent folder" }); + } + folderPath = `${parentFolder.path}/${name}`; + } else { + folderPath = `/${name}`; // Root Folder + } + // + + // Physical folder creation using fs + const fullFolderPath = path.join(__dirname, "../../../public/uploads", folderPath); + if (!fs.existsSync(fullFolderPath)) { + await fs.promises.mkdir(fullFolderPath, { recursive: true }); + } else { + return res.status(400).json({ error: "Folder already exists!" }); + } + // + + const newFolder = new FileSystem({ + name, + isDirectory: true, + path: folderPath, + parentId: parentId || null, + }); + + await newFolder.save(); + + res.status(201).json(newFolder); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = createFolder; diff --git a/backend/app/controllers/handlers/deleteItem.handler.js b/backend/app/controllers/handlers/deleteItem.handler.js new file mode 100644 index 0000000..b23c3df --- /dev/null +++ b/backend/app/controllers/handlers/deleteItem.handler.js @@ -0,0 +1,35 @@ +const FileSystem = require("../../models/FileSystem.model"); +const fs = require("fs"); +const path = require("path"); + +const deleteRecursive = async (item) => { + const children = await FileSystem.find({ parentId: item._id }); + + for (const child of children) { + await deleteRecursive(child); + } + + await FileSystem.findByIdAndDelete(item._id); +}; + +const deleteItem = async (req, res) => { + try { + const { id } = req.params; + + const item = await FileSystem.findById(id); + if (!item) { + return res.status(404).json({ error: "File or Folder not found!" }); + } + + const itemPath = path.join(__dirname, "../../../public/uploads", item.path); + await fs.promises.rm(itemPath, { recursive: true }); + + await deleteRecursive(item); + + res.status(200).json({ message: "File or Folder deleted successfully" }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = deleteItem; diff --git a/backend/app/controllers/handlers/getItems.handler.js b/backend/app/controllers/handlers/getItems.handler.js new file mode 100644 index 0000000..b2cc118 --- /dev/null +++ b/backend/app/controllers/handlers/getItems.handler.js @@ -0,0 +1,12 @@ +const FileSystem = require("../../models/FileSystem.model"); + +const getItems = async (req, res) => { + try { + const files = await FileSystem.find(); + res.status(200).json(files); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = getItems; diff --git a/backend/app/controllers/handlers/moveItem.handler.js b/backend/app/controllers/handlers/moveItem.handler.js new file mode 100644 index 0000000..0eab6be --- /dev/null +++ b/backend/app/controllers/handlers/moveItem.handler.js @@ -0,0 +1,70 @@ +const FileSystem = require("../../models/FileSystem.model"); +const fs = require("fs"); +const path = require("path"); + +const recursiveMove = async (sourceItem, destinationFolder) => { + const moveItem = new FileSystem({ + name: sourceItem.name, + isDirectory: sourceItem.isDirectory, + path: `${destinationFolder?.path ?? ""}/${sourceItem.name}`, + parentId: destinationFolder?._id || null, + size: sourceItem.size, + mimeType: sourceItem.mimeType, + }); + + await moveItem.save(); + await FileSystem.findByIdAndDelete(sourceItem._id); + + const children = await FileSystem.find({ parentId: sourceItem._id }); + for (const child of children) { + await recursiveMove(child, moveItem); + } +}; + +const moveItem = async (req, res) => { + try { + const { sourceId, destinationId } = req.body; + const isRootDestination = !destinationId; + + if (!sourceId) { + return res.status(400).json({ error: "sourceId is required!" }); + } + + const sourceItem = await FileSystem.findById(sourceId); + if (!sourceItem) { + return res.status(404), json({ error: "Source File/Folder not found!" }); + } + + const srcFullPath = path.join(__dirname, "../../../public/uploads", sourceItem.path); + + if (isRootDestination) { + const destFullPath = path.join(__dirname, "../../../public/uploads", sourceItem.name); + await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); + await fs.promises.rm(srcFullPath, { recursive: true }); + + await recursiveMove(sourceItem, null); + } else { + const destinationFolder = await FileSystem.findById(destinationId); + if (!destinationFolder || !destinationFolder.isDirectory) { + return res.status(400).json({ error: "Invalid destinationId!" }); + } + + const destFullPath = path.join( + __dirname, + "../../../public/uploads", + destinationFolder.path, + sourceItem.name + ); + await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); + await fs.promises.rm(srcFullPath, { recursive: true }); + + await recursiveMove(sourceItem, destinationFolder); + } + + res.status(200).json({ message: "Item(s) moved successfully!" }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = moveItem; diff --git a/backend/app/controllers/handlers/renameItem.handler.js b/backend/app/controllers/handlers/renameItem.handler.js new file mode 100644 index 0000000..a3bd8af --- /dev/null +++ b/backend/app/controllers/handlers/renameItem.handler.js @@ -0,0 +1,51 @@ +const FileSystem = require("../../models/FileSystem.model"); +const fs = require("fs"); +const path = require("path"); + +const updateChildernPathRecursive = async (item) => { + const children = await FileSystem.find({ parentId: item._id }); + + for (const child of children) { + child.path = `${item.path}/${child.name}`; + await child.save(); + + if (child.isDirectory) updateChildernPathRecursive(child); + } +}; + +const renameItem = async (req, res) => { + try { + const { id, newName } = req.body; + const item = await FileSystem.findById(id); + if (!item) { + return res.status(404).json({ error: "File or Folder not found!" }); + } + + const parentDir = `${path.dirname(item.path)}`; + const newPath = `${parentDir}${parentDir === "/" ? "" : "/"}${newName}`; + + const oldFullPath = path.join(__dirname, "../../../public/uploads", item.path); + const newFullPath = path.join(__dirname, "../../../public/uploads", newPath); + + if (fs.existsSync(newFullPath)) { + return res.status(400).json({ error: "A file or folder with that name already exists!" }); + } + + await fs.promises.rename(oldFullPath, newFullPath); + + item.name = newName; + item.path = newPath; + + await item.save(); + + if (item.isDirectory) { + await updateChildernPathRecursive(item); + } + + res.status(200).json({ message: "File or Folder renamed successfully!", item }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +module.exports = renameItem; diff --git a/backend/app/controllers/handlers/uploadFile.handler.js b/backend/app/controllers/handlers/uploadFile.handler.js new file mode 100644 index 0000000..f170ea4 --- /dev/null +++ b/backend/app/controllers/handlers/uploadFile.handler.js @@ -0,0 +1,36 @@ +const FileSystem = require("../../models/FileSystem.model"); + +const uploadFile = async (req, res) => { + try { + const { parentId } = req.body; + const file = req.file; + + let filePath = ""; + if (parentId) { + const parentFolder = await FileSystem.findById(parentId); + if (!parentFolder || !parentFolder.isDirectory) { + return res.status(400).json({ error: "Invalid parentId!" }); + } + filePath = `${parentFolder.path}/${file.originalname}`; + } else { + filePath = `/${file.originalname}`; + } + + const newFile = new FileSystem({ + name: file.originalname, + isDirectory: false, + path: filePath, + parentId: parentId || null, + size: file.size, + mimeType: file.mimetype, + }); + + await newFile.save(); + + res.status(201).json(newFile); + } catch (error) { + res.status(500).json({ error: error }); + } +}; + +module.exports = uploadFile; diff --git a/backend/app/middlewares/errorHandler.middleware.js b/backend/app/middlewares/errorHandler.middleware.js new file mode 100644 index 0000000..60886e3 --- /dev/null +++ b/backend/app/middlewares/errorHandler.middleware.js @@ -0,0 +1,11 @@ +const multer = require("multer"); + +const errorHandler = (err, req, res, next) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ error: err.code }); + } + + res.status(500).json({ error: err.message }); +}; + +module.exports = errorHandler; diff --git a/backend/config/multer.js b/backend/app/middlewares/multer.middleware.js similarity index 80% rename from backend/config/multer.js rename to backend/app/middlewares/multer.middleware.js index cddc663..c1cc788 100644 --- a/backend/config/multer.js +++ b/backend/app/middlewares/multer.middleware.js @@ -1,11 +1,11 @@ const multer = require("multer"); const path = require("path"); -const FileSystem = require("../models/FileSystem"); +const FileSystem = require("../models/FileSystem.model"); const fs = require("fs"); const storage = multer.diskStorage({ destination: async (req, file, cb) => { - let uploadPath = path.join(__dirname, "../uploads"); + let uploadPath = path.join(__dirname, "../../public/uploads"); if (req.body.parentId) { try { @@ -13,7 +13,7 @@ const storage = multer.diskStorage({ if (!parentFolder || !parentFolder.isDirectory) { return cb(new Error("Invalid parentId!"), false); } - uploadPath = path.join(__dirname, "../uploads", parentFolder.path); + uploadPath = path.join(__dirname, "../../public/uploads", parentFolder.path); } catch (error) { return cb(error, false); } diff --git a/backend/models/FileSystem.js b/backend/app/models/FileSystem.model.js similarity index 100% rename from backend/models/FileSystem.js rename to backend/app/models/FileSystem.model.js diff --git a/backend/routes/fileSystem.js b/backend/app/routes/fileSystem.routes.js similarity index 53% rename from backend/routes/fileSystem.js rename to backend/app/routes/fileSystem.routes.js index fa2a05b..5e7fb94 100644 --- a/backend/routes/fileSystem.js +++ b/backend/app/routes/fileSystem.routes.js @@ -1,14 +1,14 @@ const express = require("express"); const router = express.Router(); -const fileSystemController = require("../controllers/fileSystemController"); -const upload = require("../config/multer"); +const fileSystemController = require("../controllers/fileSystem.controller"); +const upload = require("../middlewares/multer.middleware"); -router.get("/", fileSystemController.getFiles); router.post("/folder", fileSystemController.createFolder); router.post("/upload", upload.single("file"), fileSystemController.uploadFile); -router.delete("/:id", fileSystemController.delete); -router.patch("/rename", fileSystemController.rename); +router.get("/", fileSystemController.getItems); router.post("/copy", fileSystemController.copyItem); router.put("/move", fileSystemController.moveItem); +router.patch("/rename", fileSystemController.renameItem); +router.delete("/:id", fileSystemController.deleteItem); module.exports = router; diff --git a/backend/controllers/fileSystemController.js b/backend/controllers/fileSystemController.js deleted file mode 100644 index 6209ce9..0000000 --- a/backend/controllers/fileSystemController.js +++ /dev/null @@ -1,303 +0,0 @@ -const FileSystem = require("../models/FileSystem"); -const fs = require("fs"); -const path = require("path"); - -// Get all files + folders -exports.getFiles = async (req, res) => { - try { - const files = await FileSystem.find(); - res.status(200).json(files); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// - -// Create Folder -exports.createFolder = async (req, res) => { - try { - const { name, parentId } = req.body; - - // Path calculation - let folderPath = ""; - if (parentId) { - const parentFolder = await FileSystem.findById(parentId); - if (!parentFolder || !parentFolder.isDirectory) { - return res.status(400).json({ error: "Invalid parent folder" }); - } - folderPath = `${parentFolder.path}/${name}`; - } else { - folderPath = `/${name}`; // Root Folder - } - // - - // Physical folder creation using fs - const fullFolderPath = path.join(__dirname, "../uploads", folderPath); - if (!fs.existsSync(fullFolderPath)) { - await fs.promises.mkdir(fullFolderPath, { recursive: true }); - } else { - return res.status(400).json({ error: "Folder already exists!" }); - } - // - - const newFolder = new FileSystem({ - name, - isDirectory: true, - path: folderPath, - parentId: parentId || null, - }); - - await newFolder.save(); - - res.status(201).json(newFolder); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// - -// Upload File -exports.uploadFile = async (req, res) => { - try { - const { parentId } = req.body; - const file = req.file; - - let filePath = ""; - if (parentId) { - const parentFolder = await FileSystem.findById(parentId); - if (!parentFolder || !parentFolder.isDirectory) { - return res.status(400).json({ error: "Invalid parentId!" }); - } - filePath = `${parentFolder.path}/${file.originalname}`; - } else { - filePath = `/${file.originalname}`; - } - - const newFile = new FileSystem({ - name: file.originalname, - isDirectory: false, - path: filePath, - parentId: parentId || null, - size: file.size, - mimeType: file.mimetype, - }); - - await newFile.save(); - - res.status(201).json(newFile); - } catch (error) { - res.status(500).json({ error: error }); - } -}; -// - -// Delete File/Folder -const deleteRecursive = async (item) => { - const children = await FileSystem.find({ parentId: item._id }); - - for (const child of children) { - await deleteRecursive(child); - } - - await FileSystem.findByIdAndDelete(item._id); -}; - -exports.delete = async (req, res) => { - try { - const { id } = req.params; - - const item = await FileSystem.findById(id); - if (!item) { - return res.status(404).json({ error: "File or Folder not found!" }); - } - - const itemPath = path.join(__dirname, "../uploads", item.path); - await fs.promises.rm(itemPath, { recursive: true }); - - await deleteRecursive(item); - - res.status(200).json({ message: "File or Folder deleted successfully" }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// - -// Rename File/Folder -const updateChildernPathRecursive = async (item) => { - const children = await FileSystem.find({ parentId: item._id }); - - for (const child of children) { - child.path = `${item.path}/${child.name}`; - await child.save(); - - if (child.isDirectory) updateChildernPathRecursive(child); - } -}; - -exports.rename = async (req, res) => { - try { - const { id, newName } = req.body; - const item = await FileSystem.findById(id); - if (!item) { - return res.status(404).json({ error: "File or Folder not found!" }); - } - - const parentDir = `${path.dirname(item.path)}`; - const newPath = `${parentDir}${parentDir === "/" ? "" : "/"}${newName}`; - - const oldFullPath = path.join(__dirname, "../uploads", item.path); - const newFullPath = path.join(__dirname, "../uploads", newPath); - - if (fs.existsSync(newFullPath)) { - return res.status(400).json({ error: "A file or folder with that name already exists!" }); - } - - await fs.promises.rename(oldFullPath, newFullPath); - - item.name = newName; - item.path = newPath; - - await item.save(); - - if (item.isDirectory) { - await updateChildernPathRecursive(item); - } - - res.status(200).json({ message: "File or Folder renamed successfully!", item }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// - -// Copy File/Folder -const recursiveCopy = async (sourceItem, destinationFolder) => { - const copyItem = new FileSystem({ - name: sourceItem.name, - isDirectory: sourceItem.isDirectory, - path: `${destinationFolder?.path ?? ""}/${sourceItem.name}`, - parentId: destinationFolder?._id || null, - size: sourceItem.size, - mimeType: sourceItem.mimeType, - }); - - await copyItem.save(); - - const children = await FileSystem.find({ parentId: sourceItem._id }); - - for (const child of children) { - await recursiveCopy(child, copyItem); - } -}; - -exports.copyItem = async (req, res) => { - try { - const { sourceId, destinationId } = req.body; - const isRootDestination = !destinationId; - - if (!sourceId) { - return res.status(400).json({ error: "sourceId is required!" }); - } - - const sourceItem = await FileSystem.findById(sourceId); - if (!sourceItem) { - return res.status(404).json({ error: "Source File/Folder not found!" }); - } - - const srcFullPath = path.join(__dirname, "../uploads", sourceItem.path); - - if (isRootDestination) { - const destFullPath = path.join(__dirname, "../uploads", sourceItem.name); - await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); - await recursiveCopy(sourceItem, null); // Destination Folder -> Root Folder - } else { - const destinationFolder = await FileSystem.findById(destinationId); - if (!destinationFolder || !destinationFolder.isDirectory) { - return res.status(400).json({ error: "Invalid destinationId!" }); - } - const destFullPath = path.join( - __dirname, - "../uploads", - destinationFolder.path, - sourceItem.name - ); - await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); - await recursiveCopy(sourceItem, destinationFolder); - } - - res.status(200).json({ message: "Item(s) copied successfully!" }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// - -// Move File/Folder -// We want the source file / folder to copy in the destination directory. -// And move from its original location. -const recursiveMove = async (sourceItem, destinationFolder) => { - const moveItem = new FileSystem({ - name: sourceItem.name, - isDirectory: sourceItem.isDirectory, - path: `${destinationFolder?.path ?? ""}/${sourceItem.name}`, - parentId: destinationFolder?._id || null, - size: sourceItem.size, - mimeType: sourceItem.mimeType, - }); - - await moveItem.save(); - await FileSystem.findByIdAndDelete(sourceItem._id); - - const children = await FileSystem.find({ parentId: sourceItem._id }); - for (const child of children) { - await recursiveMove(child, moveItem); - } -}; - -exports.moveItem = async (req, res) => { - try { - const { sourceId, destinationId } = req.body; - const isRootDestination = !destinationId; - - if (!sourceId) { - return res.status(400).json({ error: "sourceId is required!" }); - } - - const sourceItem = await FileSystem.findById(sourceId); - if (!sourceItem) { - return res.status(404), json({ error: "Source File/Folder not found!" }); - } - - const srcFullPath = path.join(__dirname, "../uploads", sourceItem.path); - - if (isRootDestination) { - const destFullPath = path.join(__dirname, "../uploads", sourceItem.name); - await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); - await fs.promises.rm(srcFullPath, { recursive: true }); - - await recursiveMove(sourceItem, null); - } else { - const destinationFolder = await FileSystem.findById(destinationId); - if (!destinationFolder || !destinationFolder.isDirectory) { - return res.status(400).json({ error: "Invalid destinationId!" }); - } - - const destFullPath = path.join( - __dirname, - "../uploads", - destinationFolder.path, - sourceItem.name - ); - await fs.promises.cp(srcFullPath, destFullPath, { recursive: true }); - await fs.promises.rm(srcFullPath, { recursive: true }); - - await recursiveMove(sourceItem, destinationFolder); - } - - res.status(200).json({ message: "Item(s) moved successfully!" }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; -// diff --git a/backend/uploads/.gitkeep b/backend/public/uploads/.gitkeep similarity index 100% rename from backend/uploads/.gitkeep rename to backend/public/uploads/.gitkeep diff --git a/backend/server.js b/backend/server.js index a225962..eb8e999 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,8 +1,8 @@ const express = require("express"); -const connectDB = require("./config/db"); +const connectDB = require("./app/config/db.config"); const cors = require("cors"); -const fileSystemRoutes = require("./routes/fileSystem"); -const multer = require("multer"); +const fileSystemRoutes = require("./app/routes/fileSystem.routes"); +const errorHandler = require("./app/middlewares/errorHandler.middleware"); const app = express(); @@ -12,23 +12,15 @@ connectDB(); // CORS setup app.use(cors()); -// Middleware to parse URL-encoded body +// Middlewares to parse URL-encoded body & JSON app.use(express.urlencoded({ extended: true })); - -// Middleware to parse JSON app.use(express.json()); // Routes app.use("/api/file-system", fileSystemRoutes); // Error handling middleware -app.use((err, req, res, next) => { - if (err instanceof multer.MulterError) { - return res.status(400).json({ error: err.code }); - } - - res.status(500).json({ error: err.message }); -}); +app.use(errorHandler); const PORT = process.env.PORT || 3000;