(FEAT): Can no delete, but it is buggy.
It selects the first element after a delete, not sure why and don't want to worry about it right now.
This commit is contained in:
parent
4cd88c6505
commit
b31626dce9
@ -1,17 +1,17 @@
|
||||
import express, {Express, Request, Response, Router} from "express";
|
||||
import {Healthcheck} from "./healthcheck";
|
||||
import {printEndpoints, validateHash} from "./utils";
|
||||
import {LogRequestMiddleware} from "./log";
|
||||
import express, { Express, Request, Response, Router } from "express";
|
||||
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 { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { entry } from "./entry";
|
||||
import cors from "cors";
|
||||
import archiver from "archiver";
|
||||
import {appendDirectoryToArchive, appendFileToArchive} from "./download";
|
||||
import { appendDirectoryToArchive, appendFileToArchive } from "./download";
|
||||
import path from "node:path";
|
||||
import {verifyToken} from "./authenicate";
|
||||
import { verifyToken } from "./authenicate";
|
||||
import jwt from "jsonwebtoken";
|
||||
import {config} from "dotenv";
|
||||
import { config } from "dotenv";
|
||||
import Multer from "multer";
|
||||
|
||||
/**
|
||||
@ -27,7 +27,7 @@ const ROOT: string = "/home/azpect";
|
||||
* Configure the .env file, this is for testing only, should be ignored in production.
|
||||
*/
|
||||
try {
|
||||
config({path: ".env"});
|
||||
config({ path: ".env" });
|
||||
} catch (error) {
|
||||
console.error(`Could not load the .env file. If this is a production environment, this is normal!`);
|
||||
}
|
||||
@ -62,7 +62,7 @@ const upload = Multer({
|
||||
APP.use(verifyToken);
|
||||
APP.use(LogRequestMiddleware);
|
||||
APP.use(express.json());
|
||||
APP.use(express.urlencoded({extended: true}));
|
||||
APP.use(express.urlencoded({ extended: true }));
|
||||
|
||||
/**
|
||||
* Create routes for modular routing
|
||||
@ -87,22 +87,22 @@ v1.get("/healthcheck", (req: Request, res: Response): void => {
|
||||
*/
|
||||
v1.post("/login", (req: Request, res: Response): void => {
|
||||
// Get info from body
|
||||
const {username, password} = req.body;
|
||||
const { username, password } = req.body;
|
||||
|
||||
// Get required info from the environment and validate
|
||||
if (process.env["FILE_GOPHERNEST_USER"] === username && validateHash(password, process.env["FILE_GOPHERNEST_PASSWORD"] as string)) {
|
||||
// Get the secret from the env
|
||||
const jwt_secret: string | undefined = process.env["FILE_GOPHERNEST_JWT_SECRET"];
|
||||
if (!jwt_secret) {
|
||||
res.status(500).json({code: 500, message: "JSON web tokens are not configured."});
|
||||
res.status(500).json({ code: 500, message: "JSON web tokens are not configured." });
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the token
|
||||
const token: string = jwt.sign({username}, jwt_secret, {expiresIn: "30d"});
|
||||
res.status(200).json({code: 200, token});
|
||||
const token: string = jwt.sign({ username }, jwt_secret, { expiresIn: "30d" });
|
||||
res.status(200).json({ code: 200, token });
|
||||
} else {
|
||||
res.status(404).json({code: 404, message: "Invalid credentials. Please try again!"});
|
||||
res.status(404).json({ code: 404, message: "Invalid credentials. Please try again!" });
|
||||
}
|
||||
});
|
||||
|
||||
@ -139,16 +139,16 @@ v1.get("/children", (req: Request, res: Response): void => {
|
||||
*/
|
||||
v1.post("/download", (req: Request, res: Response): void => {
|
||||
// Get the files from the body
|
||||
const {filePaths} = req.body;
|
||||
const { filePaths } = req.body;
|
||||
|
||||
// Validate the path array
|
||||
if (!filePaths || !Array.isArray(filePaths) || filePaths.length === 0) {
|
||||
res.status(400).send({code: 400, error: 'Invalid file paths provided.'});
|
||||
res.status(400).send({ code: 400, error: 'Invalid file paths provided.' });
|
||||
return;
|
||||
}
|
||||
|
||||
const archive: archiver.Archiver = archiver('zip', {
|
||||
zlib: {level: 9}, // Compression leve
|
||||
zlib: { level: 9 }, // Compression leve
|
||||
});
|
||||
|
||||
// Set the file headers
|
||||
@ -176,7 +176,7 @@ v1.post("/download", (req: Request, res: Response): void => {
|
||||
|
||||
// Return errors
|
||||
archive.on('error', (err): void => {
|
||||
res.status(500).send({error: err.message});
|
||||
res.status(500).send({ error: err.message });
|
||||
});
|
||||
|
||||
archive.finalize();
|
||||
@ -196,16 +196,16 @@ v1.get("/content", (req: Request, res: Response): void => {
|
||||
// Ensure the file isn't something we can't edit
|
||||
const ext: string = path.extname(tgtPath).slice(1);
|
||||
if (INVALID_EXTS.includes(ext)) {
|
||||
res.status(400).json({error: `Cannot edit files of type *.${ext}`, code: 400});
|
||||
res.status(400).json({ error: `Cannot edit files of type *.${ext}`, code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read the file and return it
|
||||
const content = fs.readFileSync(tgtPath, {encoding: "utf-8", flag: "r"})
|
||||
res.status(200).json({content, code: 200});
|
||||
const content = fs.readFileSync(tgtPath, { encoding: "utf-8", flag: "r" })
|
||||
res.status(200).json({ content, code: 200 });
|
||||
} catch (err) {
|
||||
res.status(500).json({error: `An error occurred on the server. ${err}`, code: 500});
|
||||
res.status(500).json({ error: `An error occurred on the server. ${err}`, code: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -215,13 +215,13 @@ v1.get("/content", (req: Request, res: Response): void => {
|
||||
*/
|
||||
v1.post("/update", (req: Request, res: Response): void => {
|
||||
// Get path and content from the request
|
||||
const {path, content} = req.body;
|
||||
const { path, content } = req.body;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(path, content);
|
||||
res.status(200).json({code: 200, message: "Success"});
|
||||
res.status(200).json({ code: 200, message: "Success" });
|
||||
} catch (error) {
|
||||
res.status(500).json({code: 500, error})
|
||||
res.status(500).json({ code: 500, error })
|
||||
}
|
||||
});
|
||||
|
||||
@ -258,54 +258,68 @@ 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, {mode: "666"});
|
||||
writeFileSync(newPath, data, { mode: "666" });
|
||||
|
||||
// Delete the tmp file using a relative path
|
||||
rmSync("./" + file.path);
|
||||
} catch (error: any) {
|
||||
if (error.code === 'EACCES') {
|
||||
return res.status(403).json({code: 403, error: "Permission denied."}); // Specific error
|
||||
return res.status(403).json({ code: 403, error: "Permission denied." }); // Specific error
|
||||
} else if (error.code === 'ENOSPC') {
|
||||
return res.status(507).json({code: 507, error: "Insufficient storage."}); // Specific error
|
||||
return res.status(507).json({ code: 507, error: "Insufficient storage." }); // Specific error
|
||||
} else if (error instanceof TypeError) {
|
||||
return res.status(400).json({code: 400, error: "Invalid data type."}); // Example of instance check
|
||||
return res.status(400).json({ code: 400, error: "Invalid data type." }); // Example of instance check
|
||||
} else {
|
||||
return res.status(500).json({code: 500, error: "Error processing file."}); // Generic error
|
||||
return res.status(500).json({ code: 500, error: "Error processing file." }); // Generic error
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
res.status(200).json({code: 200, message: "Success"});
|
||||
res.status(200).json({ code: 200, message: "Success" });
|
||||
});
|
||||
|
||||
v1.post("/create", (req: Request, res: Response): void => {
|
||||
// Generate the path to create
|
||||
const {cwd, name} = req.body;
|
||||
const { cwd, name } = req.body;
|
||||
|
||||
try {
|
||||
const newPath: string = path.join("/", ...cwd, name);
|
||||
if (name.endsWith("/")) {
|
||||
mkdirSync(newPath, {recursive: true, mode: "666"})
|
||||
mkdirSync(newPath, { recursive: true, mode: "666" })
|
||||
} else {
|
||||
writeFileSync(newPath, "", {mode: "666"})
|
||||
writeFileSync(newPath, "", { mode: "666" })
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.code === 'EACCES') {
|
||||
res.status(403).json({code: 403, error: "Permission denied."}); // Specific error
|
||||
res.status(403).json({ code: 403, error: "Permission denied." }); // Specific error
|
||||
return;
|
||||
} else if (error.code === 'ENOSPC') {
|
||||
res.status(507).json({code: 507, error: "Insufficient storage."}); // Specific error
|
||||
res.status(507).json({ code: 507, error: "Insufficient storage." }); // Specific error
|
||||
return;
|
||||
} else if (error instanceof TypeError) {
|
||||
res.status(400).json({code: 400, error: "Invalid data type."}); // Example of instance check
|
||||
res.status(400).json({ code: 400, error: "Invalid data type." }); // Example of instance check
|
||||
return;
|
||||
} else {
|
||||
res.status(500).json({code: 500, error: "Error processing directory."}); // Generic error
|
||||
res.status(500).json({ code: 500, error: "Error processing directory." }); // Generic error
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json({code: 201, message: "Success"});
|
||||
res.status(201).json({ code: 201, message: "Success" });
|
||||
});
|
||||
|
||||
v1.post("/delete", (req: Request, res: Response): void => {
|
||||
// Get the array of paths and the root
|
||||
const { files, root } = req.body;
|
||||
|
||||
// Delete the files
|
||||
// NOTE: These are not moved somewhere, they're just raw deleted
|
||||
for (const file of files) {
|
||||
const absPath = "/" + path.join(...file);
|
||||
rmSync(absPath, { recursive: true });
|
||||
}
|
||||
|
||||
res.status(201).json({ code: 201, message: `Deleted ${files.length} files.` });
|
||||
});
|
||||
|
||||
/**
|
||||
@ -322,7 +336,7 @@ v1.post("/create", (req: Request, res: Response): void => {
|
||||
*/
|
||||
v1.post("/remove", (req: Request, res: Response): void => {
|
||||
// Get the array of paths and the root
|
||||
const {files, root} = req.body;
|
||||
const { files, root } = req.body;
|
||||
|
||||
// Stores the name of the trash directory
|
||||
const trashDir: string = ".trash";
|
||||
@ -336,16 +350,16 @@ v1.post("/remove", (req: Request, res: Response): void => {
|
||||
|
||||
console.log(oldPath, " -> ", newPath);
|
||||
try {
|
||||
mkdirSync(path.dirname(newPath), {recursive: true, mode: "666"});
|
||||
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}`})
|
||||
res.status(500).json({ code: 500, message: `Failed to delete. ${error}` })
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json({code: 201, message: `Deleted ${files.length} files.`});
|
||||
res.status(201).json({ code: 201, message: `Deleted ${files.length} files.` });
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DirectoryList from "../components/DirectoryList.jsx";
|
||||
import PathDisplay from "../components/PathDisplay.jsx";
|
||||
import Navbar from "../components/Navbar.jsx";
|
||||
@ -58,7 +58,7 @@ export default function Dashboard() {
|
||||
return t;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFiles = () => {
|
||||
const getData = async (token) => {
|
||||
const response = await fetch(`${backendUrl}/v1/children?path=/${path.join("/")}`, {
|
||||
method: "GET",
|
||||
@ -90,7 +90,10 @@ export default function Dashboard() {
|
||||
});
|
||||
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFiles();
|
||||
}, [path, uploading, creating]);
|
||||
|
||||
|
||||
@ -172,7 +175,7 @@ export default function Dashboard() {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({filePaths: paths}),
|
||||
body: JSON.stringify({ filePaths: paths }),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json();
|
||||
@ -236,7 +239,7 @@ export default function Dashboard() {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({path, content}),
|
||||
body: JSON.stringify({ path, content }),
|
||||
})
|
||||
if (!resp.ok) {
|
||||
setError("An error occurred when saving the file. Please try again.");
|
||||
@ -368,7 +371,7 @@ export default function Dashboard() {
|
||||
"Content-Type": "application/json",
|
||||
"authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({name, cwd})
|
||||
body: JSON.stringify({ name, cwd })
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
@ -398,13 +401,13 @@ export default function Dashboard() {
|
||||
}
|
||||
|
||||
const remove = async (files) => {
|
||||
const resp = await fetch(`${backendUrl}/v1/remove`, {
|
||||
const resp = await fetch(`${backendUrl}/v1/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({files, root: defaultPath})
|
||||
body: JSON.stringify({ files, root: defaultPath })
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
@ -426,6 +429,9 @@ export default function Dashboard() {
|
||||
if (data.code === 201) {
|
||||
console.log(data);
|
||||
setSelected([]);
|
||||
|
||||
// Fetch the new files & deselect everything
|
||||
fetchFiles();
|
||||
}
|
||||
}).catch((error) => {
|
||||
setError(error);
|
||||
@ -434,25 +440,25 @@ export default function Dashboard() {
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen h-screen pb-8">
|
||||
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading}/>
|
||||
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading} />
|
||||
<div className="h-full w-full flex flex-col items-center justify-center pb-8">
|
||||
{downloadLoading && <DownloadLoading/>}
|
||||
{creating && <CreateDirectory close={closeCreate} create={createDirectory}/>}
|
||||
{uploading && <Uploader close={toggleUploading} upload={upload}/>}
|
||||
{downloadLoading && <DownloadLoading />}
|
||||
{creating && <CreateDirectory close={closeCreate} create={createDirectory} />}
|
||||
{uploading && <Uploader close={toggleUploading} upload={upload} />}
|
||||
|
||||
{error && <Error error={error} clear={clearError}/>}
|
||||
{error && <Error error={error} clear={clearError} />}
|
||||
{(editing !== "" && !error) &&
|
||||
<Editor content={editingFileContent} path={editing} exit={exitFile} saveExit={exitAndSaveFile}
|
||||
loading={contentLoading}/>}
|
||||
loading={contentLoading} />}
|
||||
|
||||
<PathDisplay path={path} updatePath={updatePath} backHome={backHome} backArrow={backArrow}
|
||||
enabled={path.length > defaultPath.length} create={createDir} remove={removeSelected}
|
||||
removeEnable={selected.length > 0}/>
|
||||
removeEnable={selected.length > 0} />
|
||||
<div className="w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||
{childrenLoading && <ChildrenLoading/>}
|
||||
{childrenLoading && <ChildrenLoading />}
|
||||
<DirectoryList dirs={files} showHidden={showHidden} appendPath={appendPath}
|
||||
// TODO: Rework the toggleSelected functionality
|
||||
toggleSelected={toggleSelected} toggleEditing={toggleEditing}/>
|
||||
toggleSelected={toggleSelected} toggleEditing={toggleEditing} />
|
||||
</div>
|
||||
<div className="w-2/3 flex justify-end items-center">
|
||||
<label className="text-sm mx-2" htmlFor="showHiddenItems">Show Hidden Items</label>
|
||||
@ -461,7 +467,7 @@ export default function Dashboard() {
|
||||
name="showHiddenItems"
|
||||
type="checkbox"
|
||||
checked={showHidden}
|
||||
onClick={toggleHidden}/>
|
||||
onClick={toggleHidden} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user