mirror of
https://github.com/MedzikUser/HomeDisk.git
synced 2024-08-14 21:46:53 +00:00
feat: add create folder feature
This commit is contained in:
parent
ab7f1bc4ea
commit
403ed4199e
11 changed files with 183 additions and 7 deletions
41
server/src/fs/create_dir.rs
Normal file
41
server/src/fs/create_dir.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use std::fs;
|
||||
|
||||
use axum::{extract::rejection::JsonRejection, Extension, Json};
|
||||
use axum_auth::AuthBearer;
|
||||
use homedisk_database::Database;
|
||||
use homedisk_types::fs::create_dir::{Request, Response};
|
||||
use homedisk_types::{
|
||||
config::types::Config,
|
||||
errors::{FsError, ServerError},
|
||||
};
|
||||
|
||||
use crate::fs::validate_path;
|
||||
use crate::middleware::{find_user, validate_json, validate_jwt};
|
||||
|
||||
pub async fn handle(
|
||||
Extension(db): Extension<Database>,
|
||||
Extension(config): Extension<Config>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
request: Result<Json<Request>, JsonRejection>,
|
||||
) -> Result<Json<Response>, ServerError> {
|
||||
let Json(request) = validate_json::<Request>(request)?;
|
||||
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
|
||||
|
||||
// validate the `path` can be used
|
||||
validate_path(&request.path)?;
|
||||
|
||||
// search for a user by UUID from a token
|
||||
let user = find_user(db, token.claims.sub).await?;
|
||||
|
||||
// directory where the file will be placed
|
||||
let path = format!(
|
||||
"{user_dir}/{req_dir}",
|
||||
user_dir = user.user_dir(&config.storage.path),
|
||||
req_dir = request.path
|
||||
);
|
||||
|
||||
fs::create_dir_all(path)
|
||||
.map_err(|err| ServerError::FsError(FsError::CreateDirectory(err.to_string())))?;
|
||||
|
||||
Ok(Json(Response { created: true }))
|
||||
}
|
|
@ -6,10 +6,7 @@ use axum::Extension;
|
|||
use axum_auth::AuthBearer;
|
||||
use homedisk_database::Database;
|
||||
use homedisk_types::fs::upload::Pagination;
|
||||
use homedisk_types::{
|
||||
config::types::Config,
|
||||
errors::ServerError,
|
||||
};
|
||||
use homedisk_types::{config::types::Config, errors::ServerError};
|
||||
|
||||
use crate::middleware::{find_user, validate_jwt};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod create_dir;
|
||||
pub mod delete;
|
||||
pub mod download;
|
||||
pub mod list;
|
||||
|
@ -11,6 +12,7 @@ pub fn app() -> axum::Router {
|
|||
.route("/upload", post(upload::handle))
|
||||
.route("/delete", delete(upload::handle))
|
||||
.route("/download", get(download::handle))
|
||||
.route("/createdir", post(create_dir::handle))
|
||||
}
|
||||
|
||||
pub fn validate_path(path: &str) -> Result<(), homedisk_types::errors::ServerError> {
|
||||
|
|
|
@ -14,6 +14,9 @@ pub enum Error {
|
|||
#[error("create file - {0}")]
|
||||
CreateFile(String),
|
||||
|
||||
#[error("create dir - {0}")]
|
||||
CreateDirectory(String),
|
||||
|
||||
#[error("delete file - {0}")]
|
||||
DeleteFile(String),
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ impl axum::response::IntoResponse for ServerError {
|
|||
FsError::FileDoesNotExist => StatusCode::BAD_REQUEST,
|
||||
FsError::MultipartError => StatusCode::BAD_REQUEST,
|
||||
FsError::CreateFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::CreateDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::DeleteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::DeleteDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
|
11
types/src/fs/create_dir.rs
Normal file
11
types/src/fs/create_dir.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Request {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Response {
|
||||
pub created: bool,
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod create_dir;
|
||||
pub mod delete;
|
||||
pub mod download;
|
||||
pub mod list;
|
||||
|
|
34
website/api_utils/create-directory.ts
Normal file
34
website/api_utils/create-directory.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import axios from './axios'
|
||||
|
||||
export default async function createDir(path: string, token: string): Promise<any> {
|
||||
const request = axios.post("/fs/createdir", {
|
||||
path
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
})
|
||||
|
||||
const response = request
|
||||
.then(response => {
|
||||
const { data } = response
|
||||
return data
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.response?.data?.error_message) {
|
||||
const error = err.response.data.error_message
|
||||
|
||||
if (error.toString() == "[object Object]") {
|
||||
Object.keys(error).forEach(key => {
|
||||
throw new Error(key)
|
||||
})
|
||||
}
|
||||
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(err)
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
|
@ -2,7 +2,8 @@ import list from "./list"
|
|||
import login from "./login"
|
||||
import register from "./register"
|
||||
import upload from "./upload"
|
||||
import createDir from "./create-directory"
|
||||
|
||||
const api = { list, login, register, upload }
|
||||
const api = { list, login, register, upload, createDir }
|
||||
|
||||
export default api
|
||||
|
|
71
website/components/user/modals/create-folder.tsx
Normal file
71
website/components/user/modals/create-folder.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Backdrop, Box, Button, Fade, Link, Modal, TextField } from "@mui/material"
|
||||
import { useCookies } from "react-cookie"
|
||||
import api from '../../../api_utils'
|
||||
import style from './style'
|
||||
|
||||
function CreateFolderModal({ open, setOpen, refresh }: Props) {
|
||||
const [name, setName] = useState("")
|
||||
|
||||
const [cookies] = useCookies(["token"])
|
||||
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = event => {
|
||||
const value = event.target.value
|
||||
setName(value)
|
||||
}
|
||||
|
||||
// handle click "Enter (Return)"
|
||||
const handleKeyPress = (event: React.KeyboardEvent) => {
|
||||
if (event.keyCode === 13 || event.which === 13 || event.charCode === 13) {
|
||||
handle()
|
||||
}
|
||||
}
|
||||
|
||||
const handle = () => {
|
||||
setOpen(false)
|
||||
|
||||
const request = api.createDir(name, cookies.token)
|
||||
|
||||
request
|
||||
.then(refresh)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
closeAfterTransition
|
||||
BackdropComponent={Backdrop}
|
||||
BackdropProps={{
|
||||
timeout: 500,
|
||||
}}
|
||||
>
|
||||
<Fade in={open}>
|
||||
<Box sx={style}>
|
||||
<TextField
|
||||
label="Folder"
|
||||
placeholder="Folder"
|
||||
margin="normal"
|
||||
value={name}
|
||||
onChange={handleChange}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
|
||||
<Link>
|
||||
<Button variant="outlined" onClick={handle}>
|
||||
Create Folder
|
||||
</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
</Fade>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
open: boolean,
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
refresh: () => void,
|
||||
}
|
||||
|
||||
export default CreateFolderModal
|
|
@ -1,5 +1,5 @@
|
|||
import { faFile, faFolder } from "@fortawesome/free-solid-svg-icons"
|
||||
import { CloudUpload } from "@mui/icons-material"
|
||||
import { CloudUpload, CreateNewFolder } from "@mui/icons-material"
|
||||
import { IconButton, Link as MuiLink } from "@mui/material"
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
|
@ -10,6 +10,7 @@ import api from '../../api_utils'
|
|||
import Icon from "../../components/other/icon"
|
||||
import Table from "../../components/user/table"
|
||||
import UploadModal from "../../components/user/modals/upload"
|
||||
import CreateFolderModal from "../../components/user/modals/create-folder"
|
||||
|
||||
export default function Files() {
|
||||
const [cookies] = useCookies(["token"])
|
||||
|
@ -45,6 +46,7 @@ export default function Files() {
|
|||
|
||||
// modals
|
||||
const [uploadModal, setUploadModal] = useState(false)
|
||||
const [createFolderModal, setCreateFolderModal] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -52,18 +54,30 @@ export default function Files() {
|
|||
<title>Files - HomeDisk</title>
|
||||
</Head>
|
||||
|
||||
<MuiLink onClick={() => setUploadModal(true)}>
|
||||
<MuiLink>
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="logo"
|
||||
onClick={() => setUploadModal(true)}
|
||||
>
|
||||
<CloudUpload />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="logo"
|
||||
onClick={() => setCreateFolderModal(true)}
|
||||
>
|
||||
<CreateNewFolder />
|
||||
</IconButton>
|
||||
</MuiLink>
|
||||
|
||||
<UploadModal open={uploadModal} setOpen={setUploadModal} path={path} refresh={refreshFolder} />
|
||||
<CreateFolderModal open={createFolderModal} setOpen={setCreateFolderModal} refresh={refreshFolder} />
|
||||
|
||||
<Table>
|
||||
<thead>
|
||||
|
|
Loading…
Reference in a new issue