From b4731f6f81541e34e75b0dca386cf95d50007c2b Mon Sep 17 00:00:00 2001 From: 409 <409dev@protonmail.com> Date: Sun, 13 Jul 2025 03:18:22 +0200 Subject: [PATCH] create dirs + delete dirs + delete files --- backend/Cargo.toml | 2 +- backend/src/api/warrens/create_directory.rs | 24 ++++++ backend/src/api/warrens/delete_directory.rs | 31 +++++++ backend/src/api/warrens/mod.rs | 6 +- backend/src/fs/dir.rs | 18 ++++ backend/src/fs/file.rs | 14 +++ backend/src/fs/mod.rs | 6 +- backend/src/server.rs | 8 +- backend/src/warrens/mod.rs | 40 +++++++-- frontend/app.vue | 5 ++ frontend/assets/css/sonner.css | 17 ++++ frontend/bun.lock | 3 + frontend/components/AppSidebar.vue | 2 +- frontend/components/CreateDirectoryDialog.vue | 66 ++++++++++++++ frontend/components/DirectoryEntry.vue | 65 ++++++++++---- frontend/components/DirectoryList.vue | 12 ++- .../ui/context-menu/ContextMenu.vue | 18 ++++ .../context-menu/ContextMenuCheckboxItem.vue | 38 ++++++++ .../ui/context-menu/ContextMenuContent.vue | 34 ++++++++ .../ui/context-menu/ContextMenuGroup.vue | 14 +++ .../ui/context-menu/ContextMenuItem.vue | 39 +++++++++ .../ui/context-menu/ContextMenuLabel.vue | 21 +++++ .../ui/context-menu/ContextMenuPortal.vue | 14 +++ .../ui/context-menu/ContextMenuRadioGroup.vue | 22 +++++ .../ui/context-menu/ContextMenuRadioItem.vue | 38 ++++++++ .../ui/context-menu/ContextMenuSeparator.vue | 21 +++++ .../ui/context-menu/ContextMenuShortcut.vue | 17 ++++ .../ui/context-menu/ContextMenuSub.vue | 22 +++++ .../ui/context-menu/ContextMenuSubContent.vue | 33 +++++++ .../ui/context-menu/ContextMenuSubTrigger.vue | 32 +++++++ .../ui/context-menu/ContextMenuTrigger.vue | 16 ++++ frontend/components/ui/context-menu/index.ts | 14 +++ frontend/components/ui/dialog/Dialog.vue | 17 ++++ frontend/components/ui/dialog/DialogClose.vue | 14 +++ .../components/ui/dialog/DialogContent.vue | 46 ++++++++++ .../ui/dialog/DialogDescription.vue | 22 +++++ .../components/ui/dialog/DialogFooter.vue | 15 ++++ .../components/ui/dialog/DialogHeader.vue | 17 ++++ .../components/ui/dialog/DialogOverlay.vue | 20 +++++ .../ui/dialog/DialogScrollContent.vue | 56 ++++++++++++ frontend/components/ui/dialog/DialogTitle.vue | 22 +++++ .../components/ui/dialog/DialogTrigger.vue | 14 +++ frontend/components/ui/dialog/index.ts | 10 +++ frontend/components/ui/sonner/Sonner.vue | 18 ++++ frontend/components/ui/sonner/index.ts | 1 + frontend/layouts/default.vue | 10 ++- frontend/lib/api/warrens.ts | 86 +++++++++++++++++-- frontend/nuxt.config.ts | 12 +-- frontend/package.json | 3 +- frontend/pages/warrens/[...path].vue | 9 +- frontend/stores/index.ts | 3 + frontend/types/warrens.ts | 4 +- frontend/utils/api.ts | 4 +- 53 files changed, 1056 insertions(+), 59 deletions(-) create mode 100644 backend/src/api/warrens/create_directory.rs create mode 100644 backend/src/api/warrens/delete_directory.rs create mode 100644 backend/src/fs/file.rs create mode 100644 frontend/assets/css/sonner.css create mode 100644 frontend/components/CreateDirectoryDialog.vue create mode 100644 frontend/components/ui/context-menu/ContextMenu.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuCheckboxItem.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuContent.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuGroup.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuItem.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuLabel.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuPortal.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuRadioGroup.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuRadioItem.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuSeparator.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuShortcut.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuSub.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuSubContent.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuSubTrigger.vue create mode 100644 frontend/components/ui/context-menu/ContextMenuTrigger.vue create mode 100644 frontend/components/ui/context-menu/index.ts create mode 100644 frontend/components/ui/dialog/Dialog.vue create mode 100644 frontend/components/ui/dialog/DialogClose.vue create mode 100644 frontend/components/ui/dialog/DialogContent.vue create mode 100644 frontend/components/ui/dialog/DialogDescription.vue create mode 100644 frontend/components/ui/dialog/DialogFooter.vue create mode 100644 frontend/components/ui/dialog/DialogHeader.vue create mode 100644 frontend/components/ui/dialog/DialogOverlay.vue create mode 100644 frontend/components/ui/dialog/DialogScrollContent.vue create mode 100644 frontend/components/ui/dialog/DialogTitle.vue create mode 100644 frontend/components/ui/dialog/DialogTrigger.vue create mode 100644 frontend/components/ui/dialog/index.ts create mode 100644 frontend/components/ui/sonner/Sonner.vue create mode 100644 frontend/components/ui/sonner/index.ts diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 90895e4..7167c40 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -axum = "0.8.4" +axum = { version = "0.8.4", features = ["query"] } dotenv = "0.15.0" env_logger = "0.11.8" log = "0.4.27" diff --git a/backend/src/api/warrens/create_directory.rs b/backend/src/api/warrens/create_directory.rs new file mode 100644 index 0000000..9896c7e --- /dev/null +++ b/backend/src/api/warrens/create_directory.rs @@ -0,0 +1,24 @@ +use axum::extract::{Path, State}; +use serde::Deserialize; +use uuid::Uuid; + +use crate::{Result, api::AppState, warrens::Warren}; + +#[derive(Debug, Deserialize)] +pub(super) struct CreateWarrenDirectoryPath { + warren_id: Uuid, + rest: String, +} + +pub(super) async fn route( + State(state): State, + Path(path): Path, +) -> Result<()> { + let warren = Warren::get(state.pool(), &path.warren_id).await?; + + warren + .create_directory(state.serve_dir(), path.rest) + .await?; + + Ok(()) +} diff --git a/backend/src/api/warrens/delete_directory.rs b/backend/src/api/warrens/delete_directory.rs new file mode 100644 index 0000000..4f647d8 --- /dev/null +++ b/backend/src/api/warrens/delete_directory.rs @@ -0,0 +1,31 @@ +use axum::extract::{Path, Query, State}; +use serde::Deserialize; +use uuid::Uuid; + +use crate::{Result, api::AppState, fs::FileType, warrens::Warren}; + +#[derive(Deserialize)] +pub(super) struct DeleteWarrenDirectoryPath { + warren_id: Uuid, + rest: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteWarrenParams { + file_type: FileType, +} + +pub(super) async fn route( + State(state): State, + Path(path): Path, + Query(DeleteWarrenParams { file_type }): Query, +) -> Result<()> { + let warren = Warren::get(state.pool(), &path.warren_id).await?; + + warren + .delete_entry(state.serve_dir(), path.rest, file_type) + .await?; + + Ok(()) +} diff --git a/backend/src/api/warrens/mod.rs b/backend/src/api/warrens/mod.rs index d1961f9..9d5f3ed 100644 --- a/backend/src/api/warrens/mod.rs +++ b/backend/src/api/warrens/mod.rs @@ -1,7 +1,9 @@ +mod create_directory; +mod delete_directory; mod get_warren_path; mod list_warrens; -use axum::routing::get; +use axum::routing::{delete, get, post}; use crate::server::Router; @@ -10,4 +12,6 @@ pub(super) fn router() -> Router { .route("/", get(list_warrens::route)) .route("/{warren_id}", get(get_warren_path::route)) .route("/{warren_id}/{*rest}", get(get_warren_path::route)) + .route("/{warren_id}/{*rest}", post(create_directory::route)) + .route("/{warren_id}/{*rest}", delete(delete_directory::route)) } diff --git a/backend/src/fs/dir.rs b/backend/src/fs/dir.rs index 4c05911..4b71547 100644 --- a/backend/src/fs/dir.rs +++ b/backend/src/fs/dir.rs @@ -36,3 +36,21 @@ where Ok(files) } + +pub async fn create_dir

