copy files
This commit is contained in:
57
backend/src/lib/domain/warren/models/file/requests/cp.rs
Normal file
57
backend/src/lib/domain/warren/models/file/requests/cp.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::domain::warren::models::file::AbsoluteFilePath;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct CpRequest {
|
||||||
|
path: AbsoluteFilePath,
|
||||||
|
target_path: AbsoluteFilePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpRequest {
|
||||||
|
pub fn new(path: AbsoluteFilePath, target_path: AbsoluteFilePath) -> Self {
|
||||||
|
Self { path, target_path }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &AbsoluteFilePath {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_path(&self) -> &AbsoluteFilePath {
|
||||||
|
&self.target_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_paths(self) -> (AbsoluteFilePath, AbsoluteFilePath) {
|
||||||
|
(self.path, self.target_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CpError {
|
||||||
|
#[error("The file does not exist")]
|
||||||
|
NotFound,
|
||||||
|
#[error("The target path already exists")]
|
||||||
|
AlreadyExists,
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct CpResponse {
|
||||||
|
path: AbsoluteFilePath,
|
||||||
|
target_path: AbsoluteFilePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpResponse {
|
||||||
|
pub fn new(path: AbsoluteFilePath, target_path: AbsoluteFilePath) -> Self {
|
||||||
|
Self { path, target_path }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &AbsoluteFilePath {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_path(&self) -> &AbsoluteFilePath {
|
||||||
|
&self.target_path
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
mod cat;
|
mod cat;
|
||||||
|
mod cp;
|
||||||
mod ls;
|
mod ls;
|
||||||
mod mkdir;
|
mod mkdir;
|
||||||
mod mv;
|
mod mv;
|
||||||
@@ -7,6 +8,7 @@ mod save;
|
|||||||
mod touch;
|
mod touch;
|
||||||
|
|
||||||
pub use cat::*;
|
pub use cat::*;
|
||||||
|
pub use cp::*;
|
||||||
pub use ls::*;
|
pub use ls::*;
|
||||||
pub use mkdir::*;
|
pub use mkdir::*;
|
||||||
pub use mv::*;
|
pub use mv::*;
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ use futures_util::StreamExt;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::domain::warren::models::file::CpError;
|
||||||
|
use crate::domain::warren::models::file::CpRequest;
|
||||||
|
use crate::domain::warren::models::file::CpResponse;
|
||||||
use crate::domain::warren::models::file::LsResponse;
|
use crate::domain::warren::models::file::LsResponse;
|
||||||
use crate::domain::warren::models::file::SaveResponse;
|
use crate::domain::warren::models::file::SaveResponse;
|
||||||
use crate::domain::warren::models::file::{
|
use crate::domain::warren::models::file::{
|
||||||
@@ -673,3 +676,68 @@ impl WarrenTouchResponse {
|
|||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct WarrenCpRequest {
|
||||||
|
warren_id: Uuid,
|
||||||
|
base: CpRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WarrenCpRequest {
|
||||||
|
pub fn new(warren_id: Uuid, base: CpRequest) -> Self {
|
||||||
|
Self { warren_id, base }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warren_id(&self) -> &Uuid {
|
||||||
|
&self.warren_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base(&self) -> &CpRequest {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_fs_request(self, warren: &Warren) -> CpRequest {
|
||||||
|
let (base_path, base_target_path) = self.base.into_paths();
|
||||||
|
|
||||||
|
let path = warren.path().clone().join(&base_path.to_relative());
|
||||||
|
let target_path = warren.path().clone().join(&base_target_path.to_relative());
|
||||||
|
|
||||||
|
CpRequest::new(path, target_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<FetchWarrenRequest> for &WarrenCpRequest {
|
||||||
|
fn into(self) -> FetchWarrenRequest {
|
||||||
|
FetchWarrenRequest::new(self.warren_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum WarrenCpError {
|
||||||
|
#[error(transparent)]
|
||||||
|
FetchWarren(#[from] FetchWarrenError),
|
||||||
|
#[error(transparent)]
|
||||||
|
FileSystem(#[from] CpError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct WarrenCpResponse {
|
||||||
|
warren: Warren,
|
||||||
|
base: CpResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WarrenCpResponse {
|
||||||
|
pub fn new(warren: Warren, base: CpResponse) -> Self {
|
||||||
|
Self { warren, base }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warren(&self) -> &Warren {
|
||||||
|
&self.warren
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base(&self) -> &CpResponse {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static {
|
|||||||
|
|
||||||
fn record_warren_touch_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_warren_touch_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_warren_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_warren_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_warren_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_warren_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
||||||
@@ -62,6 +65,9 @@ pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
|||||||
|
|
||||||
fn record_touch_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_touch_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
||||||
@@ -139,4 +145,7 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
|||||||
|
|
||||||
fn record_auth_warren_touch_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_auth_warren_touch_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_auth_warren_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_auth_warren_touch_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_auth_warren_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_auth_warren_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ use super::models::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
file::{
|
file::{
|
||||||
CatError, CatRequest, FileStream, LsError, LsRequest, LsResponse, MkdirError, MkdirRequest,
|
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||||
MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError,
|
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||||
TouchRequest,
|
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
||||||
@@ -37,11 +37,11 @@ use super::models::{
|
|||||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||||
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
||||||
FetchWarrensError, FetchWarrensRequest, ListWarrensError, ListWarrensRequest, Warren,
|
FetchWarrensError, FetchWarrensRequest, ListWarrensError, ListWarrensRequest, Warren,
|
||||||
WarrenCatError, WarrenCatRequest, WarrenLsError, WarrenLsRequest, WarrenLsResponse,
|
WarrenCatError, WarrenCatRequest, WarrenCpError, WarrenCpRequest, WarrenCpResponse,
|
||||||
WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse, WarrenMvError, WarrenMvRequest,
|
WarrenLsError, WarrenLsRequest, WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest,
|
||||||
WarrenMvResponse, WarrenRmError, WarrenRmRequest, WarrenRmResponse, WarrenSaveError,
|
WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmError,
|
||||||
WarrenSaveRequest, WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest,
|
WarrenRmRequest, WarrenRmResponse, WarrenSaveError, WarrenSaveRequest, WarrenSaveResponse,
|
||||||
WarrenTouchResponse,
|
WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,6 +100,10 @@ pub trait WarrenService: Clone + Send + Sync + 'static {
|
|||||||
&self,
|
&self,
|
||||||
request: WarrenTouchRequest,
|
request: WarrenTouchRequest,
|
||||||
) -> impl Future<Output = Result<WarrenTouchResponse, WarrenTouchError>> + Send;
|
) -> impl Future<Output = Result<WarrenTouchResponse, WarrenTouchError>> + Send;
|
||||||
|
fn warren_cp(
|
||||||
|
&self,
|
||||||
|
request: WarrenCpRequest,
|
||||||
|
) -> impl Future<Output = Result<WarrenCpResponse, WarrenCpError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||||
@@ -114,6 +118,7 @@ pub trait FileSystemService: Clone + Send + Sync + 'static {
|
|||||||
request: SaveRequest,
|
request: SaveRequest,
|
||||||
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
||||||
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
||||||
|
fn cp(&self, request: CpRequest) -> impl Future<Output = Result<CpResponse, CpError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AuthService: Clone + Send + Sync + 'static {
|
pub trait AuthService: Clone + Send + Sync + 'static {
|
||||||
@@ -252,4 +257,9 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
|||||||
request: AuthRequest<WarrenTouchRequest>,
|
request: AuthRequest<WarrenTouchRequest>,
|
||||||
warren_service: &WS,
|
warren_service: &WS,
|
||||||
) -> impl Future<Output = Result<WarrenTouchResponse, AuthError<WarrenTouchError>>> + Send;
|
) -> impl Future<Output = Result<WarrenTouchResponse, AuthError<WarrenTouchError>>> + Send;
|
||||||
|
fn auth_warren_cp<WS: WarrenService>(
|
||||||
|
&self,
|
||||||
|
request: AuthRequest<WarrenCpRequest>,
|
||||||
|
warren_service: &WS,
|
||||||
|
) -> impl Future<Output = Result<WarrenCpResponse, AuthError<WarrenCpError>>> + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use crate::domain::warren::models::{
|
|||||||
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
||||||
user_warren::UserWarren,
|
user_warren::UserWarren,
|
||||||
warren::{
|
warren::{
|
||||||
Warren, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse, WarrenRmResponse,
|
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
||||||
WarrenSaveResponse, WarrenTouchResponse,
|
WarrenRmResponse, WarrenSaveResponse, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
|
|||||||
warren: &Warren,
|
warren: &Warren,
|
||||||
path: &AbsoluteFilePath,
|
path: &AbsoluteFilePath,
|
||||||
) -> impl Future<Output = ()> + Send;
|
) -> impl Future<Output = ()> + Send;
|
||||||
|
fn warren_cp(&self, response: &WarrenCpResponse) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||||
@@ -57,6 +58,11 @@ pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
|||||||
) -> impl Future<Output = ()> + Send;
|
) -> impl Future<Output = ()> + Send;
|
||||||
fn save(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
fn save(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
||||||
fn touch(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
fn touch(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
||||||
|
fn cp(
|
||||||
|
&self,
|
||||||
|
path: &AbsoluteFilePath,
|
||||||
|
target_path: &AbsoluteFilePath,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
||||||
@@ -160,4 +166,9 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
|||||||
user: &User,
|
user: &User,
|
||||||
response: &WarrenTouchResponse,
|
response: &WarrenTouchResponse,
|
||||||
) -> impl Future<Output = ()> + Send;
|
) -> impl Future<Output = ()> + Send;
|
||||||
|
fn auth_warren_cp(
|
||||||
|
&self,
|
||||||
|
user: &User,
|
||||||
|
response: &WarrenCpResponse,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ use crate::domain::warren::models::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
file::{
|
file::{
|
||||||
CatError, CatRequest, FileStream, LsError, LsRequest, LsResponse, MkdirError, MkdirRequest,
|
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||||
MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError,
|
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||||
TouchRequest,
|
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
||||||
@@ -77,6 +77,7 @@ pub trait FileSystemRepository: Clone + Send + Sync + 'static {
|
|||||||
request: SaveRequest,
|
request: SaveRequest,
|
||||||
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
||||||
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
||||||
|
fn cp(&self, request: CpRequest) -> impl Future<Output = Result<CpResponse, CpError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AuthRepository: Clone + Send + Sync + 'static {
|
pub trait AuthRepository: Clone + Send + Sync + 'static {
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ use crate::{
|
|||||||
warren::{
|
warren::{
|
||||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||||
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
||||||
FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenLsError,
|
FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenCpError,
|
||||||
WarrenLsRequest, WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest,
|
WarrenCpRequest, WarrenCpResponse, WarrenLsError, WarrenLsRequest,
|
||||||
WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, WarrenMvResponse,
|
WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse,
|
||||||
WarrenRmError, WarrenRmRequest, WarrenRmResponse, WarrenSaveError,
|
WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmError, WarrenRmRequest,
|
||||||
WarrenSaveRequest, WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest,
|
WarrenRmResponse, WarrenSaveError, WarrenSaveRequest, WarrenSaveResponse,
|
||||||
WarrenTouchResponse,
|
WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
||||||
@@ -839,12 +839,51 @@ where
|
|||||||
.map_err(AuthError::Custom);
|
.map_err(AuthError::Custom);
|
||||||
|
|
||||||
if let Ok(response) = result.as_ref() {
|
if let Ok(response) = result.as_ref() {
|
||||||
self.metrics.record_auth_warren_save_success().await;
|
self.metrics.record_auth_warren_touch_success().await;
|
||||||
self.notifier
|
self.notifier
|
||||||
.auth_warren_touch(session_response.user(), response)
|
.auth_warren_touch(session_response.user(), response)
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
self.metrics.record_auth_warren_save_failure().await;
|
self.metrics.record_auth_warren_touch_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn auth_warren_cp<WS: WarrenService>(
|
||||||
|
&self,
|
||||||
|
request: AuthRequest<WarrenCpRequest>,
|
||||||
|
warren_service: &WS,
|
||||||
|
) -> Result<WarrenCpResponse, AuthError<WarrenCpError>> {
|
||||||
|
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||||
|
|
||||||
|
let request = request.into_value();
|
||||||
|
|
||||||
|
let user_warren = self
|
||||||
|
.repository
|
||||||
|
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||||
|
session_response.user().id().clone(),
|
||||||
|
request.warren_id().clone(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO: Maybe create a separate permission for this
|
||||||
|
if !user_warren.can_modify_files() {
|
||||||
|
return Err(AuthError::InsufficientPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = warren_service
|
||||||
|
.warren_cp(request)
|
||||||
|
.await
|
||||||
|
.map_err(AuthError::Custom);
|
||||||
|
|
||||||
|
if let Ok(response) = result.as_ref() {
|
||||||
|
self.metrics.record_auth_warren_cp_success().await;
|
||||||
|
self.notifier
|
||||||
|
.auth_warren_cp(session_response.user(), response)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_auth_warren_cp_failure().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::domain::warren::{
|
use crate::domain::warren::{
|
||||||
models::file::{
|
models::file::{
|
||||||
CatError, CatRequest, FileStream, LsError, LsRequest, LsResponse, MkdirError, MkdirRequest,
|
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||||
MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError,
|
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||||
TouchRequest,
|
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
ports::{FileSystemMetrics, FileSystemNotifier, FileSystemRepository, FileSystemService},
|
ports::{FileSystemMetrics, FileSystemNotifier, FileSystemRepository, FileSystemService},
|
||||||
};
|
};
|
||||||
@@ -137,4 +137,19 @@ where
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cp(&self, request: CpRequest) -> Result<CpResponse, CpError> {
|
||||||
|
let path = request.path().clone();
|
||||||
|
let target_path = request.target_path().clone();
|
||||||
|
let result = self.repository.cp(request).await;
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
self.metrics.record_cp_success().await;
|
||||||
|
self.notifier.cp(&path, &target_path).await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_cp_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use crate::domain::warren::{
|
|||||||
warren::{
|
warren::{
|
||||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||||
EditWarrenError, EditWarrenRequest, FetchWarrensError, FetchWarrensRequest,
|
EditWarrenError, EditWarrenRequest, FetchWarrensError, FetchWarrensRequest,
|
||||||
ListWarrensError, ListWarrensRequest, WarrenCatError, WarrenCatRequest,
|
ListWarrensError, ListWarrensRequest, WarrenCatError, WarrenCatRequest, WarrenCpError,
|
||||||
WarrenLsResponse, WarrenMkdirResponse, WarrenMvError, WarrenMvRequest,
|
WarrenCpRequest, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse,
|
||||||
WarrenMvResponse, WarrenRmRequest, WarrenRmResponse, WarrenSaveResponse,
|
WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmRequest, WarrenRmResponse,
|
||||||
WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::FileSystemService,
|
ports::FileSystemService,
|
||||||
@@ -314,4 +314,25 @@ where
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn warren_cp(&self, request: WarrenCpRequest) -> Result<WarrenCpResponse, WarrenCpError> {
|
||||||
|
let warren = self.repository.fetch_warren((&request).into()).await?;
|
||||||
|
|
||||||
|
let cp_request = request.build_fs_request(&warren);
|
||||||
|
let result = self
|
||||||
|
.fs_service
|
||||||
|
.cp(cp_request)
|
||||||
|
.await
|
||||||
|
.map(|base| WarrenCpResponse::new(warren, base))
|
||||||
|
.map_err(Into::into);
|
||||||
|
|
||||||
|
if let Ok(response) = result.as_ref() {
|
||||||
|
self.metrics.record_warren_cp_success().await;
|
||||||
|
self.notifier.warren_cp(response).await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_warren_cp_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ mod fetch_warren;
|
|||||||
mod list_warrens;
|
mod list_warrens;
|
||||||
mod upload_warren_files;
|
mod upload_warren_files;
|
||||||
mod warren_cat;
|
mod warren_cat;
|
||||||
|
mod warren_cp;
|
||||||
mod warren_ls;
|
mod warren_ls;
|
||||||
mod warren_mkdir;
|
mod warren_mkdir;
|
||||||
mod warren_move;
|
mod warren_mv;
|
||||||
mod warren_rm;
|
mod warren_rm;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -27,7 +28,8 @@ use warren_rm::warren_rm;
|
|||||||
|
|
||||||
use upload_warren_files::warren_save;
|
use upload_warren_files::warren_save;
|
||||||
use warren_cat::fetch_file;
|
use warren_cat::fetch_file;
|
||||||
use warren_move::warren_move;
|
use warren_cp::warren_cp;
|
||||||
|
use warren_mv::warren_mv;
|
||||||
|
|
||||||
pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>> {
|
pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
@@ -42,5 +44,6 @@ pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>>
|
|||||||
// 10737418240 bytes = 10GB
|
// 10737418240 bytes = 10GB
|
||||||
post(warren_save).route_layer(DefaultBodyLimit::max(10737418240)),
|
post(warren_save).route_layer(DefaultBodyLimit::max(10737418240)),
|
||||||
)
|
)
|
||||||
.route("/files/mv", post(warren_move))
|
.route("/files/mv", post(warren_mv))
|
||||||
|
.route("/files/cp", post(warren_cp))
|
||||||
}
|
}
|
||||||
|
|||||||
80
backend/src/lib/inbound/http/handlers/warrens/warren_cp.rs
Normal file
80
backend/src/lib/inbound/http/handlers/warrens/warren_cp.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use axum::{Json, extract::State, http::StatusCode};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domain::warren::{
|
||||||
|
models::{
|
||||||
|
auth_session::AuthRequest,
|
||||||
|
file::{AbsoluteFilePath, AbsoluteFilePathError, CpRequest, FilePath, FilePathError},
|
||||||
|
warren::WarrenCpRequest,
|
||||||
|
},
|
||||||
|
ports::{AuthService, WarrenService},
|
||||||
|
},
|
||||||
|
inbound::http::{
|
||||||
|
AppState,
|
||||||
|
handlers::extractors::SessionIdHeader,
|
||||||
|
responses::{ApiError, ApiSuccess},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CpWarrenEntryHttpRequestBody {
|
||||||
|
warren_id: Uuid,
|
||||||
|
path: String,
|
||||||
|
target_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum ParseWarrenCpHttpRequestError {
|
||||||
|
#[error(transparent)]
|
||||||
|
FilePath(#[from] FilePathError),
|
||||||
|
#[error(transparent)]
|
||||||
|
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpWarrenEntryHttpRequestBody {
|
||||||
|
fn try_into_domain(self) -> Result<WarrenCpRequest, ParseWarrenCpHttpRequestError> {
|
||||||
|
let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?;
|
||||||
|
let target_path: AbsoluteFilePath = FilePath::new(&self.target_path)?.try_into()?;
|
||||||
|
|
||||||
|
Ok(WarrenCpRequest::new(
|
||||||
|
self.warren_id,
|
||||||
|
CpRequest::new(path, target_path),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseWarrenCpHttpRequestError> for ApiError {
|
||||||
|
fn from(value: ParseWarrenCpHttpRequestError) -> Self {
|
||||||
|
match value {
|
||||||
|
ParseWarrenCpHttpRequestError::FilePath(err) => match err {
|
||||||
|
FilePathError::InvalidPath => {
|
||||||
|
ApiError::BadRequest("The file path must be valid".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParseWarrenCpHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||||
|
AbsoluteFilePathError::NotAbsolute => {
|
||||||
|
ApiError::BadRequest("The file path must be absolute".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn warren_cp<WS: WarrenService, AS: AuthService>(
|
||||||
|
State(state): State<AppState<WS, AS>>,
|
||||||
|
SessionIdHeader(session): SessionIdHeader,
|
||||||
|
Json(request): Json<CpWarrenEntryHttpRequestBody>,
|
||||||
|
) -> Result<ApiSuccess<()>, ApiError> {
|
||||||
|
let domain_request = AuthRequest::new(session, request.try_into_domain()?);
|
||||||
|
|
||||||
|
state
|
||||||
|
.auth_service
|
||||||
|
.auth_warren_cp(domain_request, state.warren_service.as_ref())
|
||||||
|
.await
|
||||||
|
.map(|_| ApiSuccess::new(StatusCode::OK, ()))
|
||||||
|
.map_err(ApiError::from)
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RenameWarrenEntryHttpRequestBody {
|
pub struct MvWarrenEntryHttpRequestBody {
|
||||||
warren_id: Uuid,
|
warren_id: Uuid,
|
||||||
path: String,
|
path: String,
|
||||||
target_path: String,
|
target_path: String,
|
||||||
@@ -35,7 +35,7 @@ pub enum ParseWarrenMvHttpRequestError {
|
|||||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenameWarrenEntryHttpRequestBody {
|
impl MvWarrenEntryHttpRequestBody {
|
||||||
fn try_into_domain(self) -> Result<WarrenMvRequest, ParseWarrenMvHttpRequestError> {
|
fn try_into_domain(self) -> Result<WarrenMvRequest, ParseWarrenMvHttpRequestError> {
|
||||||
let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?;
|
let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?;
|
||||||
let target_path: AbsoluteFilePath = FilePath::new(&self.target_path)?.try_into()?;
|
let target_path: AbsoluteFilePath = FilePath::new(&self.target_path)?.try_into()?;
|
||||||
@@ -64,10 +64,10 @@ impl From<ParseWarrenMvHttpRequestError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn warren_move<WS: WarrenService, AS: AuthService>(
|
pub async fn warren_mv<WS: WarrenService, AS: AuthService>(
|
||||||
State(state): State<AppState<WS, AS>>,
|
State(state): State<AppState<WS, AS>>,
|
||||||
SessionIdHeader(session): SessionIdHeader,
|
SessionIdHeader(session): SessionIdHeader,
|
||||||
Json(request): Json<RenameWarrenEntryHttpRequestBody>,
|
Json(request): Json<MvWarrenEntryHttpRequestBody>,
|
||||||
) -> Result<ApiSuccess<()>, ApiError> {
|
) -> Result<ApiSuccess<()>, ApiError> {
|
||||||
let domain_request = AuthRequest::new(session, request.try_into_domain()?);
|
let domain_request = AuthRequest::new(session, request.try_into_domain()?);
|
||||||
|
|
||||||
@@ -14,10 +14,10 @@ use crate::{
|
|||||||
domain::warren::{
|
domain::warren::{
|
||||||
models::{
|
models::{
|
||||||
file::{
|
file::{
|
||||||
AbsoluteFilePath, CatError, CatRequest, File, FileMimeType, FileName, FilePath,
|
AbsoluteFilePath, CatError, CatRequest, CpError, CpRequest, CpResponse, File,
|
||||||
FileStream, FileType, LsError, LsRequest, LsResponse, MkdirError, MkdirRequest,
|
FileMimeType, FileName, FilePath, FileStream, FileType, LsError, LsRequest,
|
||||||
MvError, MvRequest, RelativeFilePath, RmError, RmRequest, SaveError, SaveRequest,
|
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RelativeFilePath,
|
||||||
SaveResponse, TouchError, TouchRequest,
|
RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
warren::UploadFileStream,
|
warren::UploadFileStream,
|
||||||
},
|
},
|
||||||
@@ -242,7 +242,20 @@ impl FileSystem {
|
|||||||
async fn touch(&self, path: &AbsoluteFilePath) -> io::Result<()> {
|
async fn touch(&self, path: &AbsoluteFilePath) -> io::Result<()> {
|
||||||
let path = self.get_target_path(path);
|
let path = self.get_target_path(path);
|
||||||
|
|
||||||
tokio::fs::File::create(&path).await.map(|_| ())
|
fs::File::create(&path).await.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cp(
|
||||||
|
&self,
|
||||||
|
path: AbsoluteFilePath,
|
||||||
|
target_path: AbsoluteFilePath,
|
||||||
|
) -> io::Result<CpResponse> {
|
||||||
|
let fs_current_path = self.get_target_path(&path);
|
||||||
|
let fs_target_path = self.get_target_path(&target_path);
|
||||||
|
|
||||||
|
fs::copy(fs_current_path, fs_target_path).await?;
|
||||||
|
|
||||||
|
Ok(CpResponse::new(path, target_path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +326,13 @@ impl FileSystemRepository for FileSystem {
|
|||||||
let (path, mut stream) = request.unpack();
|
let (path, mut stream) = request.unpack();
|
||||||
Ok(self.save(&path, &mut stream).await.map(SaveResponse::new)?)
|
Ok(self.save(&path, &mut stream).await.map(SaveResponse::new)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cp(&self, request: CpRequest) -> Result<CpResponse, CpError> {
|
||||||
|
let (path, target_path) = request.into_paths();
|
||||||
|
self.cp(path, target_path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| CpError::Unknown(e.into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use `DirEntry::metadata` once `target=x86_64-unknown-linux-musl` updates from musl 1.2.3 to 1.2.5
|
// TODO: Use `DirEntry::metadata` once `target=x86_64-unknown-linux-musl` updates from musl 1.2.3 to 1.2.5
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ impl WarrenMetrics for MetricsDebugLogger {
|
|||||||
async fn record_warren_touch_failure(&self) {
|
async fn record_warren_touch_failure(&self) {
|
||||||
tracing::debug!("[Metrics] Warren entry touch failed");
|
tracing::debug!("[Metrics] Warren entry touch failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_warren_cp_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] Warren entry cp succeeded");
|
||||||
|
}
|
||||||
|
async fn record_warren_cp_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] Warren entry cp failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemMetrics for MetricsDebugLogger {
|
impl FileSystemMetrics for MetricsDebugLogger {
|
||||||
@@ -151,6 +158,13 @@ impl FileSystemMetrics for MetricsDebugLogger {
|
|||||||
async fn record_touch_failure(&self) {
|
async fn record_touch_failure(&self) {
|
||||||
tracing::debug!("[Metrics] Touch failed");
|
tracing::debug!("[Metrics] Touch failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_cp_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] Cp succeeded");
|
||||||
|
}
|
||||||
|
async fn record_cp_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] Cp failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthMetrics for MetricsDebugLogger {
|
impl AuthMetrics for MetricsDebugLogger {
|
||||||
@@ -328,4 +342,11 @@ impl AuthMetrics for MetricsDebugLogger {
|
|||||||
async fn record_auth_warren_touch_failure(&self) {
|
async fn record_auth_warren_touch_failure(&self) {
|
||||||
tracing::debug!("[Metrics] Auth warren touch failed");
|
tracing::debug!("[Metrics] Auth warren touch failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_auth_warren_cp_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] Auth warren cp succeeded");
|
||||||
|
}
|
||||||
|
async fn record_auth_warren_cp_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] Auth warren cp failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use crate::domain::warren::{
|
|||||||
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
||||||
user_warren::UserWarren,
|
user_warren::UserWarren,
|
||||||
warren::{
|
warren::{
|
||||||
Warren, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse, WarrenRmResponse,
|
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
||||||
WarrenSaveResponse, WarrenTouchResponse,
|
WarrenRmResponse, WarrenSaveResponse, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
|
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
|
||||||
@@ -98,6 +98,15 @@ impl WarrenNotifier for NotifierDebugLogger {
|
|||||||
warren.name()
|
warren.name()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn warren_cp(&self, response: &WarrenCpResponse) {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Notifier] Copied file {} to {} in warren {}",
|
||||||
|
response.base().path(),
|
||||||
|
response.base().target_path(),
|
||||||
|
response.warren().name()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemNotifier for NotifierDebugLogger {
|
impl FileSystemNotifier for NotifierDebugLogger {
|
||||||
@@ -128,6 +137,10 @@ impl FileSystemNotifier for NotifierDebugLogger {
|
|||||||
async fn touch(&self, path: &AbsoluteFilePath) {
|
async fn touch(&self, path: &AbsoluteFilePath) {
|
||||||
tracing::debug!("[Notifier] Touched file {}", path);
|
tracing::debug!("[Notifier] Touched file {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cp(&self, path: &AbsoluteFilePath, target_path: &AbsoluteFilePath) {
|
||||||
|
tracing::debug!("[Notifier] Copied file {} to {}", path, target_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthNotifier for NotifierDebugLogger {
|
impl AuthNotifier for NotifierDebugLogger {
|
||||||
@@ -330,4 +343,14 @@ impl AuthNotifier for NotifierDebugLogger {
|
|||||||
user.id()
|
user.id()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn auth_warren_cp(&self, user: &User, response: &WarrenCpResponse) {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Notifier] Copied file {} to {} in warren {} for authenticated user {}",
|
||||||
|
response.base().path(),
|
||||||
|
response.base().target_path(),
|
||||||
|
response.warren().name(),
|
||||||
|
user.id()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import type { DirectoryEntry } from '#shared/types';
|
import type { DirectoryEntry } from '#shared/types';
|
||||||
|
|
||||||
const warrenStore = useWarrenStore();
|
const warrenStore = useWarrenStore();
|
||||||
|
const copyStore = useCopyStore();
|
||||||
const renameDialog = useRenameDirectoryDialog();
|
const renameDialog = useRenameDirectoryDialog();
|
||||||
|
|
||||||
const { entry, disabled } = defineProps<{
|
const { entry, disabled } = defineProps<{
|
||||||
@@ -22,6 +23,14 @@ const { entry, disabled } = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const deleting = ref(false);
|
const deleting = ref(false);
|
||||||
|
const isCopied = computed(
|
||||||
|
() =>
|
||||||
|
warrenStore.current != null &&
|
||||||
|
copyStore.file != null &&
|
||||||
|
warrenStore.current.warrenId === copyStore.file.warrenId &&
|
||||||
|
warrenStore.current.path === copyStore.file.path &&
|
||||||
|
entry.name === copyStore.file.name
|
||||||
|
);
|
||||||
|
|
||||||
async function submitDelete(force: boolean = false) {
|
async function submitDelete(force: boolean = false) {
|
||||||
if (warrenStore.current == null) {
|
if (warrenStore.current == null) {
|
||||||
@@ -89,6 +98,18 @@ function onDragStart(e: DragEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onDrop = onDirectoryEntryDrop(entry);
|
const onDrop = onDirectoryEntryDrop(entry);
|
||||||
|
|
||||||
|
function onCopy() {
|
||||||
|
if (warrenStore.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyStore.copyFile(
|
||||||
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path,
|
||||||
|
entry.name
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -96,7 +117,10 @@ const onDrop = onDirectoryEntryDrop(entry);
|
|||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<button
|
<button
|
||||||
:disabled="warrenStore.loading || disabled"
|
:disabled="warrenStore.loading || disabled"
|
||||||
class="bg-accent/30 border-border flex w-52 translate-0 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2 select-none"
|
:class="[
|
||||||
|
'bg-accent/30 border-border flex w-52 translate-0 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2 select-none',
|
||||||
|
isCopied && 'border-primary/50 border',
|
||||||
|
]"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="onDragStart"
|
@dragstart="onDragStart"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
@@ -124,6 +148,10 @@ const onDrop = onDirectoryEntryDrop(entry);
|
|||||||
<Icon name="lucide:pencil" />
|
<Icon name="lucide:pencil" />
|
||||||
Rename
|
Rename
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem @select="onCopy">
|
||||||
|
<Icon name="lucide:copy" />
|
||||||
|
Copy
|
||||||
|
</ContextMenuItem>
|
||||||
|
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,35 @@ import {
|
|||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
} from '@/components/ui/context-menu';
|
} from '@/components/ui/context-menu';
|
||||||
|
|
||||||
const dialog = useCreateDirectoryDialog();
|
const warrenStore = useWarrenStore();
|
||||||
|
const copyStore = useCopyStore();
|
||||||
|
const createDirectoryDialog = useCreateDirectoryDialog();
|
||||||
|
|
||||||
|
const pasting = ref<boolean>(false);
|
||||||
|
const validPaste = computed(
|
||||||
|
() =>
|
||||||
|
!pasting.value && copyStore.file != null && warrenStore.current != null
|
||||||
|
);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: string;
|
class?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
async function onPaste() {
|
||||||
|
if (!validPaste.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pasting.value = true;
|
||||||
|
|
||||||
|
await pasteFile(copyStore.file!, {
|
||||||
|
warrenId: warrenStore.current!.warrenId,
|
||||||
|
name: copyStore.file!.name,
|
||||||
|
path: warrenStore.current!.path,
|
||||||
|
});
|
||||||
|
|
||||||
|
pasting.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -19,7 +43,11 @@ const props = defineProps<{
|
|||||||
<slot />
|
<slot />
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem @select="dialog.openDialog">
|
<ContextMenuItem @disabled="!validPaste" @select="onPaste">
|
||||||
|
<Icon name="lucide:clipboard-paste" />
|
||||||
|
Paste
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem @select="createDirectoryDialog.openDialog">
|
||||||
<Icon name="lucide:folder-plus" />
|
<Icon name="lucide:folder-plus" />
|
||||||
Create directory
|
Create directory
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
|||||||
@@ -335,3 +335,38 @@ export async function moveFile(
|
|||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function copyFile(
|
||||||
|
warrenId: string,
|
||||||
|
currentPath: string,
|
||||||
|
targetPath: string
|
||||||
|
): Promise<{ success: boolean }> {
|
||||||
|
const { status } = await useFetch(getApiUrl(`warrens/files/cp`), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
warrenId,
|
||||||
|
path: currentPath,
|
||||||
|
targetPath: targetPath,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status.value !== 'success') {
|
||||||
|
toast.error('Copy', {
|
||||||
|
id: 'COPY_FILE_TOAST',
|
||||||
|
description: `Failed to copy file`,
|
||||||
|
});
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshNuxtData('current-directory');
|
||||||
|
|
||||||
|
toast.success('Copy', {
|
||||||
|
id: 'COPY_FILE_TOAST',
|
||||||
|
description: `Successfully copied file`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
18
frontend/stores/copy.ts
Normal file
18
frontend/stores/copy.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const useCopyStore = defineStore('file-copy', {
|
||||||
|
state: () => ({
|
||||||
|
file: null as { warrenId: string; path: string; name: string } | null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
copyFile(warrenId: string, filePath: string, fileName: string) {
|
||||||
|
this.file = {
|
||||||
|
warrenId,
|
||||||
|
path: filePath,
|
||||||
|
name: fileName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/** Removes the current file from the "clipboard" */
|
||||||
|
clearFile() {
|
||||||
|
this.file = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { moveFile } from '~/lib/api/warrens';
|
import { copyFile, moveFile } from '~/lib/api/warrens';
|
||||||
import type { DirectoryEntry } from '~/shared/types';
|
import type { DirectoryEntry } from '~/shared/types';
|
||||||
|
|
||||||
export function joinPaths(path: string, ...other: string[]): string {
|
export function joinPaths(path: string, ...other: string[]): string {
|
||||||
@@ -58,3 +58,20 @@ export function onDirectoryEntryDrop(
|
|||||||
await moveFile(warrenStore.current.warrenId, currentPath, targetPath);
|
await moveFile(warrenStore.current.warrenId, currentPath, targetPath);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function pasteFile(
|
||||||
|
current: { warrenId: string; path: string; name: string },
|
||||||
|
target: { warrenId: string; path: string; name: string }
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (current.warrenId !== target.warrenId) {
|
||||||
|
throw new Error('Cross-warren copies are not supported yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { success } = await copyFile(
|
||||||
|
current.warrenId,
|
||||||
|
joinPaths(current.path, current.name),
|
||||||
|
joinPaths(target.path, target.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user