From 4cd88c65058330f9bdca40790ffbe7dbc10da05f Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Wed, 26 Mar 2025 23:21:16 -0700 Subject: [PATCH] BUG: Having permission issues with the deleting and creating. --- backend/src/server.ts | 67 +++++++++++++++++++++---- frontend/src/components/PathDisplay.jsx | 23 ++++++++- frontend/src/pages/Dashboard.jsx | 51 ++++++++++++++++++- 3 files changed, 127 insertions(+), 14 deletions(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index db2627f..bffd8c1 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -3,6 +3,7 @@ import {Healthcheck} from "./healthcheck"; import {printEndpoints, validateHash} from "./utils"; import {LogRequestMiddleware} from "./log"; import * as fs from "node:fs"; +import {mkdirSync, readFileSync, renameSync, rmSync, writeFileSync} from "node:fs"; import {entry} from "./entry"; import cors from "cors"; import archiver from "archiver"; @@ -12,13 +13,14 @@ import {verifyToken} from "./authenicate"; import jwt from "jsonwebtoken"; import {config} from "dotenv"; import Multer from "multer"; -import {mkdirSync, readFileSync, rmSync, writeFileSync} from "fs"; /** * App details */ const PORT = 5000; const APP: Express = express(); +// TODO: BACK TO NORMAL PATH +// const ROOT: string = "/media/vault"; const ROOT: string = "/home/azpect"; /** @@ -44,6 +46,16 @@ const corsOptions: cors.CorsOptions = { }; APP.use(cors(corsOptions)); +/** + * File upload settings + */ +const upload = Multer({ + dest: "tmp/", + limits: { + fileSize: 1024 * 1024 * 100 + }, +}); + /** * Apply middleware, this must be done before the routes are created. */ @@ -213,12 +225,6 @@ v1.post("/update", (req: Request, res: Response): void => { } }); -const upload = Multer({ - dest: "tmp/", - limits: { - fileSize: 1024 * 1024 * 100 - }, -}); /** * Custom type for the multer uploads. @@ -252,7 +258,7 @@ v1.post("/upload", upload.array("files"), (req: Request, res: Response) => { const newPath: string = path.join("/", ...cwd, file.originalname); // Write the new file - writeFileSync(newPath, data); + writeFileSync(newPath, data, {mode: "666"}); // Delete the tmp file using a relative path rmSync("./" + file.path); @@ -279,10 +285,9 @@ v1.post("/create", (req: Request, res: Response): void => { try { const newPath: string = path.join("/", ...cwd, name); if (name.endsWith("/")) { - mkdirSync(newPath, {mode: "644"}) + mkdirSync(newPath, {recursive: true, mode: "666"}) } else { - console.log("NOT DIR"); - writeFileSync(newPath, "", {mode: "644"}) + writeFileSync(newPath, "", {mode: "666"}) } } catch (error: any) { if (error.code === 'EACCES') { @@ -303,6 +308,46 @@ v1.post("/create", (req: Request, res: Response): void => { res.status(201).json({code: 201, message: "Success"}); }); +/** + * Files are not deleted from the file system, just moved to a hidden folder where + * they can be recovered if needed. + * + * The hidden folder will be called `.trash` stored in the root of the mount, and + * the files entire paths will be created. + * + * e.g., Deleting /media/vault/main.txt will get moved to + * `/media/vault/.trash//media/vault/main.txt` + * assuming /media/vault is the mounted root. + * The timestamp will also be appended to allow files with the same path to be deleted. + */ +v1.post("/remove", (req: Request, res: Response): void => { + // Get the array of paths and the root + const {files, root} = req.body; + + // Stores the name of the trash directory + const trashDir: string = ".trash"; + const timestamp: string = (new Date()).toISOString(); + + console.log(timestamp); + + for (const file of files) { + const oldPath = path.join("/", ...file); + const newPath = path.join("/", ...root, trashDir, timestamp, ...file); + + console.log(oldPath, " -> ", newPath); + try { + mkdirSync(path.dirname(newPath), {recursive: true, mode: "666"}); + renameSync(oldPath, newPath) + } catch (error) { + console.error(error); + res.status(500).json({code: 500, message: `Failed to delete. ${error}`}) + return; + } + } + + res.status(201).json({code: 201, message: `Deleted ${files.length} files.`}); +}); + /** * Apply the routes to the server */ diff --git a/frontend/src/components/PathDisplay.jsx b/frontend/src/components/PathDisplay.jsx index 0927f46..b108c23 100644 --- a/frontend/src/components/PathDisplay.jsx +++ b/frontend/src/components/PathDisplay.jsx @@ -2,6 +2,7 @@ * Takes the user back to the home directory. The onClick prop * is called when the button is clicked. * @param onClick {function} + * @param enabled * @returns {JSX.Element} * @constructor */ @@ -50,6 +51,21 @@ function CreateButton({onClick}) { ) } +function DeleteButton({onClick, enabled}) { + return ( + + ); +} + /** * * @param name {string} @@ -73,10 +89,12 @@ function PathElement({name, index, onClick}) { * @param backArrow {function} * @param enabled {boolean} * @param create {function} + * @param remove {function} + * @param removeEnable {boolean} * @returns {JSX.Element} * @constructor */ -export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create}) { +export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create, remove, removeEnable}) { return (
@@ -84,6 +102,9 @@ export default function PathDisplay({path, updatePath, backHome, backArrow, enab {path.map((seg, idx) => )}
+ +
+
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 352bb0a..d946d09 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -12,7 +12,9 @@ import CreateDirectory from "../components/CreateDirectory.jsx"; export default function Dashboard() { // Store the default path - const defaultPath = ["media", "vault"]; + // TODO: BACK TO NORMAL PATH + // const defaultPath = ["media", "vault"]; + const defaultPath = ["home", "azpect"]; /** * URL To the backend web server. @@ -387,6 +389,49 @@ export default function Dashboard() { }); }; + /** + * Remove the selected files. + */ + const removeSelected = () => { + if (selected.length === 0) { + return setError("Please select files or directories to delete"); + } + + const remove = async (files) => { + const resp = await fetch(`${backendUrl}/v1/remove`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "authorization": `Bearer ${token}`, + }, + body: JSON.stringify({files, root: defaultPath}) + }); + + if (!resp.ok) { + const data = await resp.json() + setError(data.error); + return data; + } + + return await resp.json(); + }; + + // Files are stored as arrays of paths + const files = []; + for (const file of selected) { + files.push([...path, file]); + } + + remove(files).then((data) => { + if (data.code === 201) { + console.log(data); + setSelected([]); + } + }).catch((error) => { + setError(error); + }); + }; + return (
@@ -401,10 +446,12 @@ export default function Dashboard() { loading={contentLoading}/>} defaultPath.length} create={createDir}/> + enabled={path.length > defaultPath.length} create={createDir} remove={removeSelected} + removeEnable={selected.length > 0}/>
{childrenLoading && }