(path: P) -> Result<()> +where + P: AsRef, +{ + fs::create_dir(path).await?; + + Ok(()) +} + +pub async fn delete_dir

(path: P) -> Result<()> +where + P: AsRef, +{ + fs::remove_dir_all(path).await?; + + Ok(()) +} diff --git a/backend/src/fs/file.rs b/backend/src/fs/file.rs new file mode 100644 index 0000000..b637da5 --- /dev/null +++ b/backend/src/fs/file.rs @@ -0,0 +1,14 @@ +use std::path::Path; + +use tokio::fs; + +use crate::Result; + +pub async fn delete_file

(path: P) -> Result<()> +where + P: AsRef, +{ + fs::remove_file(path).await?; + + Ok(()) +} diff --git a/backend/src/fs/mod.rs b/backend/src/fs/mod.rs index b213365..f5381d4 100644 --- a/backend/src/fs/mod.rs +++ b/backend/src/fs/mod.rs @@ -1,9 +1,11 @@ mod dir; +mod file; pub use dir::*; +pub use file::*; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, Serialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum FileType { File, diff --git a/backend/src/server.rs b/backend/src/server.rs index 4eb3219..f19c9b3 100644 --- a/backend/src/server.rs +++ b/backend/src/server.rs @@ -22,9 +22,11 @@ pub(super) async fn start(pool: Pool) -> Result<()> { let mut app = Router::new() .nest("/api", api::router()) .layer( - CorsLayer::new().allow_origin(cors::AllowOrigin::exact(HeaderValue::from_str( - &cors_origin, - )?)), + CorsLayer::new() + .allow_origin(cors::AllowOrigin::exact(HeaderValue::from_str( + &cors_origin, + )?)) + .allow_methods(cors::Any), ) .with_state(AppState::new(pool, serve_dir)); diff --git a/backend/src/warrens/mod.rs b/backend/src/warrens/mod.rs index 28d65d3..b655138 100644 --- a/backend/src/warrens/mod.rs +++ b/backend/src/warrens/mod.rs @@ -8,7 +8,7 @@ use uuid::Uuid; use crate::{ Result, - fs::{DirectoryEntry, get_dir_entries}, + fs::{DirectoryEntry, FileType, create_dir, delete_dir, delete_file, get_dir_entries}, }; #[derive(Debug, Clone, Serialize, FromRow)] @@ -40,14 +40,40 @@ impl Warren { serve_path: &str, path: Option, ) -> Result> { - let mut final_path = PathBuf::from(serve_path); + let path = build_path(serve_path, &self.path, path.as_deref()); - final_path.push(self.path.strip_prefix("/").unwrap_or(&self.path)); + get_dir_entries(path).await + } - if let Some(ref rest) = path { - final_path.push(rest); + pub async fn create_directory(&self, serve_path: &str, path: String) -> Result<()> { + let path = build_path(serve_path, &self.path, Some(&path)); + + create_dir(path).await + } + + pub async fn delete_entry( + &self, + serve_path: &str, + path: String, + file_type: FileType, + ) -> Result<()> { + let path = build_path(serve_path, &self.path, Some(&path)); + + match file_type { + FileType::File => delete_file(path).await, + FileType::Directory => delete_dir(path).await, } - - get_dir_entries(final_path).await } } + +fn build_path(serve_path: &str, warren_path: &str, rest_path: Option<&str>) -> PathBuf { + let mut final_path = PathBuf::from(serve_path); + + final_path.push(warren_path.strip_prefix("/").unwrap_or(warren_path)); + + if let Some(ref rest) = rest_path { + final_path.push(rest); + } + + final_path +} diff --git a/frontend/app.vue b/frontend/app.vue index b8dbd7a..b4c5566 100644 --- a/frontend/app.vue +++ b/frontend/app.vue @@ -1,4 +1,7 @@