rename directory entries
This commit is contained in:
@@ -5,6 +5,7 @@ use crate::domain::warren::models::file::{
|
||||
AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError,
|
||||
CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError,
|
||||
DeleteFileRequest, FileName, FilePath, ListFilesError, ListFilesRequest, RelativeFilePath,
|
||||
RenameEntryError, RenameEntryRequest,
|
||||
};
|
||||
|
||||
use super::Warren;
|
||||
@@ -310,3 +311,53 @@ impl UploadFile {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct RenameWarrenEntryRequest {
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
new_name: FileName,
|
||||
}
|
||||
|
||||
impl RenameWarrenEntryRequest {
|
||||
pub fn new(warren_id: Uuid, path: AbsoluteFilePath, new_name: FileName) -> Self {
|
||||
Self {
|
||||
warren_id,
|
||||
path,
|
||||
new_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn new_name(&self) -> &FileName {
|
||||
&self.new_name
|
||||
}
|
||||
|
||||
pub fn to_fs_request(self, warren: &Warren) -> RenameEntryRequest {
|
||||
let path = warren.path().clone().join(&self.path.to_relative());
|
||||
RenameEntryRequest::new(path, self.new_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &RenameWarrenEntryRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RenameWarrenEntryError {
|
||||
#[error(transparent)]
|
||||
Fetch(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Rename(#[from] RenameEntryError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static {
|
||||
|
||||
fn record_warren_file_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_file_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_entry_rename_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_entry_rename_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
||||
|
||||
@@ -16,8 +16,8 @@ use super::models::{
|
||||
CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError,
|
||||
DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest,
|
||||
FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest,
|
||||
ListWarrensError, ListWarrensRequest, UploadWarrenFilesError, UploadWarrenFilesRequest,
|
||||
Warren,
|
||||
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest,
|
||||
UploadWarrenFilesError, UploadWarrenFilesRequest, Warren,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -55,6 +55,11 @@ pub trait WarrenService: Clone + Send + Sync + 'static {
|
||||
&self,
|
||||
request: DeleteWarrenFileRequest,
|
||||
) -> impl Future<Output = Result<FilePath, DeleteWarrenFileError>> + Send;
|
||||
|
||||
fn rename_warren_entry(
|
||||
&self,
|
||||
request: RenameWarrenEntryRequest,
|
||||
) -> impl Future<Output = Result<FilePath, RenameWarrenEntryError>> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::domain::warren::models::{
|
||||
file::{File, FilePath},
|
||||
file::{AbsoluteFilePath, File, FilePath},
|
||||
warren::Warren,
|
||||
};
|
||||
|
||||
@@ -44,6 +44,13 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
|
||||
warren: &Warren,
|
||||
path: &FilePath,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn warren_entry_renamed(
|
||||
&self,
|
||||
warren: &Warren,
|
||||
old_path: &AbsoluteFilePath,
|
||||
new_path: &FilePath,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||
|
||||
@@ -3,7 +3,9 @@ use anyhow::Context;
|
||||
use crate::domain::warren::{
|
||||
models::{
|
||||
file::{File, FilePath},
|
||||
warren::{ListWarrensError, ListWarrensRequest},
|
||||
warren::{
|
||||
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest,
|
||||
},
|
||||
},
|
||||
ports::FileSystemService,
|
||||
};
|
||||
@@ -230,4 +232,28 @@ where
|
||||
|
||||
result.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn rename_warren_entry(
|
||||
&self,
|
||||
request: RenameWarrenEntryRequest,
|
||||
) -> Result<FilePath, RenameWarrenEntryError> {
|
||||
let warren = self.repository.fetch_warren((&request).into()).await?;
|
||||
|
||||
let old_path = request.path().clone();
|
||||
let result = self
|
||||
.fs_service
|
||||
.rename_entry(request.to_fs_request(&warren))
|
||||
.await;
|
||||
|
||||
if let Ok(new_path) = result.as_ref() {
|
||||
self.metrics.record_warren_entry_rename_success().await;
|
||||
self.notifier
|
||||
.warren_entry_renamed(&warren, &old_path, new_path)
|
||||
.await;
|
||||
} else {
|
||||
self.metrics.record_warren_entry_rename_failure().await;
|
||||
}
|
||||
|
||||
result.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ mod delete_warren_file;
|
||||
mod fetch_warren;
|
||||
mod list_warren_files;
|
||||
mod list_warrens;
|
||||
mod rename_warren_entry;
|
||||
mod upload_warren_files;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
extract::DefaultBodyLimit,
|
||||
routing::{delete, get, post},
|
||||
routing::{delete, get, patch, post},
|
||||
};
|
||||
|
||||
use crate::{domain::warren::ports::WarrenService, inbound::http::AppState};
|
||||
@@ -22,6 +23,7 @@ use create_warren_directory::create_warren_directory;
|
||||
use delete_warren_directory::delete_warren_directory;
|
||||
|
||||
use delete_warren_file::delete_warren_file;
|
||||
use rename_warren_entry::rename_warren_entry;
|
||||
use upload_warren_files::upload_warren_files;
|
||||
|
||||
pub fn routes<WS: WarrenService>() -> Router<AppState<WS>> {
|
||||
@@ -37,4 +39,5 @@ pub fn routes<WS: WarrenService>() -> Router<AppState<WS>> {
|
||||
post(upload_warren_files).route_layer(DefaultBodyLimit::max(1073741824)),
|
||||
)
|
||||
.route("/files/file", delete(delete_warren_file))
|
||||
.route("/files/rename", patch(rename_warren_entry))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
file::{
|
||||
AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath,
|
||||
FilePathError,
|
||||
},
|
||||
warren::{RenameWarrenEntryError, RenameWarrenEntryRequest},
|
||||
},
|
||||
ports::WarrenService,
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameWarrenEntryHttpRequestBody {
|
||||
warren_id: Uuid,
|
||||
path: String,
|
||||
new_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ParseRenameWarrenEntryHttpRequestError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
#[error(transparent)]
|
||||
FileName(#[from] FileNameError),
|
||||
}
|
||||
|
||||
impl RenameWarrenEntryHttpRequestBody {
|
||||
fn try_into_domain(
|
||||
self,
|
||||
) -> Result<RenameWarrenEntryRequest, ParseRenameWarrenEntryHttpRequestError> {
|
||||
let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?;
|
||||
let new_name = FileName::new(&self.new_name)?;
|
||||
|
||||
Ok(RenameWarrenEntryRequest::new(
|
||||
self.warren_id,
|
||||
path,
|
||||
new_name,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseRenameWarrenEntryHttpRequestError> for ApiError {
|
||||
fn from(value: ParseRenameWarrenEntryHttpRequestError) -> Self {
|
||||
match value {
|
||||
ParseRenameWarrenEntryHttpRequestError::FilePath(err) => match err {
|
||||
FilePathError::InvalidPath => {
|
||||
ApiError::BadRequest("The file path must be valid".to_string())
|
||||
}
|
||||
},
|
||||
ParseRenameWarrenEntryHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||
AbsoluteFilePathError::NotAbsolute => {
|
||||
ApiError::BadRequest("The file path must be absolute".to_string())
|
||||
}
|
||||
},
|
||||
ParseRenameWarrenEntryHttpRequestError::FileName(err) => match err {
|
||||
FileNameError::Slash => {
|
||||
ApiError::BadRequest("The new name must not include a slash".to_string())
|
||||
}
|
||||
FileNameError::Empty => {
|
||||
ApiError::BadRequest("The new name must not be empty".to_string())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RenameWarrenEntryError> for ApiError {
|
||||
fn from(_: RenameWarrenEntryError) -> Self {
|
||||
ApiError::InternalServerError("Internal server error".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rename_warren_entry<WS: WarrenService>(
|
||||
State(state): State<AppState<WS>>,
|
||||
Json(request): Json<RenameWarrenEntryHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<()>, ApiError> {
|
||||
let domain_request = request.try_into_domain()?;
|
||||
|
||||
state
|
||||
.warren_service
|
||||
.rename_warren_entry(domain_request)
|
||||
.await
|
||||
.map(|_| ApiSuccess::new(StatusCode::OK, ()))
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use tokio::{fs, io::AsyncWriteExt as _};
|
||||
|
||||
use crate::domain::warren::{
|
||||
@@ -156,6 +156,10 @@ impl FileSystem {
|
||||
FilePath::new(&c)?
|
||||
};
|
||||
|
||||
if fs::try_exists(&new_path).await? {
|
||||
bail!("File already exists");
|
||||
}
|
||||
|
||||
fs::rename(current_path, &new_path).await?;
|
||||
|
||||
Ok(new_path)
|
||||
|
||||
@@ -70,6 +70,13 @@ impl WarrenMetrics for MetricsDebugLogger {
|
||||
async fn record_warren_file_deletion_failure(&self) {
|
||||
log::debug!("[Metrics] Warren file deletion failed");
|
||||
}
|
||||
|
||||
async fn record_warren_entry_rename_success(&self) {
|
||||
log::debug!("[Metrics] Warren entry rename succeeded");
|
||||
}
|
||||
async fn record_warren_entry_rename_failure(&self) {
|
||||
log::debug!("[Metrics] Warren entry rename failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystemMetrics for MetricsDebugLogger {
|
||||
|
||||
@@ -71,6 +71,20 @@ impl WarrenNotifier for NotifierDebugLogger {
|
||||
warren.name(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_entry_renamed(
|
||||
&self,
|
||||
warren: &Warren,
|
||||
old_path: &crate::domain::warren::models::file::AbsoluteFilePath,
|
||||
new_path: &FilePath,
|
||||
) {
|
||||
log::debug!(
|
||||
"[Notifier] Renamed file {} to {} in warren {}",
|
||||
old_path,
|
||||
new_path,
|
||||
warren.name(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystemNotifier for NotifierDebugLogger {
|
||||
|
||||
Reference in New Issue
Block a user