diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 217cdd1..a3f284b 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -58,56 +58,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.59.0", -] - [[package]] name = "anyhow" version = "1.0.98" @@ -391,12 +341,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -587,29 +531,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1128,42 +1049,12 @@ dependencies = [ "libc", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jiff" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "js-sys" version = "0.3.77" @@ -1308,6 +1199,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1371,10 +1272,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "once_cell_polyfill" -version = "1.70.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" @@ -1459,21 +1360,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.2" @@ -1834,6 +1720,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2208,6 +2103,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -2389,6 +2293,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -2459,12 +2389,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.17.0" @@ -2476,6 +2400,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2497,8 +2427,6 @@ dependencies = [ "axum_typed_multipart", "derive_more", "dotenv", - "env_logger", - "log", "mime_guess", "regex", "serde", @@ -2506,7 +2434,10 @@ dependencies = [ "sqlx", "thiserror", "tokio", + "tower", "tower-http", + "tracing", + "tracing-subscriber", "uuid", ] @@ -2599,6 +2530,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.2" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5de4f65..408fa53 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -17,8 +17,6 @@ axum = { version = "0.8.4", features = ["multipart", "query"] } axum_typed_multipart = "0.16.3" derive_more = { version = "2.0.1", features = ["display"] } dotenv = "0.15.0" -env_logger = "0.11.8" -log = "0.4.27" mime_guess = "2.0.5" regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } @@ -26,5 +24,8 @@ serde_json = "1.0.140" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "uuid"] } thiserror = "2.0.12" tokio = { version = "1.46.1", features = ["full"] } -tower-http = { version = "0.6.6", features = ["cors", "fs"] } +tower = "0.5.2" +tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] } +tracing = "0.1.41" +tracing-subscriber = "0.3.19" uuid = { version = "1.17.0", features = ["serde"] } diff --git a/backend/src/bin/backend/main.rs b/backend/src/bin/backend/main.rs index a36abf1..65eb8a3 100644 --- a/backend/src/bin/backend/main.rs +++ b/backend/src/bin/backend/main.rs @@ -16,11 +16,9 @@ async fn main() -> anyhow::Result<()> { let config = Config::from_env()?; - env_logger::builder() - .format_target(false) - .filter_level(log::LevelFilter::Info) - .parse_default_env() - .filter_level(config.log_level) + tracing_subscriber::fmt() + .with_max_level(config.log_level) + .with_target(cfg!(debug_assertions)) .init(); let metrics = MetricsDebugLogger::new(); diff --git a/backend/src/lib/config.rs b/backend/src/lib/config.rs index c204927..3fe9ad8 100644 --- a/backend/src/lib/config.rs +++ b/backend/src/lib/config.rs @@ -1,7 +1,7 @@ -use std::{env, str::FromStr as _}; +use std::{env, str::FromStr}; use anyhow::Context as _; -use log::LevelFilter; +use tracing::level_filters::LevelFilter; const DATABASE_URL_KEY: &str = "DATABASE_URL"; const DATABASE_NAME_KEY: &str = "DATABASE_NAME"; diff --git a/backend/src/lib/domain/warren/models/file/requests.rs b/backend/src/lib/domain/warren/models/file/requests.rs index e1a9d36..04975a4 100644 --- a/backend/src/lib/domain/warren/models/file/requests.rs +++ b/backend/src/lib/domain/warren/models/file/requests.rs @@ -28,6 +28,7 @@ pub enum ListFilesError { #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeleteDirectoryRequest { path: AbsoluteFilePath, + force: bool, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -36,13 +37,17 @@ pub struct DeleteFileRequest { } impl DeleteDirectoryRequest { - pub fn new(path: AbsoluteFilePath) -> Self { - Self { path } + pub fn new(path: AbsoluteFilePath, force: bool) -> Self { + Self { path, force } } pub fn path(&self) -> &AbsoluteFilePath { &self.path } + + pub fn force(&self) -> bool { + self.force + } } impl DeleteFileRequest { @@ -57,6 +62,10 @@ impl DeleteFileRequest { #[derive(Debug, Error)] pub enum DeleteDirectoryError { + #[error("The directory does not exist")] + NotFound, + #[error("The directory is not empty")] + NotEmpty, #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -84,6 +93,8 @@ impl CreateDirectoryRequest { #[derive(Debug, Error)] pub enum CreateDirectoryError { + #[error("The directory already exists")] + Exists, #[error(transparent)] Unknown(#[from] anyhow::Error), } diff --git a/backend/src/lib/domain/warren/models/warren/requests.rs b/backend/src/lib/domain/warren/models/warren/requests.rs index 23c0c67..5a354c3 100644 --- a/backend/src/lib/domain/warren/models/warren/requests.rs +++ b/backend/src/lib/domain/warren/models/warren/requests.rs @@ -148,7 +148,7 @@ impl DeleteWarrenDirectoryRequest { pub fn to_fs_request(self, warren: &Warren) -> DeleteDirectoryRequest { let path = warren.path().clone().join(&self.path.to_relative()); - DeleteDirectoryRequest::new(path) + DeleteDirectoryRequest::new(path, self.force) } pub fn warren_id(&self) -> &Uuid { diff --git a/backend/src/lib/inbound/http/errors.rs b/backend/src/lib/inbound/http/errors.rs new file mode 100644 index 0000000..66940a8 --- /dev/null +++ b/backend/src/lib/inbound/http/errors.rs @@ -0,0 +1,127 @@ +use crate::{ + domain::warren::models::{ + file::{CreateDirectoryError, DeleteDirectoryError, DeleteFileError, ListFilesError}, + warren::{ + CreateWarrenDirectoryError, DeleteWarrenDirectoryError, DeleteWarrenFileError, + FetchWarrenError, ListWarrenFilesError, ListWarrensError, RenameWarrenEntryError, + UploadWarrenFilesError, + }, + }, + inbound::http::responses::ApiError, +}; + +impl From for ApiError { + fn from(value: CreateDirectoryError) -> Self { + match value { + CreateDirectoryError::Exists => match value { + CreateDirectoryError::Exists => { + Self::BadRequest("The directory already exists".to_string()) + } + CreateDirectoryError::Unknown(error) => { + Self::InternalServerError(error.to_string()) + } + }, + CreateDirectoryError::Unknown(error) => Self::InternalServerError(error.to_string()), + } + } +} + +impl From for ApiError { + fn from(value: CreateWarrenDirectoryError) -> Self { + match value { + CreateWarrenDirectoryError::FileSystem(fs) => fs.into(), + CreateWarrenDirectoryError::Unknown(error) => { + Self::InternalServerError(error.to_string()) + } + } + } +} + +impl From for ApiError { + fn from(value: DeleteDirectoryError) -> Self { + match value { + DeleteDirectoryError::NotFound => { + Self::NotFound("The directory does not exist".to_string()) + } + DeleteDirectoryError::NotEmpty => { + Self::BadRequest("The directory is not empty".to_string()) + } + DeleteDirectoryError::Unknown(e) => Self::InternalServerError(e.to_string()), + } + } +} + +impl From for ApiError { + fn from(value: DeleteWarrenDirectoryError) -> Self { + match value { + DeleteWarrenDirectoryError::FileSystem(fs) => fs.into(), + DeleteWarrenDirectoryError::Unknown(error) => { + Self::InternalServerError(error.to_string()) + } + } + } +} + +impl From for ApiError { + fn from(value: DeleteFileError) -> Self { + Self::InternalServerError(value.to_string()) + } +} + +impl From for ApiError { + fn from(value: DeleteWarrenFileError) -> Self { + match value { + DeleteWarrenFileError::FileSystem(fs) => fs.into(), + DeleteWarrenFileError::Unknown(error) => Self::InternalServerError(error.to_string()), + } + } +} + +impl From for ApiError { + fn from(value: FetchWarrenError) -> Self { + match value { + FetchWarrenError::NotFound(uuid) => { + Self::NotFound(format!("Warren with id {uuid} does not exist")) + } + FetchWarrenError::Unknown(error) => Self::InternalServerError(error.to_string()), + } + } +} + +impl From for ApiError { + fn from(value: ListFilesError) -> Self { + match value { + ListFilesError::NotFound(_) => { + Self::NotFound("Could not find the requested directory".to_string()) + } + ListFilesError::Unknown(e) => Self::InternalServerError(e.to_string()), + } + } +} + +impl From for ApiError { + fn from(value: ListWarrenFilesError) -> Self { + match value { + ListWarrenFilesError::FileSystem(fs_error) => fs_error.into(), + ListWarrenFilesError::Unknown(error) => Self::InternalServerError(error.to_string()), + } + } +} + +impl From for ApiError { + fn from(error: ListWarrensError) -> Self { + Self::InternalServerError(error.to_string()) + } +} + +impl From for ApiError { + fn from(value: RenameWarrenEntryError) -> Self { + ApiError::InternalServerError(value.to_string()) + } +} + +impl From for ApiError { + fn from(value: UploadWarrenFilesError) -> Self { + ApiError::InternalServerError(value.to_string()) + } +} diff --git a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs index 1d39e8b..8c5bba6 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs @@ -7,7 +7,7 @@ use crate::{ domain::warren::{ models::{ file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::{CreateWarrenDirectoryError, CreateWarrenDirectoryRequest}, + warren::CreateWarrenDirectoryRequest, }, ports::WarrenService, }, @@ -62,12 +62,6 @@ impl CreateWarrenDirectoryHttpRequestBody { } } -impl From for ApiError { - fn from(_value: CreateWarrenDirectoryError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn create_warren_directory( State(state): State>, Json(request): Json, diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs index 31cea3e..874c962 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs @@ -7,7 +7,7 @@ use crate::{ domain::warren::{ models::{ file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::{DeleteWarrenDirectoryError, DeleteWarrenDirectoryRequest}, + warren::DeleteWarrenDirectoryRequest, }, ports::WarrenService, }, @@ -64,12 +64,6 @@ impl DeleteWarrenDirectoryHttpRequestBody { } } -impl From for ApiError { - fn from(_value: DeleteWarrenDirectoryError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn delete_warren_directory( State(state): State>, Json(request): Json, diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs index 8e27ca2..1c18641 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs @@ -7,7 +7,7 @@ use crate::{ domain::warren::{ models::{ file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::{DeleteWarrenFileError, DeleteWarrenFileRequest}, + warren::DeleteWarrenFileRequest, }, ports::WarrenService, }, @@ -62,12 +62,6 @@ impl DeleteWarrenFileHttpRequestBody { } } -impl From for ApiError { - fn from(_value: DeleteWarrenFileError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn delete_warren_file( State(state): State>, Json(request): Json, diff --git a/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs b/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs index 34f1b80..6b62223 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs @@ -5,7 +5,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ - models::warren::{FetchWarrenError, FetchWarrenRequest, Warren}, + models::warren::{FetchWarrenRequest, Warren}, ports::WarrenService, }, inbound::http::{ @@ -29,25 +29,12 @@ impl Into for &Warren { } } -impl From for ApiError { - fn from(error: FetchWarrenError) -> Self { - match error { - FetchWarrenError::NotFound(uuid) => { - Self::NotFound(format!("Warren with id {uuid} does not exist")) - } - FetchWarrenError::Unknown(_cause) => { - Self::InternalServerError("Internal server error".to_string()) - } - } - } -} - #[derive(Debug, Clone, Error)] enum ParseFetchWarrenHttpRequestError {} impl From for ApiError { - fn from(_e: ParseFetchWarrenHttpRequestError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) + fn from(e: ParseFetchWarrenHttpRequestError) -> Self { + ApiError::BadRequest(e.to_string()) } } diff --git a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs b/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs index d9a77b9..2502a54 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs @@ -7,7 +7,7 @@ use crate::{ domain::warren::{ models::{ file::{AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType}, - warren::{ListWarrenFilesError, ListWarrenFilesRequest}, + warren::ListWarrenFilesRequest, }, ports::WarrenService, }, @@ -94,12 +94,6 @@ impl From> for ListWarrenFilesResponseData { } } -impl From for ApiError { - fn from(_value: ListWarrenFilesError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn list_warren_files( State(state): State>, Json(request): Json, diff --git a/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs b/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs index e259a02..3f714f8 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ - models::warren::{ListWarrensError, ListWarrensRequest, Warren}, + models::warren::{ListWarrensRequest, Warren}, ports::WarrenService, }, inbound::http::{ @@ -41,16 +41,6 @@ impl From<&Vec> for ListWarrensResponseData { } } -impl From for ApiError { - fn from(error: ListWarrensError) -> Self { - match error { - ListWarrensError::Unknown(_cause) => { - Self::InternalServerError("Internal server error".to_string()) - } - } - } -} - pub async fn list_warrens( State(state): State>, ) -> Result, ApiError> { diff --git a/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs b/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs index b7b5ffe..da72074 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs @@ -10,7 +10,7 @@ use crate::{ AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError, }, - warren::{RenameWarrenEntryError, RenameWarrenEntryRequest}, + warren::RenameWarrenEntryRequest, }, ports::WarrenService, }, @@ -78,12 +78,6 @@ impl From for ApiError { } } -impl From for ApiError { - fn from(_: RenameWarrenEntryError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn rename_warren_entry( State(state): State>, Json(request): Json, diff --git a/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs b/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs index 1d4aff2..4180375 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs @@ -7,10 +7,7 @@ use crate::{ domain::warren::{ models::{ file::{AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError}, - warren::{ - UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesError, - UploadWarrenFilesRequest, - }, + warren::{UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesRequest}, }, ports::WarrenService, }, @@ -95,12 +92,6 @@ impl From for ApiError { } } -impl From for ApiError { - fn from(_: UploadWarrenFilesError) -> Self { - ApiError::InternalServerError("Internal server error".to_string()) - } -} - pub async fn upload_warren_files( State(state): State>, TypedMultipart(multipart): TypedMultipart, diff --git a/backend/src/lib/inbound/http/mod.rs b/backend/src/lib/inbound/http/mod.rs index 995d91d..deb576e 100644 --- a/backend/src/lib/inbound/http/mod.rs +++ b/backend/src/lib/inbound/http/mod.rs @@ -1,3 +1,4 @@ +pub mod errors; pub mod handlers; pub mod responses; @@ -7,7 +8,13 @@ use anyhow::Context; use axum::{Router, http::HeaderValue}; use handlers::warrens; use tokio::net::TcpListener; -use tower_http::{cors::CorsLayer, services::ServeDir}; +use tower::ServiceBuilder; +use tower_http::{ + cors::CorsLayer, + services::ServeDir, + trace::{DefaultOnBodyChunk, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse}, +}; +use tracing::Level; use crate::domain::warren::ports::WarrenService; @@ -55,19 +62,28 @@ impl HttpServer { ) -> anyhow::Result { let cors_layer = cors_layer(&config)?; + let trace_layer = tower_http::trace::TraceLayer::new_for_http() + .make_span_with(|request: &axum::extract::Request| { + let uri = request.uri().to_string(); + tracing::info_span!("http_request", "{} {}", request.method(), uri) + }) + .on_response(DefaultOnResponse::new().level(Level::TRACE)) + .on_request(DefaultOnRequest::new().level(Level::TRACE)) + .on_failure(DefaultOnFailure::new().level(Level::INFO)); + let state = AppState { warren_service: Arc::new(warren_service), }; let mut router = Router::new() .nest("/api", api_routes()) - .layer(cors_layer) + .layer(ServiceBuilder::new().layer(trace_layer).layer(cors_layer)) .with_state(state); if let Some(frontend_dir) = config.static_frontend_dir { - let frontend_service = ServeDir::new(frontend_dir); + tracing::debug!("Registering static frontend: {}", frontend_dir); - log::debug!("Registering static frontend"); + let frontend_service = ServeDir::new(frontend_dir); router = router.fallback_service(frontend_service); } @@ -80,7 +96,7 @@ impl HttpServer { } pub async fn run(self) -> anyhow::Result<()> { - log::info!("Listening on {}", self.listener.local_addr()?); + tracing::info!("Listening on {}", self.listener.local_addr()?); axum::serve(self.listener, self.router) .await diff --git a/backend/src/lib/inbound/http/responses.rs b/backend/src/lib/inbound/http/responses.rs index 792fe8c..86ffc73 100644 --- a/backend/src/lib/inbound/http/responses.rs +++ b/backend/src/lib/inbound/http/responses.rs @@ -64,13 +64,16 @@ impl IntoResponse for ApiError { use ApiError::*; match self { - BadRequest(_) => (StatusCode::BAD_REQUEST, "Bad request".to_string()).into_response(), - NotFound(_) => (StatusCode::NOT_FOUND, "Not found".to_string()).into_response(), - InternalServerError(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Internal server error".to_string(), - ) - .into_response(), + BadRequest(e) => (StatusCode::BAD_REQUEST, e).into_response(), + NotFound(e) => (StatusCode::NOT_FOUND, e).into_response(), + InternalServerError(e) => { + tracing::error!("{}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal Server Error".to_string(), + ) + .into_response() + } } } } diff --git a/backend/src/lib/outbound/file_system.rs b/backend/src/lib/outbound/file_system.rs index 4224589..0bf75f2 100644 --- a/backend/src/lib/outbound/file_system.rs +++ b/backend/src/lib/outbound/file_system.rs @@ -1,7 +1,10 @@ use std::time::UNIX_EPOCH; use anyhow::{Context, anyhow, bail}; -use tokio::{fs, io::AsyncWriteExt as _}; +use tokio::{ + fs, + io::{self, AsyncWriteExt as _}, +}; use crate::domain::warren::{ models::file::{ @@ -91,9 +94,13 @@ impl FileSystem { /// Actually created a directory in the underlying file system /// /// * `path`: The directory's absolute path (absolute not in relation to the root file system but `self.base_directory`) - async fn create_dir(&self, path: &AbsoluteFilePath) -> anyhow::Result { + async fn create_dir(&self, path: &AbsoluteFilePath) -> io::Result { let file_path = self.get_target_path(path); + if fs::try_exists(&file_path).await? { + return Err(io::ErrorKind::AlreadyExists.into()); + } + fs::create_dir(&file_path).await?; Ok(file_path) @@ -103,7 +110,7 @@ impl FileSystem { /// /// * `path`: The directory's absolute path (absolute not in relation to the root file system but `self.base_directory`) /// * `force`: Whether to delete directories that are not empty - async fn remove_dir(&self, path: &AbsoluteFilePath, force: bool) -> anyhow::Result { + async fn remove_dir(&self, path: &AbsoluteFilePath, force: bool) -> io::Result { let file_path = self.get_target_path(path); if force { @@ -157,7 +164,7 @@ impl FileSystem { }; if fs::try_exists(&new_path).await? { - bail!("File already exists"); + bail!("File exists"); } fs::rename(current_path, &new_path).await?; @@ -182,35 +189,40 @@ impl FileSystemRepository for FileSystem { &self, request: CreateDirectoryRequest, ) -> Result { - let created_path = self.create_dir(request.path()).await.context(format!( - "Failed to create directory at path {}", - request.path() - ))?; - - Ok(created_path) + match self.create_dir(request.path()).await { + Ok(path) => Ok(path), + Err(e) => match e.kind() { + std::io::ErrorKind::AlreadyExists => Err(CreateDirectoryError::Exists), + _ => Err(anyhow!("Failed to create directory at path: {}", request.path()).into()), + }, + } } async fn delete_directory( &self, request: DeleteDirectoryRequest, ) -> Result { - let deleted_path = self - .remove_dir(request.path(), false) - .await - .context(format!("Failed to delete directory at {}", request.path()))?; - - Ok(deleted_path) + match self.remove_dir(request.path(), request.force()).await { + Ok(deleted_path) => return Ok(deleted_path), + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => Err(DeleteDirectoryError::NotFound), + std::io::ErrorKind::DirectoryNotEmpty => Err(DeleteDirectoryError::NotEmpty), + _ => Err(anyhow!("Failed to delete directory at {}: {e:?}", request.path()).into()), + }, + } } async fn create_file(&self, request: CreateFileRequest) -> Result { let file_path = self .write_file(request.path(), request.data()) .await - .context(format!( - "Failed to write {} byte(s) to path {}", - request.data().len(), - request.path() - ))?; + .map_err(|e| { + anyhow!( + "Failed to write {} byte(s) to path {}: {e:?}", + request.data().len(), + request.path() + ) + })?; Ok(file_path) } @@ -231,11 +243,13 @@ impl FileSystemRepository for FileSystem { let new_path = self .rename(request.path(), request.new_name()) .await - .context(format!( - "Failed to rename {} to {}", - request.path(), - request.new_name() - ))?; + .map_err(|e| { + anyhow!( + "Failed to rename {} to {}: {e:?}", + request.path(), + request.new_name() + ) + })?; Ok(new_path) } diff --git a/backend/src/lib/outbound/metrics_debug_logger.rs b/backend/src/lib/outbound/metrics_debug_logger.rs index 48eecda..037c7a2 100644 --- a/backend/src/lib/outbound/metrics_debug_logger.rs +++ b/backend/src/lib/outbound/metrics_debug_logger.rs @@ -11,114 +11,114 @@ impl MetricsDebugLogger { impl WarrenMetrics for MetricsDebugLogger { async fn record_warren_list_success(&self) { - log::debug!("[Metrics] Warren list succeeded"); + tracing::debug!("[Metrics] Warren list succeeded"); } async fn record_warren_list_failure(&self) { - log::debug!("[Metrics] Warren list failed"); + tracing::debug!("[Metrics] Warren list failed"); } async fn record_warren_fetch_success(&self) { - log::debug!("[Metrics] Warren fetch succeeded"); + tracing::debug!("[Metrics] Warren fetch succeeded"); } async fn record_warren_fetch_failure(&self) { - log::debug!("[Metrics] Warren fetch failed"); + tracing::debug!("[Metrics] Warren fetch failed"); } async fn record_list_warren_files_success(&self) { - log::debug!("[Metrics] Warren list files succeeded"); + tracing::debug!("[Metrics] Warren list files succeeded"); } async fn record_list_warren_files_failure(&self) { - log::debug!("[Metrics] Warren list files failed"); + tracing::debug!("[Metrics] Warren list files failed"); } async fn record_warren_directory_creation_success(&self) { - log::debug!("[Metrics] Warren directory creation succeeded"); + tracing::debug!("[Metrics] Warren directory creation succeeded"); } async fn record_warren_directory_creation_failure(&self) { - log::debug!("[Metrics] Warren directory creation failed"); + tracing::debug!("[Metrics] Warren directory creation failed"); } async fn record_warren_directory_deletion_success(&self) { - log::debug!("[Metrics] Warren directory deletion succeeded"); + tracing::debug!("[Metrics] Warren directory deletion succeeded"); } async fn record_warren_directory_deletion_failure(&self) { - log::debug!("[Metrics] Warren directory deletion failed"); + tracing::debug!("[Metrics] Warren directory deletion failed"); } async fn record_warren_file_upload_success(&self) { - log::debug!("[Metrics] Warren file upload succeeded"); + tracing::debug!("[Metrics] Warren file upload succeeded"); } async fn record_warren_file_upload_failure(&self) { - log::debug!("[Metrics] Warren file upload failed"); + tracing::debug!("[Metrics] Warren file upload failed"); } async fn record_warren_files_upload_success(&self) { - log::debug!("[Metrics] Warren files upload succeded"); + tracing::debug!("[Metrics] Warren files upload succeded"); } async fn record_warren_files_upload_failure(&self) { - log::debug!("[Metrics] Warren files upload failed at least partially"); + tracing::debug!("[Metrics] Warren files upload failed at least partially"); } async fn record_warren_file_deletion_success(&self) { - log::debug!("[Metrics] Warren file deletion succeeded"); + tracing::debug!("[Metrics] Warren file deletion succeeded"); } async fn record_warren_file_deletion_failure(&self) { - log::debug!("[Metrics] Warren file deletion failed"); + tracing::debug!("[Metrics] Warren file deletion failed"); } async fn record_warren_entry_rename_success(&self) { - log::debug!("[Metrics] Warren entry rename succeeded"); + tracing::debug!("[Metrics] Warren entry rename succeeded"); } async fn record_warren_entry_rename_failure(&self) { - log::debug!("[Metrics] Warren entry rename failed"); + tracing::debug!("[Metrics] Warren entry rename failed"); } } impl FileSystemMetrics for MetricsDebugLogger { async fn record_list_files_success(&self) { - log::debug!("[Metrics] File list succeeded"); + tracing::debug!("[Metrics] File list succeeded"); } async fn record_list_files_failure(&self) { - log::debug!("[Metrics] File list failed"); + tracing::debug!("[Metrics] File list failed"); } async fn record_directory_creation_success(&self) { - log::debug!("[Metrics] Directory creation succeeded"); + tracing::debug!("[Metrics] Directory creation succeeded"); } async fn record_directory_creation_failure(&self) { - log::debug!("[Metrics] Directory creation failed"); + tracing::debug!("[Metrics] Directory creation failed"); } async fn record_directory_deletion_success(&self) { - log::debug!("[Metrics] Directory deletion succeeded"); + tracing::debug!("[Metrics] Directory deletion succeeded"); } async fn record_directory_deletion_failure(&self) { - log::debug!("[Metrics] Directory deletion failed"); + tracing::debug!("[Metrics] Directory deletion failed"); } async fn record_file_creation_success(&self) { - log::debug!("[Metrics] File creation succeeded"); + tracing::debug!("[Metrics] File creation succeeded"); } async fn record_file_creation_failure(&self) { - log::debug!("[Metrics] File creation failed"); + tracing::debug!("[Metrics] File creation failed"); } async fn record_file_deletion_success(&self) { - log::debug!("[Metrics] File deletion succeeded"); + tracing::debug!("[Metrics] File deletion succeeded"); } async fn record_file_deletion_failure(&self) { - log::debug!("[Metrics] File deletion failed"); + tracing::debug!("[Metrics] File deletion failed"); } async fn record_entry_rename_success(&self) -> () { - log::debug!("[Metrics] Entry rename succeeded"); + tracing::debug!("[Metrics] Entry rename succeeded"); } async fn record_entry_rename_failure(&self) -> () { - log::debug!("[Metrics] Entry rename failed"); + tracing::debug!("[Metrics] Entry rename failed"); } } diff --git a/backend/src/lib/outbound/notifier_debug_logger.rs b/backend/src/lib/outbound/notifier_debug_logger.rs index 60a6ea5..f93ad53 100644 --- a/backend/src/lib/outbound/notifier_debug_logger.rs +++ b/backend/src/lib/outbound/notifier_debug_logger.rs @@ -17,15 +17,15 @@ impl NotifierDebugLogger { impl WarrenNotifier for NotifierDebugLogger { async fn warrens_listed(&self, warrens: &Vec) { - log::debug!("[Notifier] Listed {} warren(s)", warrens.len()); + tracing::debug!("[Notifier] Listed {} warren(s)", warrens.len()); } async fn warren_fetched(&self, warren: &Warren) { - log::debug!("[Notifier] Fetched warren {}", warren.name()); + tracing::debug!("[Notifier] Fetched warren {}", warren.name()); } async fn warren_files_listed(&self, warren: &Warren, files: &Vec) { - log::debug!( + tracing::debug!( "[Notifier] Listed {} file(s) in warren {}", files.len(), warren.name() @@ -33,7 +33,7 @@ impl WarrenNotifier for NotifierDebugLogger { } async fn warren_directory_created(&self, warren: &Warren, path: &FilePath) { - log::debug!( + tracing::debug!( "[Notifier] Created directory {} in warren {}", path, warren.name() @@ -41,7 +41,7 @@ impl WarrenNotifier for NotifierDebugLogger { } async fn warren_directory_deleted(&self, warren: &Warren, path: &FilePath) { - log::debug!( + tracing::debug!( "[Notifier] Deleted directory {} in warren {}", path, warren.name() @@ -49,7 +49,7 @@ impl WarrenNotifier for NotifierDebugLogger { } async fn warren_file_uploaded(&self, warren: &Warren, path: &FilePath) { - log::debug!( + tracing::debug!( "[Notifier] Uploaded file {} to warren {}", path, warren.name() @@ -57,7 +57,7 @@ impl WarrenNotifier for NotifierDebugLogger { } async fn warren_files_uploaded(&self, warren: &Warren, paths: &[FilePath]) { - log::debug!( + tracing::debug!( "[Notifier] Uploaded {} file(s) to warren {}", paths.len(), warren.name() @@ -65,7 +65,7 @@ impl WarrenNotifier for NotifierDebugLogger { } async fn warren_file_deleted(&self, warren: &Warren, path: &FilePath) { - log::debug!( + tracing::debug!( "[Notifier] Deleted file {} from warren {}", path, warren.name(), @@ -78,7 +78,7 @@ impl WarrenNotifier for NotifierDebugLogger { old_path: &crate::domain::warren::models::file::AbsoluteFilePath, new_path: &FilePath, ) { - log::debug!( + tracing::debug!( "[Notifier] Renamed file {} to {} in warren {}", old_path, new_path, @@ -89,26 +89,26 @@ impl WarrenNotifier for NotifierDebugLogger { impl FileSystemNotifier for NotifierDebugLogger { async fn files_listed(&self, files: &Vec) { - log::debug!("[Notifier] Listed {} file(s)", files.len()); + tracing::debug!("[Notifier] Listed {} file(s)", files.len()); } async fn directory_created(&self, path: &FilePath) { - log::debug!("[Notifier] Created directory {}", path); + tracing::debug!("[Notifier] Created directory {}", path); } async fn directory_deleted(&self, path: &FilePath) { - log::debug!("[Notifier] Deleted directory {}", path); + tracing::debug!("[Notifier] Deleted directory {}", path); } async fn file_created(&self, path: &FilePath) { - log::debug!("[Notifier] Created file {}", path); + tracing::debug!("[Notifier] Created file {}", path); } async fn file_deleted(&self, path: &FilePath) { - log::debug!("[Notifier] Deleted file {}", path); + tracing::debug!("[Notifier] Deleted file {}", path); } async fn entry_renamed(&self, old_path: &FilePath, new_path: &FilePath) { - log::debug!("[Notifier] Renamed file {} to {}", old_path, new_path); + tracing::debug!("[Notifier] Renamed file {} to {}", old_path, new_path); } } diff --git a/backend/src/lib/outbound/postgres.rs b/backend/src/lib/outbound/postgres.rs index afcd6e7..f8c380f 100644 --- a/backend/src/lib/outbound/postgres.rs +++ b/backend/src/lib/outbound/postgres.rs @@ -114,7 +114,7 @@ impl WarrenRepository for Postgres { let warrens = self .list_warrens(&mut connection) .await - .map_err(|err| anyhow!(err).context("Failed to fetch warren with id"))?; + .map_err(|err| anyhow!(err).context("Failed to list warrens"))?; Ok(warrens) } diff --git a/frontend/lib/api/warrens.ts b/frontend/lib/api/warrens.ts index 7d67f36..c4cbb26 100644 --- a/frontend/lib/api/warrens.ts +++ b/frontend/lib/api/warrens.ts @@ -68,7 +68,7 @@ export async function createDirectory( if (rest == null) { rest = '/'; } else { - rest = '/' + rest + '/'; + rest = '/' + decodeURI(rest) + '/'; } rest += directoryName; @@ -113,7 +113,7 @@ export async function deleteWarrenDirectory( if (rest == null) { rest = '/'; } else { - rest = '/' + rest + '/'; + rest = '/' + decodeURI(rest) + '/'; } rest += directoryName; @@ -159,7 +159,7 @@ export async function deleteWarrenFile( if (rest == null) { rest = '/'; } else { - rest = '/' + rest + '/'; + rest = '/' + decodeURI(rest) + '/'; } rest += fileName; @@ -205,7 +205,7 @@ export async function uploadToWarren( if (rest == null) { rest = '/'; } else { - rest = '/' + rest; + rest = '/' + decodeURI(rest); } const xhr = new XMLHttpRequest(); @@ -268,7 +268,7 @@ export async function renameWarrenEntry( if (rest == null) { rest = '/'; } else { - rest = '/' + rest + '/'; + rest = '/' + decodeURI(rest) + '/'; } rest += currentName;