BUG: Having permission issues with the deleting and creating.
This commit is contained in:
parent
a2deb2252a
commit
4cd88c6505
@ -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/<timstamp>/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
|
||||
*/
|
||||
|
||||
@ -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 (
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
title="Delete selected files/directories"
|
||||
className="hover:bg-gray-200 p-1.5 mr-1 rounded-full transition-colors duration-150 disabled:text-gray-500 disabled:hover:bg-red-300 disabled:cursor-not-allowed text-black">
|
||||
<svg className="h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18 6L17.1991 18.0129C17.129 19.065 17.0939 19.5911 16.8667 19.99C16.6666 20.3412 16.3648 20.6235 16.0011 20.7998C15.588 21 15.0607 21 14.0062 21H9.99377C8.93927 21 8.41202 21 7.99889 20.7998C7.63517 20.6235 7.33339 20.3412 7.13332 19.99C6.90607 19.5911 6.871 19.065 6.80086 18.0129L6 6M4 6H20M16 6L15.7294 5.18807C15.4671 4.40125 15.3359 4.00784 15.0927 3.71698C14.8779 3.46013 14.6021 3.26132 14.2905 3.13878C13.9376 3 13.523 3 12.6936 3H11.3064C10.477 3 10.0624 3 9.70951 3.13878C9.39792 3.26132 9.12208 3.46013 8.90729 3.71698C8.66405 4.00784 8.53292 4.40125 8.27064 5.18807L8 6"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 (
|
||||
<div
|
||||
className="w-2/3 mt-8 border-b-1 border-gray-400 bg-white flex items-center truncate">
|
||||
@ -84,6 +102,9 @@ export default function PathDisplay({path, updatePath, backHome, backArrow, enab
|
||||
<BackButton onClick={backArrow} enabled={enabled}/>
|
||||
{path.map((seg, idx) => <PathElement name={seg} key={idx} index={idx} onClick={updatePath}/>)}
|
||||
<div className="ml-auto h-full flex items-center">
|
||||
<DeleteButton onClick={remove} enabled={removeEnable}/>
|
||||
</div>
|
||||
<div className="h-full flex items-center">
|
||||
<CreateButton onClick={create}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<div className="w-full min-h-screen h-screen pb-8">
|
||||
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading}/>
|
||||
@ -401,10 +446,12 @@ export default function Dashboard() {
|
||||
loading={contentLoading}/>}
|
||||
|
||||
<PathDisplay path={path} updatePath={updatePath} backHome={backHome} backArrow={backArrow}
|
||||
enabled={path.length > defaultPath.length} create={createDir}/>
|
||||
enabled={path.length > defaultPath.length} create={createDir} remove={removeSelected}
|
||||
removeEnable={selected.length > 0}/>
|
||||
<div className="w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||
{childrenLoading && <ChildrenLoading/>}
|
||||
<DirectoryList dirs={files} showHidden={showHidden} appendPath={appendPath}
|
||||
// TODO: Rework the toggleSelected functionality
|
||||
toggleSelected={toggleSelected} toggleEditing={toggleEditing}/>
|
||||
</div>
|
||||
<div className="w-2/3 flex justify-end items-center">
|
||||
|
||||
Reference in New Issue
Block a user