From 23fdd55612a9cf1d4173b26b8e462f4dcf4175f9 Mon Sep 17 00:00:00 2001 From: 409 <409dev@protonmail.com> Date: Mon, 28 Jul 2025 22:38:28 +0200 Subject: [PATCH] refactor file system operations the most notable improvement is that uploads are now using streams so they no longer require the entire file to be stored in memory --- backend/Cargo.lock | 478 ++--------------- backend/Cargo.toml | 4 +- .../domain/warren/models/auth_session/mod.rs | 12 +- .../src/lib/domain/warren/models/file/mod.rs | 14 +- .../lib/domain/warren/models/file/requests.rs | 175 ------- .../domain/warren/models/file/requests/cat.rs | 30 ++ .../domain/warren/models/file/requests/ls.rs | 30 ++ .../warren/models/file/requests/mkdir.rs | 30 ++ .../domain/warren/models/file/requests/mod.rs | 15 + .../domain/warren/models/file/requests/mv.rs | 37 ++ .../domain/warren/models/file/requests/rm.rs | 37 ++ .../warren/models/file/requests/save.rs | 53 ++ .../warren/models/file/requests/touch.rs | 30 ++ .../domain/warren/models/warren/requests.rs | 488 ++++++++++-------- .../src/lib/domain/warren/ports/metrics.rs | 94 ++-- backend/src/lib/domain/warren/ports/mod.rs | 163 +++--- .../src/lib/domain/warren/ports/notifier.rs | 118 ++--- .../src/lib/domain/warren/ports/repository.rs | 47 +- backend/src/lib/domain/warren/service/auth.rs | 309 ++++++----- .../lib/domain/warren/service/file_system.rs | 145 +++--- .../src/lib/domain/warren/service/warren.rs | 223 ++++---- backend/src/lib/inbound/http/errors.rs | 103 ++-- .../lib/inbound/http/handlers/warrens/mod.rs | 41 +- .../handlers/warrens/rename_warren_entry.rs | 96 ---- .../handlers/warrens/upload_warren_files.rs | 139 +++-- .../warrens/{fetch_file.rs => warren_cat.rs} | 33 +- .../{list_warren_files.rs => warren_ls.rs} | 34 +- ...te_warren_directory.rs => warren_mkdir.rs} | 30 +- ...ete_warren_directory.rs => warren_move.rs} | 52 +- .../{delete_warren_file.rs => warren_rm.rs} | 33 +- backend/src/lib/outbound/file_system.rs | 219 ++++---- .../src/lib/outbound/metrics_debug_logger.rs | 186 ++++--- .../src/lib/outbound/notifier_debug_logger.rs | 125 ++--- frontend/components/actions/UploadDialog.vue | 2 +- .../components/actions/UploadListEntry.vue | 5 +- frontend/lib/api/warrens.ts | 25 +- 36 files changed, 1567 insertions(+), 2088 deletions(-) delete mode 100644 backend/src/lib/domain/warren/models/file/requests.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/cat.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/ls.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/mkdir.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/mod.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/mv.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/rm.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/save.rs create mode 100644 backend/src/lib/domain/warren/models/file/requests/touch.rs delete mode 100644 backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs rename backend/src/lib/inbound/http/handlers/warrens/{fetch_file.rs => warren_cat.rs} (67%) rename backend/src/lib/inbound/http/handlers/warrens/{list_warren_files.rs => warren_ls.rs} (72%) rename backend/src/lib/inbound/http/handlers/warrens/{create_warren_directory.rs => warren_mkdir.rs} (65%) rename backend/src/lib/inbound/http/handlers/warrens/{delete_warren_directory.rs => warren_move.rs} (55%) rename backend/src/lib/inbound/http/handlers/warrens/{delete_warren_file.rs => warren_rm.rs} (65%) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 10a8024..0b207d1 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -17,17 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -76,23 +65,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "atoi" version = "2.0.0" @@ -164,38 +136,27 @@ dependencies = [ ] [[package]] -name = "axum_typed_multipart" -version = "0.16.3" +name = "axum-extra" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7915d0c957ffd1a28edd7dff1f6d7b79c80e519b19b0961a8c80423504c45a82" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" dependencies = [ - "anyhow", - "async-trait", "axum", - "axum_typed_multipart_macros", + "axum-core", "bytes", - "chrono", - "futures-core", + "fastrand", "futures-util", - "rust_decimal", - "tempfile", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "axum_typed_multipart_macros" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9e427c074d9f5355a2a7d969cf3a48dde6bf888b120937fb4c96ad2604b48a" -dependencies = [ - "darling", - "heck", - "proc-macro-error2", - "quote", - "syn 2.0.104", - "ubyte", + "http", + "http-body", + "http-body-util", + "mime", + "multer", + "pin-project-lite", + "rustversion", + "serde", + "tower", + "tower-layer", + "tower-service", ] [[package]] @@ -234,18 +195,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blake2" version = "0.10.6" @@ -264,57 +213,12 @@ dependencies = [ "generic-array", ] -[[package]] -name = "borsh" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -342,12 +246,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.41" @@ -432,41 +330,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.104", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.104", -] - [[package]] name = "der" version = "0.7.10" @@ -504,7 +367,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "unicode-xid", ] @@ -528,7 +391,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -637,12 +500,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures-channel" version = "0.3.31" @@ -695,7 +552,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -745,19 +602,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] [[package]] @@ -766,15 +611,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.4" @@ -792,7 +628,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -1031,12 +867,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.0.3" @@ -1065,7 +895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown", ] [[package]] @@ -1208,7 +1038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -1431,37 +1261,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -1471,26 +1270,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quote" version = "1.0.40" @@ -1500,18 +1279,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.5" @@ -1539,7 +1306,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom", ] [[package]] @@ -1580,44 +1347,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rsa" version = "0.9.8" @@ -1638,22 +1367,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rust_decimal" -version = "1.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand", - "rkyv", - "serde", - "serde_json", -] - [[package]] name = "rustc-demangle" version = "0.1.25" @@ -1691,12 +1404,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "serde" version = "1.0.219" @@ -1714,7 +1421,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1807,12 +1514,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "slab" version = "0.4.10" @@ -1887,7 +1588,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown", "hashlink", "indexmap", "log", @@ -1917,7 +1618,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn", ] [[package]] @@ -1940,7 +1641,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.104", + "syn", "tokio", "url", ] @@ -2074,29 +1775,12 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.104" @@ -2122,26 +1806,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", + "syn", ] [[package]] @@ -2161,7 +1826,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2257,7 +1922,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2284,23 +1949,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - [[package]] name = "tower" version = "0.5.2" @@ -2375,7 +2023,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2419,12 +2067,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" - [[package]] name = "unicase" version = "2.8.1" @@ -2517,11 +2159,13 @@ dependencies = [ "anyhow", "argon2", "axum", - "axum_typed_multipart", + "axum-extra", "base64", + "bytes", "chrono", "derive_more", "dotenv", + "futures-util", "hex", "mime_guess", "regex", @@ -2545,15 +2189,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasite" version = "0.1.0" @@ -2582,7 +2217,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-shared", ] @@ -2604,7 +2239,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2671,7 +2306,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2682,7 +2317,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2857,39 +2492,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "yoke" version = "0.8.0" @@ -2910,7 +2518,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -2931,7 +2539,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2951,7 +2559,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -2991,5 +2599,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8ed98b7..886e374 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -15,11 +15,13 @@ path = "src/bin/backend/main.rs" anyhow = "1.0.98" argon2 = "0.5.3" axum = { version = "0.8.4", features = ["multipart", "query"] } -axum_typed_multipart = "0.16.3" +axum-extra = { version = "0.10.1", features = ["multipart"] } base64 = "0.22.1" +bytes = "1.10.1" chrono = "0.4.41" derive_more = { version = "2.0.1", features = ["display"] } dotenv = "0.15.0" +futures-util = "0.3.31" hex = "0.4.3" mime_guess = "2.0.5" regex = "1.11.1" diff --git a/backend/src/lib/domain/warren/models/auth_session/mod.rs b/backend/src/lib/domain/warren/models/auth_session/mod.rs index efcf518..c77c51e 100644 --- a/backend/src/lib/domain/warren/models/auth_session/mod.rs +++ b/backend/src/lib/domain/warren/models/auth_session/mod.rs @@ -1,6 +1,6 @@ use chrono::NaiveDateTime; use derive_more::Display; -use requests::FetchAuthSessionError; +use requests::{FetchAuthSessionError, FetchAuthSessionRequest}; use thiserror::Error; use uuid::Uuid; @@ -145,6 +145,16 @@ impl AuthRequest { pub fn unpack(self) -> (AuthSessionIdWithType, T) { (self.auth, self.value) } + + pub fn into_value(self) -> T { + self.value + } +} + +impl From<&AuthRequest> for FetchAuthSessionRequest { + fn from(value: &AuthRequest) -> Self { + FetchAuthSessionRequest::new(value.auth().session_id().clone()) + } } #[derive(Debug, Error)] diff --git a/backend/src/lib/domain/warren/models/file/mod.rs b/backend/src/lib/domain/warren/models/file/mod.rs index c5e5765..e1c0a60 100644 --- a/backend/src/lib/domain/warren/models/file/mod.rs +++ b/backend/src/lib/domain/warren/models/file/mod.rs @@ -126,8 +126,14 @@ impl FilePath { pub fn join(&self, other: &RelativeFilePath) -> Self { let mut path = self.0.clone(); - path.push('/'); - path.push_str(&other.0); + + if !other.0.is_empty() { + if path != "/" { + path.push('/'); + } + + path.push_str(&other.0); + } Self(path) } @@ -173,7 +179,9 @@ impl AbsoluteFilePath { pub fn join(mut self, other: &RelativeFilePath) -> Self { if !other.0.is_empty() { - self.0.push('/'); + if self.0 != "/" { + self.0.push('/'); + } self.0.push_str(&other.0); } diff --git a/backend/src/lib/domain/warren/models/file/requests.rs b/backend/src/lib/domain/warren/models/file/requests.rs deleted file mode 100644 index 336efb9..0000000 --- a/backend/src/lib/domain/warren/models/file/requests.rs +++ /dev/null @@ -1,175 +0,0 @@ -use thiserror::Error; - -use super::{AbsoluteFilePath, FileName}; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ListFilesRequest { - path: AbsoluteFilePath, -} - -impl ListFilesRequest { - pub fn new(path: AbsoluteFilePath) -> Self { - Self { path } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } -} - -#[derive(Debug, Error)] -pub enum ListFilesError { - #[error("Directory at path {0} does not exist")] - NotFound(String), - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteDirectoryRequest { - path: AbsoluteFilePath, - force: bool, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteFileRequest { - path: AbsoluteFilePath, -} - -impl DeleteDirectoryRequest { - 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 { - pub fn new(path: AbsoluteFilePath) -> Self { - Self { path } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } -} - -#[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), -} - -#[derive(Debug, Error)] -pub enum DeleteFileError { - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CreateDirectoryRequest { - path: AbsoluteFilePath, -} - -impl CreateDirectoryRequest { - pub fn new(path: AbsoluteFilePath) -> Self { - Self { path } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } -} - -#[derive(Debug, Error)] -pub enum CreateDirectoryError { - #[error("The directory already exists")] - Exists, - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CreateFileRequest { - path: AbsoluteFilePath, - data: Box<[u8]>, -} - -impl CreateFileRequest { - pub fn new(path: AbsoluteFilePath, data: Box<[u8]>) -> Self { - Self { path, data } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } - - pub fn data(&self) -> &[u8] { - &self.data - } -} - -#[derive(Debug, Error)] -pub enum CreateFileError { - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RenameEntryRequest { - path: AbsoluteFilePath, - new_name: FileName, -} - -impl RenameEntryRequest { - pub fn new(path: AbsoluteFilePath, new_name: FileName) -> Self { - Self { path, new_name } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } - - pub fn new_name(&self) -> &FileName { - &self.new_name - } -} - -#[derive(Debug, Error)] -pub enum RenameEntryError { - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct FetchFileRequest { - path: AbsoluteFilePath, -} - -impl FetchFileRequest { - pub fn new(path: AbsoluteFilePath) -> Self { - Self { path } - } - - pub fn path(&self) -> &AbsoluteFilePath { - &self.path - } -} - -#[derive(Debug, Error)] -pub enum FetchFileError { - #[error("The file does not exist")] - NotFound, - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} diff --git a/backend/src/lib/domain/warren/models/file/requests/cat.rs b/backend/src/lib/domain/warren/models/file/requests/cat.rs new file mode 100644 index 0000000..0f1e6d8 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/cat.rs @@ -0,0 +1,30 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CatRequest { + path: AbsoluteFilePath, +} + +impl CatRequest { + pub fn new(path: AbsoluteFilePath) -> Self { + Self { path } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn into_path(self) -> AbsoluteFilePath { + self.path + } +} + +#[derive(Debug, Error)] +pub enum CatError { + #[error("The file does not exist")] + NotFound, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/file/requests/ls.rs b/backend/src/lib/domain/warren/models/file/requests/ls.rs new file mode 100644 index 0000000..3c70a58 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/ls.rs @@ -0,0 +1,30 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LsRequest { + path: AbsoluteFilePath, +} + +impl LsRequest { + pub fn new(path: AbsoluteFilePath) -> Self { + Self { path } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn into_path(self) -> AbsoluteFilePath { + self.path + } +} + +#[derive(Debug, Error)] +pub enum LsError { + #[error("File at path {0} does not exist")] + NotFound(String), + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/file/requests/mkdir.rs b/backend/src/lib/domain/warren/models/file/requests/mkdir.rs new file mode 100644 index 0000000..834b3dc --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/mkdir.rs @@ -0,0 +1,30 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MkdirRequest { + path: AbsoluteFilePath, +} + +impl MkdirRequest { + pub fn new(path: AbsoluteFilePath) -> Self { + Self { path } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn into_path(self) -> AbsoluteFilePath { + self.path + } +} + +#[derive(Debug, Error)] +pub enum MkdirError { + #[error("The directory already exists")] + Exists, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/file/requests/mod.rs b/backend/src/lib/domain/warren/models/file/requests/mod.rs new file mode 100644 index 0000000..e919807 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/mod.rs @@ -0,0 +1,15 @@ +mod cat; +mod ls; +mod mkdir; +mod mv; +mod rm; +mod save; +mod touch; + +pub use cat::*; +pub use ls::*; +pub use mkdir::*; +pub use mv::*; +pub use rm::*; +pub use save::*; +pub use touch::*; diff --git a/backend/src/lib/domain/warren/models/file/requests/mv.rs b/backend/src/lib/domain/warren/models/file/requests/mv.rs new file mode 100644 index 0000000..46c2fa2 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/mv.rs @@ -0,0 +1,37 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MvRequest { + path: AbsoluteFilePath, + target_path: AbsoluteFilePath, +} + +impl MvRequest { + 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 unpack(self) -> (AbsoluteFilePath, AbsoluteFilePath) { + (self.path, self.target_path) + } +} + +#[derive(Debug, Error)] +pub enum MvError { + #[error("The path does not exist")] + NotFound, + #[error("The target path already exists")] + AlreadyExists, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/file/requests/rm.rs b/backend/src/lib/domain/warren/models/file/requests/rm.rs new file mode 100644 index 0000000..52ceab0 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/rm.rs @@ -0,0 +1,37 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RmRequest { + path: AbsoluteFilePath, + force: bool, +} + +impl RmRequest { + pub fn new(path: AbsoluteFilePath, force: bool) -> Self { + Self { path, force } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn into_path(self) -> AbsoluteFilePath { + self.path + } + + pub fn force(&self) -> bool { + self.force + } +} + +#[derive(Debug, Error)] +pub enum RmError { + #[error("The path does not exist")] + NotFound, + #[error("The directory is not empty")] + NotEmpty, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/file/requests/save.rs b/backend/src/lib/domain/warren/models/file/requests/save.rs new file mode 100644 index 0000000..e31eee0 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/save.rs @@ -0,0 +1,53 @@ +use thiserror::Error; + +use crate::domain::warren::models::{file::AbsoluteFilePath, warren::UploadFileStream}; + +pub struct SaveRequest<'s> { + path: AbsoluteFilePath, + stream: UploadFileStream<'s>, +} + +impl<'s> SaveRequest<'s> { + pub fn new(path: AbsoluteFilePath, stream: UploadFileStream<'s>) -> Self { + Self { path, stream } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn stream(&self) -> &'s UploadFileStream { + &self.stream + } + + pub fn stream_mut(&mut self) -> &'s mut UploadFileStream { + &mut self.stream + } + + pub fn unpack(self) -> (AbsoluteFilePath, UploadFileStream<'s>) { + (self.path, self.stream) + } +} + +#[derive(Debug, Error)] +pub enum SaveError { + #[error("The path does not exist")] + NotFound, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SaveResponse { + files: Vec, +} + +impl SaveResponse { + pub fn new(files: Vec) -> Self { + Self { files } + } + + pub fn files(&self) -> &Vec { + &self.files + } +} diff --git a/backend/src/lib/domain/warren/models/file/requests/touch.rs b/backend/src/lib/domain/warren/models/file/requests/touch.rs new file mode 100644 index 0000000..30a8550 --- /dev/null +++ b/backend/src/lib/domain/warren/models/file/requests/touch.rs @@ -0,0 +1,30 @@ +use thiserror::Error; + +use crate::domain::warren::models::file::AbsoluteFilePath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TouchRequest { + path: AbsoluteFilePath, +} + +impl TouchRequest { + pub fn new(path: AbsoluteFilePath) -> Self { + Self { path } + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn into_path(self) -> AbsoluteFilePath { + self.path + } +} + +#[derive(Debug, Error)] +pub enum TouchError { + #[error("The path does not exist")] + NotFound, + #[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 ab9a5fd..01d8e56 100644 --- a/backend/src/lib/domain/warren/models/warren/requests.rs +++ b/backend/src/lib/domain/warren/models/warren/requests.rs @@ -1,11 +1,14 @@ +use bytes::Bytes; +use futures_util::Stream; +use futures_util::StreamExt; use thiserror::Error; use uuid::Uuid; +use crate::domain::warren::models::file::SaveResponse; use crate::domain::warren::models::file::{ - AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError, - CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, - DeleteFileRequest, FetchFileError, FetchFileRequest, File, FileName, FilePath, ListFilesError, - ListFilesRequest, RelativeFilePath, RenameEntryError, RenameEntryRequest, + AbsoluteFilePath, CatError, CatRequest, File, FileName, LsError, LsRequest, MkdirError, + MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, TouchError, + TouchRequest, }; use super::{Warren, WarrenName}; @@ -55,60 +58,73 @@ pub enum FetchWarrensError { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ListWarrenFilesRequest { +pub struct WarrenLsRequest { warren_id: Uuid, - path: AbsoluteFilePath, + base: LsRequest, } -impl ListWarrenFilesRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath) -> Self { - Self { warren_id, path } +impl WarrenLsRequest { + pub fn new(warren_id: Uuid, base: LsRequest) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn path(&self) -> &AbsoluteFilePath { - &self.path + pub fn base(&self) -> &LsRequest { + &self.base } - pub fn to_fs_request(self, warren: &Warren) -> ListFilesRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - ListFilesRequest::new(path) + pub fn build_fs_request(self, warren: &Warren) -> LsRequest { + let path = warren + .path() + .clone() + .join(&self.base.into_path().to_relative()); + + LsRequest::new(path) } } -impl Into for ListWarrenFilesRequest { +impl Into for WarrenLsRequest { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ListWarrenFilesResponse { +pub struct WarrenLsResponse { warren: Warren, + path: AbsoluteFilePath, files: Vec, } -impl ListWarrenFilesResponse { - pub fn new(warren: Warren, files: Vec) -> Self { - Self { warren, files } +impl WarrenLsResponse { + pub fn new(warren: Warren, path: AbsoluteFilePath, files: Vec) -> Self { + Self { + warren, + path, + files, + } } pub fn warren(&self) -> &Warren { &self.warren } + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + pub fn files(&self) -> &Vec { &self.files } } #[derive(Debug, Error)] -pub enum ListWarrenFilesError { +pub enum WarrenLsError { #[error(transparent)] - FileSystem(#[from] ListFilesError), + FileSystem(#[from] LsError), #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] @@ -116,44 +132,48 @@ pub enum ListWarrenFilesError { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CreateWarrenDirectoryRequest { +pub struct WarrenMkdirRequest { warren_id: Uuid, - path: AbsoluteFilePath, + base: MkdirRequest, } -impl CreateWarrenDirectoryRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath) -> Self { - Self { warren_id, path } +impl WarrenMkdirRequest { + pub fn new(warren_id: Uuid, base: MkdirRequest) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn path(&self) -> &AbsoluteFilePath { - &self.path + pub fn base(&self) -> &MkdirRequest { + &self.base } - pub fn to_fs_request(self, warren: &Warren) -> CreateDirectoryRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - CreateDirectoryRequest::new(path) + pub fn build_fs_request(self, warren: &Warren) -> MkdirRequest { + let path = warren + .path() + .clone() + .join(&self.base.into_path().to_relative()); + + MkdirRequest::new(path) } } -impl Into for CreateWarrenDirectoryRequest { +impl Into for WarrenMkdirRequest { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CreateWarrenDirectoryResponse { +pub struct WarrenMkdirResponse { warren: Warren, - path: FilePath, + path: AbsoluteFilePath, } -impl CreateWarrenDirectoryResponse { - pub fn new(warren: Warren, path: FilePath) -> Self { +impl WarrenMkdirResponse { + pub fn new(warren: Warren, path: AbsoluteFilePath) -> Self { Self { warren, path } } @@ -161,15 +181,15 @@ impl CreateWarrenDirectoryResponse { &self.warren } - pub fn path(&self) -> &FilePath { + pub fn path(&self) -> &AbsoluteFilePath { &self.path } } #[derive(Debug, Error)] -pub enum CreateWarrenDirectoryError { +pub enum WarrenMkdirError { #[error(transparent)] - FileSystem(#[from] CreateDirectoryError), + FileSystem(#[from] MkdirError), #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] @@ -177,53 +197,49 @@ pub enum CreateWarrenDirectoryError { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteWarrenDirectoryRequest { +pub struct WarrenRmRequest { warren_id: Uuid, - path: AbsoluteFilePath, - force: bool, + base: RmRequest, } -impl DeleteWarrenDirectoryRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath, force: bool) -> Self { - Self { - warren_id, - path, - force, - } - } - - pub fn to_fs_request(self, warren: &Warren) -> DeleteDirectoryRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - DeleteDirectoryRequest::new(path, self.force) +impl WarrenRmRequest { + pub fn new(warren_id: Uuid, base: RmRequest) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn path(&self) -> &AbsoluteFilePath { - &self.path + pub fn base(&self) -> &RmRequest { + &self.base } - pub fn force(&self) -> bool { - self.force + pub fn build_fs_request(self, warren: &Warren) -> RmRequest { + let force = self.base.force(); + let path = warren + .path() + .clone() + .join(&self.base.into_path().to_relative()); + + RmRequest::new(path, force) } } -impl Into for &DeleteWarrenDirectoryRequest { +impl Into for &WarrenRmRequest { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteWarrenDirectoryResponse { +pub struct WarrenRmResponse { warren: Warren, - path: FilePath, + path: AbsoluteFilePath, } -impl DeleteWarrenDirectoryResponse { - pub fn new(warren: Warren, path: FilePath) -> Self { +impl WarrenRmResponse { + pub fn new(warren: Warren, path: AbsoluteFilePath) -> Self { Self { warren, path } } @@ -231,248 +247,197 @@ impl DeleteWarrenDirectoryResponse { &self.warren } - pub fn path(&self) -> &FilePath { - &self.path - } -} - -#[derive(Debug, Error)] -pub enum DeleteWarrenDirectoryError { - #[error(transparent)] - FileSystem(#[from] DeleteDirectoryError), - #[error(transparent)] - FetchWarren(#[from] FetchWarrenError), - #[error(transparent)] - Unknown(#[from] anyhow::Error), -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteWarrenFileRequest { - warren_id: Uuid, - path: AbsoluteFilePath, -} - -impl DeleteWarrenFileRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath) -> Self { - Self { warren_id, path } - } - - pub fn to_fs_request(self, warren: &Warren) -> DeleteFileRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - DeleteFileRequest::new(path) - } - - pub fn warren_id(&self) -> &Uuid { - &self.warren_id - } - pub fn path(&self) -> &AbsoluteFilePath { &self.path } } -impl Into for &DeleteWarrenFileRequest { - fn into(self) -> FetchWarrenRequest { - FetchWarrenRequest::new(self.warren_id) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeleteWarrenFileResponse { - warren: Warren, - path: FilePath, -} - -impl DeleteWarrenFileResponse { - pub fn new(warren: Warren, path: FilePath) -> Self { - Self { warren, path } - } - - pub fn warren(&self) -> &Warren { - &self.warren - } - - pub fn path(&self) -> &FilePath { - &self.path - } -} - #[derive(Debug, Error)] -pub enum DeleteWarrenFileError { +pub enum WarrenRmError { #[error(transparent)] - FileSystem(#[from] DeleteFileError), + FileSystem(#[from] RmError), #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] Unknown(#[from] anyhow::Error), } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct UploadWarrenFilesRequest { +pub struct WarrenSaveRequest<'s> { warren_id: Uuid, - path: AbsoluteFilePath, - files: UploadFileList, + base: SaveRequest<'s>, } -impl UploadWarrenFilesRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath, files: UploadFileList) -> Self { - Self { - warren_id, - path, - files, - } +impl<'s> WarrenSaveRequest<'s> { + pub fn new(warren_id: Uuid, base: SaveRequest<'s>) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn to_fs_requests(self, warren: &Warren) -> Vec { - let base_upload_path = self.path.as_relative(); + pub fn base(&self) -> &SaveRequest<'s> { + &self.base + } - self.files - .0 - .into_iter() - .map(|f| { - let file_name = FilePath::new(&f.file_name.to_string()).unwrap(); - let relative_file_path: RelativeFilePath = file_name.try_into().unwrap(); - let absolute_file_path = warren - .path() - .clone() - .join(&base_upload_path) - .join(&relative_file_path); - CreateFileRequest::new(absolute_file_path, f.data) - }) - .collect() + pub fn base_mut(&mut self) -> &mut SaveRequest<'s> { + &mut self.base + } + + pub fn build_fs_request(self, warren: &Warren) -> SaveRequest<'s> { + let (base_path, stream) = self.base.unpack(); + let path = warren.path().clone().join(&base_path.to_relative()); + + SaveRequest::new(path, stream) } } -impl Into for &UploadWarrenFilesRequest { +impl Into for &WarrenSaveRequest<'_> { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct UploadWarrenFilesResponse { +pub struct WarrenSaveResponse { warren: Warren, - paths: Vec, + path: AbsoluteFilePath, + base: SaveResponse, } -impl UploadWarrenFilesResponse { - pub fn new(warren: Warren, paths: Vec) -> Self { - Self { warren, paths } +impl WarrenSaveResponse { + pub fn new(warren: Warren, path: AbsoluteFilePath, base: SaveResponse) -> Self { + Self { warren, path, base } } pub fn warren(&self) -> &Warren { &self.warren } - pub fn paths(&self) -> &Vec { - &self.paths + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } + + pub fn base(&self) -> &SaveResponse { + &self.base } } #[derive(Debug, Error)] -pub enum UploadWarrenFilesError { - #[error("Failed to upload the file at index {fail_index}")] - Partial { fail_index: usize }, +pub enum WarrenSaveError { #[error(transparent)] - FileSystem(#[from] CreateFileError), + FileSystem(#[from] SaveError), #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] Unknown(#[from] anyhow::Error), } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct UploadFileList(Vec); +pub struct UploadFileStream<'i>( + Box, anyhow::Error>> + Unpin + Send + 'i>, +); -#[derive(Debug, Clone, Error)] -pub enum UploadFileListError { - #[error("The file list must not be empty")] - Empty, -} - -impl UploadFileList { - pub fn new(files: Vec) -> Result { - if files.len() < 1 { - return Err(UploadFileListError::Empty); - } - - Ok(Self(files)) +impl<'i> UploadFileStream<'i> { + pub fn new(stream: S) -> Self + where + S: Stream, anyhow::Error>> + Unpin + Send + 'i, + { + Self(Box::new(stream)) } } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct UploadFile { +impl<'i> Stream for UploadFileStream<'i> { + type Item = Result, anyhow::Error>; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.get_mut().0.poll_next_unpin(cx) + } + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +pub struct UploadFile<'a> { file_name: FileName, - data: Box<[u8]>, + stream: Box> + Unpin + Send + 'a>, } -impl UploadFile { - pub fn new(file_name: FileName, data: Box<[u8]>) -> Self { - Self { file_name, data } +impl<'a> UploadFile<'a> { + pub fn new(file_name: FileName, stream: S) -> Self + where + S: Stream> + Unpin + Send + 'a, + { + Self { + file_name, + stream: Box::new(stream), + } } pub fn file_name(&self) -> &FileName { &self.file_name } +} - pub fn data(&self) -> &[u8] { - &self.data +impl<'a> Stream for UploadFile<'a> { + type Item = Result; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.get_mut().stream.poll_next_unpin(cx) + } + fn size_hint(&self) -> (usize, Option) { + self.stream.size_hint() } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RenameWarrenEntryRequest { +pub struct WarrenMvRequest { warren_id: Uuid, - path: AbsoluteFilePath, - new_name: FileName, + base: MvRequest, } -impl RenameWarrenEntryRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath, new_name: FileName) -> Self { - Self { - warren_id, - path, - new_name, - } +impl WarrenMvRequest { + pub fn new(warren_id: Uuid, base: MvRequest) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn path(&self) -> &AbsoluteFilePath { - &self.path + pub fn base(&self) -> &MvRequest { + &self.base } - pub fn new_name(&self) -> &FileName { - &self.new_name - } + pub fn build_fs_request(self, warren: &Warren) -> MvRequest { + let (base_path, base_target_path) = self.base.unpack(); + let path = warren.path().clone().join(&base_path.to_relative()); + let target_path = warren.path().clone().join(&base_target_path.to_relative()); - 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) + MvRequest::new(path, target_path) } } -impl Into for &RenameWarrenEntryRequest { +impl Into for &WarrenMvRequest { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RenameWarrenEntryResponse { +pub struct WarrenMvResponse { warren: Warren, old_path: AbsoluteFilePath, - path: FilePath, + path: AbsoluteFilePath, } -impl RenameWarrenEntryResponse { - pub fn new(warren: Warren, old_path: AbsoluteFilePath, path: FilePath) -> Self { +impl WarrenMvResponse { + pub fn new(warren: Warren, old_path: AbsoluteFilePath, path: AbsoluteFilePath) -> Self { Self { warren, old_path, @@ -488,17 +453,17 @@ impl RenameWarrenEntryResponse { &self.old_path } - pub fn path(&self) -> &FilePath { + pub fn path(&self) -> &AbsoluteFilePath { &self.path } } #[derive(Debug, Error)] -pub enum RenameWarrenEntryError { +pub enum WarrenMvError { #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] - Rename(#[from] RenameEntryError), + FileSystem(#[from] MvError), #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -603,42 +568,111 @@ pub enum DeleteWarrenError { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct FetchWarrenFileRequest { +pub struct WarrenCatRequest { warren_id: Uuid, - path: AbsoluteFilePath, + base: CatRequest, } -impl FetchWarrenFileRequest { - pub fn new(warren_id: Uuid, path: AbsoluteFilePath) -> Self { - Self { warren_id, path } +impl WarrenCatRequest { + pub fn new(warren_id: Uuid, base: CatRequest) -> Self { + Self { warren_id, base } } pub fn warren_id(&self) -> &Uuid { &self.warren_id } - pub fn path(&self) -> &AbsoluteFilePath { - &self.path + pub fn base(&self) -> &CatRequest { + &self.base } - pub fn to_fs_request(self, warren: &Warren) -> FetchFileRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - FetchFileRequest::new(path) + pub fn build_fs_request(self, warren: &Warren) -> CatRequest { + let path = warren + .path() + .clone() + .join(&self.base.into_path().to_relative()); + + CatRequest::new(path) } } -impl Into for &FetchWarrenFileRequest { +impl Into for &WarrenCatRequest { fn into(self) -> FetchWarrenRequest { FetchWarrenRequest::new(self.warren_id) } } #[derive(Debug, Error)] -pub enum FetchWarrenFileError { +pub enum WarrenCatError { #[error(transparent)] FetchWarren(#[from] FetchWarrenError), #[error(transparent)] - Fs(#[from] FetchFileError), + FileSystem(#[from] CatError), #[error(transparent)] Unknown(#[from] anyhow::Error), } + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WarrenTouchRequest { + warren_id: Uuid, + base: TouchRequest, +} + +impl WarrenTouchRequest { + pub fn new(warren_id: Uuid, base: TouchRequest) -> Self { + Self { warren_id, base } + } + + pub fn warren_id(&self) -> &Uuid { + &self.warren_id + } + + pub fn base(&self) -> &TouchRequest { + &self.base + } + + pub fn build_fs_request(self, warren: &Warren) -> TouchRequest { + let path = warren + .path() + .clone() + .join(&self.base.into_path().to_relative()); + + TouchRequest::new(path) + } +} + +impl Into for &WarrenTouchRequest { + fn into(self) -> FetchWarrenRequest { + FetchWarrenRequest::new(self.warren_id) + } +} + +#[derive(Debug, Error)] +pub enum WarrenTouchError { + #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] + FileSystem(#[from] TouchError), + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WarrenTouchResponse { + warren: Warren, + path: AbsoluteFilePath, +} + +impl WarrenTouchResponse { + pub fn new(warren: Warren, path: AbsoluteFilePath) -> Self { + Self { warren, path } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn path(&self) -> &AbsoluteFilePath { + &self.path + } +} diff --git a/backend/src/lib/domain/warren/ports/metrics.rs b/backend/src/lib/domain/warren/ports/metrics.rs index 945e47c..b347780 100644 --- a/backend/src/lib/domain/warren/ports/metrics.rs +++ b/backend/src/lib/domain/warren/ports/metrics.rs @@ -17,55 +17,51 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static { fn record_warren_list_success(&self) -> impl Future + Send; fn record_warren_list_failure(&self) -> impl Future + Send; - fn record_list_warren_files_success(&self) -> impl Future + Send; - fn record_list_warren_files_failure(&self) -> impl Future + Send; + fn record_warren_ls_success(&self) -> impl Future + Send; + fn record_warren_ls_failure(&self) -> impl Future + Send; - fn record_warren_fetch_file_success(&self) -> impl Future + Send; - fn record_warren_fetch_file_failure(&self) -> impl Future + Send; + fn record_warren_cat_success(&self) -> impl Future + Send; + fn record_warren_cat_failure(&self) -> impl Future + Send; - fn record_warren_directory_creation_success(&self) -> impl Future + Send; - fn record_warren_directory_creation_failure(&self) -> impl Future + Send; + fn record_warren_mkdir_success(&self) -> impl Future + Send; + fn record_warren_mkdir_failure(&self) -> impl Future + Send; - fn record_warren_directory_deletion_success(&self) -> impl Future + Send; - fn record_warren_directory_deletion_failure(&self) -> impl Future + Send; + fn record_warren_rm_success(&self) -> impl Future + Send; + fn record_warren_rm_failure(&self) -> impl Future + Send; + + fn record_warren_mv_success(&self) -> impl Future + Send; + fn record_warren_mv_failure(&self) -> impl Future + Send; /// A single file upload succeeded - fn record_warren_file_upload_success(&self) -> impl Future + Send; + fn record_warren_save_success(&self) -> impl Future + Send; /// A single file upload failed - fn record_warren_file_upload_failure(&self) -> impl Future + Send; + fn record_warren_save_failure(&self) -> impl Future + Send; - /// An upload succeeded fully - fn record_warren_files_upload_success(&self) -> impl Future + Send; - /// An upload failed at least partially - fn record_warren_files_upload_failure(&self) -> impl Future + Send; - - fn record_warren_file_deletion_success(&self) -> impl Future + Send; - fn record_warren_file_deletion_failure(&self) -> impl Future + Send; - - fn record_warren_entry_rename_success(&self) -> impl Future + Send; - fn record_warren_entry_rename_failure(&self) -> impl Future + Send; + fn record_warren_touch_success(&self) -> impl Future + Send; + fn record_warren_touch_failure(&self) -> impl Future + Send; } pub trait FileSystemMetrics: Clone + Send + Sync + 'static { - fn record_list_files_success(&self) -> impl Future + Send; - fn record_list_files_failure(&self) -> impl Future + Send; + fn record_ls_success(&self) -> impl Future + Send; + fn record_ls_failure(&self) -> impl Future + Send; - fn record_directory_creation_success(&self) -> impl Future + Send; - fn record_directory_creation_failure(&self) -> impl Future + Send; - fn record_directory_deletion_success(&self) -> impl Future + Send; - fn record_directory_deletion_failure(&self) -> impl Future + Send; + fn record_cat_success(&self) -> impl Future + Send; + fn record_cat_failure(&self) -> impl Future + Send; - fn record_file_creation_success(&self) -> impl Future + Send; - fn record_file_creation_failure(&self) -> impl Future + Send; + fn record_mkdir_success(&self) -> impl Future + Send; + fn record_mkdir_failure(&self) -> impl Future + Send; - fn record_file_fetch_success(&self) -> impl Future + Send; - fn record_file_fetch_failure(&self) -> impl Future + Send; + fn record_rm_success(&self) -> impl Future + Send; + fn record_rm_failure(&self) -> impl Future + Send; - fn record_file_deletion_success(&self) -> impl Future + Send; - fn record_file_deletion_failure(&self) -> impl Future + Send; + fn record_mv_success(&self) -> impl Future + Send; + fn record_mv_failure(&self) -> impl Future + Send; - fn record_entry_rename_success(&self) -> impl Future + Send; - fn record_entry_rename_failure(&self) -> impl Future + Send; + fn record_save_success(&self) -> impl Future + Send; + fn record_save_failure(&self) -> impl Future + Send; + + fn record_touch_success(&self) -> impl Future + Send; + fn record_touch_failure(&self) -> impl Future + Send; } pub trait AuthMetrics: Clone + Send + Sync + 'static { @@ -123,26 +119,24 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static { fn record_auth_warren_fetch_success(&self) -> impl Future + Send; fn record_auth_warren_fetch_failure(&self) -> impl Future + Send; - fn record_auth_warren_file_list_success(&self) -> impl Future + Send; - fn record_auth_warren_file_list_failure(&self) -> impl Future + Send; + fn record_auth_warren_ls_success(&self) -> impl Future + Send; + fn record_auth_warren_ls_failure(&self) -> impl Future + Send; - fn record_auth_warren_directory_creation_success(&self) -> impl Future + Send; - fn record_auth_warren_directory_creation_failure(&self) -> impl Future + Send; + fn record_auth_warren_cat_success(&self) -> impl Future + Send; + fn record_auth_warren_cat_failure(&self) -> impl Future + Send; - fn record_auth_warren_directory_deletion_success(&self) -> impl Future + Send; - fn record_auth_warren_directory_deletion_failure(&self) -> impl Future + Send; + fn record_auth_warren_mkdir_success(&self) -> impl Future + Send; + fn record_auth_warren_mkdir_failure(&self) -> impl Future + Send; - fn record_auth_warren_fetch_file_success(&self) -> impl Future + Send; - fn record_auth_warren_fetch_file_failure(&self) -> impl Future + Send; + fn record_auth_warren_rm_success(&self) -> impl Future + Send; + fn record_auth_warren_rm_failure(&self) -> impl Future + Send; - /// An upload succeeded fully - fn record_auth_warren_files_upload_success(&self) -> impl Future + Send; - /// An upload failed at least partially - fn record_auth_warren_files_upload_failure(&self) -> impl Future + Send; + fn record_auth_warren_mv_success(&self) -> impl Future + Send; + fn record_auth_warren_mv_failure(&self) -> impl Future + Send; - fn record_auth_warren_file_deletion_success(&self) -> impl Future + Send; - fn record_auth_warren_file_deletion_failure(&self) -> impl Future + Send; + fn record_auth_warren_save_success(&self) -> impl Future + Send; + fn record_auth_warren_save_failure(&self) -> impl Future + Send; - fn record_auth_warren_entry_rename_success(&self) -> impl Future + Send; - fn record_auth_warren_entry_rename_failure(&self) -> impl Future + Send; + fn record_auth_warren_touch_success(&self) -> impl Future + Send; + fn record_auth_warren_touch_failure(&self) -> impl Future + Send; } diff --git a/backend/src/lib/domain/warren/ports/mod.rs b/backend/src/lib/domain/warren/ports/mod.rs index 524f2f8..eb5a6e4 100644 --- a/backend/src/lib/domain/warren/ports/mod.rs +++ b/backend/src/lib/domain/warren/ports/mod.rs @@ -15,10 +15,9 @@ use super::models::{ }, }, file::{ - CreateDirectoryError, CreateDirectoryRequest, CreateFileError, CreateFileRequest, - DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, DeleteFileRequest, - FetchFileError, FetchFileRequest, File, FilePath, FileStream, ListFilesError, - ListFilesRequest, RenameEntryError, RenameEntryRequest, + CatError, CatRequest, File, FileStream, LsError, LsRequest, MkdirError, MkdirRequest, + MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError, + TouchRequest, }, user::{ CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError, @@ -35,16 +34,14 @@ use super::models::{ }, }, warren::{ - CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, CreateWarrenDirectoryResponse, - CreateWarrenError, CreateWarrenRequest, DeleteWarrenDirectoryError, - DeleteWarrenDirectoryRequest, DeleteWarrenDirectoryResponse, DeleteWarrenError, - DeleteWarrenFileError, DeleteWarrenFileRequest, DeleteWarrenFileResponse, - DeleteWarrenRequest, EditWarrenError, EditWarrenRequest, FetchWarrenError, - FetchWarrenFileError, FetchWarrenFileRequest, FetchWarrenRequest, FetchWarrensError, - FetchWarrensRequest, ListWarrenFilesError, ListWarrenFilesRequest, ListWarrenFilesResponse, - ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest, - RenameWarrenEntryResponse, UploadWarrenFilesError, UploadWarrenFilesRequest, - UploadWarrenFilesResponse, Warren, + CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest, + EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest, + FetchWarrensError, FetchWarrensRequest, ListWarrensError, ListWarrensRequest, Warren, + WarrenCatError, WarrenCatRequest, WarrenLsError, WarrenLsRequest, WarrenLsResponse, + WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, + WarrenMvResponse, WarrenRmError, WarrenRmRequest, WarrenRmResponse, WarrenSaveError, + WarrenSaveRequest, WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest, + WarrenTouchResponse, }, }; @@ -75,73 +72,48 @@ pub trait WarrenService: Clone + Send + Sync + 'static { request: FetchWarrenRequest, ) -> impl Future> + Send; - fn fetch_warren_file( + fn warren_ls( &self, - request: FetchWarrenFileRequest, - ) -> impl Future> + Send; - - fn list_warren_files( + request: WarrenLsRequest, + ) -> impl Future> + Send; + fn warren_cat( &self, - request: ListWarrenFilesRequest, - ) -> impl Future> + Send; - - fn create_warren_directory( + request: WarrenCatRequest, + ) -> impl Future> + Send; + fn warren_mkdir( &self, - request: CreateWarrenDirectoryRequest, - ) -> impl Future> + Send; - - fn delete_warren_directory( + request: WarrenMkdirRequest, + ) -> impl Future> + Send; + fn warren_rm( &self, - request: DeleteWarrenDirectoryRequest, - ) -> impl Future> + Send; - - fn upload_warren_files( + request: WarrenRmRequest, + ) -> impl Future> + Send; + fn warren_mv( &self, - request: UploadWarrenFilesRequest, - ) -> impl Future> + Send; - fn delete_warren_file( + request: WarrenMvRequest, + ) -> impl Future> + Send; + fn warren_save( &self, - request: DeleteWarrenFileRequest, - ) -> impl Future> + Send; - - fn rename_warren_entry( + request: WarrenSaveRequest, + ) -> impl Future> + Send; + fn warren_touch( &self, - request: RenameWarrenEntryRequest, - ) -> impl Future> + Send; + request: WarrenTouchRequest, + ) -> impl Future> + Send; } pub trait FileSystemService: Clone + Send + Sync + 'static { - fn list_files( + fn ls(&self, request: LsRequest) -> impl Future, LsError>> + Send; + fn cat(&self, request: CatRequest) + -> impl Future> + Send; + fn mkdir(&self, request: MkdirRequest) -> impl Future> + Send; + fn rm(&self, request: RmRequest) -> impl Future> + Send; + fn mv(&self, request: MvRequest) -> impl Future> + Send; + fn save( &self, - request: ListFilesRequest, - ) -> impl Future, ListFilesError>> + Send; - - fn create_directory( - &self, - request: CreateDirectoryRequest, - ) -> impl Future> + Send; - fn delete_directory( - &self, - request: DeleteDirectoryRequest, - ) -> impl Future> + Send; - - fn create_file( - &self, - request: CreateFileRequest, - ) -> impl Future> + Send; - fn fetch_file( - &self, - request: FetchFileRequest, - ) -> impl Future> + Send; - fn delete_file( - &self, - request: DeleteFileRequest, - ) -> impl Future> + Send; - - fn rename_entry( - &self, - request: RenameEntryRequest, - ) -> impl Future> + Send; + request: SaveRequest, + ) -> impl Future> + Send; + fn touch(&self, request: TouchRequest) -> impl Future> + Send; } pub trait AuthService: Clone + Send + Sync + 'static { @@ -245,48 +217,39 @@ pub trait AuthService: Clone + Send + Sync + 'static { warren_service: &WS, ) -> impl Future>> + Send; - fn fetch_warren_file( + fn auth_warren_ls( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future>> + Send; - - fn list_warren_files( + ) -> impl Future>> + Send; + fn auth_warren_cat( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future>> + Send; - - fn create_warren_directory( + ) -> impl Future>> + Send; + fn auth_warren_mkdir( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future< - Output = Result>, - > + Send; - - fn delete_warren_directory( + ) -> impl Future>> + Send; + fn auth_warren_rm( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future< - Output = Result>, - > + Send; - - fn upload_warren_files( + ) -> impl Future>> + Send; + fn auth_warren_mv( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future>> + Send; - fn delete_warren_file( + ) -> impl Future>> + Send; + fn auth_warren_save( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future>> + Send; - - fn rename_warren_entry( + ) -> impl Future>> + Send; + fn auth_warren_touch( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; } diff --git a/backend/src/lib/domain/warren/ports/notifier.rs b/backend/src/lib/domain/warren/ports/notifier.rs index c5c66bf..e984f07 100644 --- a/backend/src/lib/domain/warren/ports/notifier.rs +++ b/backend/src/lib/domain/warren/ports/notifier.rs @@ -2,12 +2,12 @@ use uuid::Uuid; use crate::domain::warren::models::{ auth_session::requests::FetchAuthSessionResponse, - file::{AbsoluteFilePath, File, FilePath}, + file::{AbsoluteFilePath, File}, user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User}, user_warren::UserWarren, warren::{ - CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse, - ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren, + Warren, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse, WarrenRmResponse, + WarrenSaveResponse, WarrenTouchResponse, }, }; @@ -19,67 +19,44 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static { fn warrens_fetched(&self, warrens: &Vec) -> impl Future + Send; fn warren_fetched(&self, warren: &Warren) -> impl Future + Send; fn warrens_listed(&self, warrens: &Vec) -> impl Future + Send; - fn warren_files_listed( - &self, - response: &ListWarrenFilesResponse, - ) -> impl Future + Send; - fn warren_directory_created( - &self, - response: &CreateWarrenDirectoryResponse, - ) -> impl Future + Send; - fn warren_directory_deleted( - &self, - response: &DeleteWarrenDirectoryResponse, - ) -> impl Future + Send; - fn warren_file_fetched( + fn warren_ls(&self, response: &WarrenLsResponse) -> impl Future + Send; + fn warren_cat( &self, warren: &Warren, path: &AbsoluteFilePath, ) -> impl Future + Send; + fn warren_mkdir(&self, response: &WarrenMkdirResponse) -> impl Future + Send; + fn warren_rm(&self, response: &WarrenRmResponse) -> impl Future + Send; + fn warren_mv(&self, response: &WarrenMvResponse) -> impl Future + Send; /// A single file was uploaded /// /// * `warren`: The warren the file was uploaded to /// * `path`: The file's path - fn warren_file_uploaded( + fn warren_save( &self, warren: &Warren, - path: &FilePath, + path: &AbsoluteFilePath, ) -> impl Future + Send; - /// A collection of files was uploaded - /// - /// * `warren`: The warren the file was uploaded to - /// * `files`: The files' paths - fn warren_files_uploaded( + fn warren_touch( &self, - response: &UploadWarrenFilesResponse, - ) -> impl Future + Send; - fn warren_file_deleted( - &self, - response: &DeleteWarrenFileResponse, - ) -> impl Future + Send; - - fn warren_entry_renamed( - &self, - response: &RenameWarrenEntryResponse, + warren: &Warren, + path: &AbsoluteFilePath, ) -> impl Future + Send; } pub trait FileSystemNotifier: Clone + Send + Sync + 'static { - fn files_listed(&self, files: &Vec) -> impl Future + Send; - - fn directory_created(&self, path: &FilePath) -> impl Future + Send; - fn directory_deleted(&self, path: &FilePath) -> impl Future + Send; - - fn file_created(&self, path: &FilePath) -> impl Future + Send; - fn file_fetched(&self, path: &AbsoluteFilePath) -> impl Future + Send; - fn file_deleted(&self, path: &FilePath) -> impl Future + Send; - - fn entry_renamed( + fn ls(&self, files: &Vec) -> impl Future + Send; + fn cat(&self, path: &AbsoluteFilePath) -> impl Future + Send; + fn mkdir(&self, path: &AbsoluteFilePath) -> impl Future + Send; + fn rm(&self, path: &AbsoluteFilePath) -> impl Future + Send; + fn mv( &self, - old_path: &FilePath, - new_path: &FilePath, + old_path: &AbsoluteFilePath, + new_path: &AbsoluteFilePath, ) -> impl Future + Send; + fn save(&self, path: &AbsoluteFilePath) -> impl Future + Send; + fn touch(&self, path: &AbsoluteFilePath) -> impl Future + Send; } pub trait AuthNotifier: Clone + Send + Sync + 'static { @@ -145,47 +122,42 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static { ) -> impl Future + Send; fn auth_warren_fetched(&self, user: &User, warren: &Warren) -> impl Future + Send; - fn auth_warren_files_listed( - &self, - user: &User, - response: &ListWarrenFilesResponse, - ) -> impl Future + Send; - fn auth_warren_directory_created( - &self, - user: &User, - response: &CreateWarrenDirectoryResponse, - ) -> impl Future + Send; - fn auth_warren_directory_deleted( - &self, - user: &User, - response: &DeleteWarrenDirectoryResponse, - ) -> impl Future + Send; - fn auth_warren_file_fetched( + fn auth_warren_ls( + &self, + user: &User, + response: &WarrenLsResponse, + ) -> impl Future + Send; + fn auth_warren_cat( &self, user: &User, warren_id: &Uuid, path: &AbsoluteFilePath, ) -> impl Future + Send; - - /// A collection of files was uploaded - /// - /// * `warren`: The warren the file was uploaded to - /// * `files`: The files' paths - fn auth_warren_files_uploaded( + fn auth_warren_mkdir( &self, user: &User, - response: &UploadWarrenFilesResponse, + response: &WarrenMkdirResponse, ) -> impl Future + Send; - fn auth_warren_file_deleted( + fn auth_warren_rm( &self, user: &User, - response: &DeleteWarrenFileResponse, + response: &WarrenRmResponse, ) -> impl Future + Send; - - fn auth_warren_entry_renamed( + fn auth_warren_mv( &self, user: &User, - response: &RenameWarrenEntryResponse, + response: &WarrenMvResponse, + ) -> impl Future + Send; + /// A file was uploaded + fn auth_warren_save( + &self, + user: &User, + response: &WarrenSaveResponse, + ) -> impl Future + Send; + fn auth_warren_touch( + &self, + user: &User, + response: &WarrenTouchResponse, ) -> impl Future + Send; } diff --git a/backend/src/lib/domain/warren/ports/repository.rs b/backend/src/lib/domain/warren/ports/repository.rs index bc00250..5ae904d 100644 --- a/backend/src/lib/domain/warren/ports/repository.rs +++ b/backend/src/lib/domain/warren/ports/repository.rs @@ -7,10 +7,9 @@ use crate::domain::warren::models::{ }, }, file::{ - CreateDirectoryError, CreateDirectoryRequest, CreateFileError, CreateFileRequest, - DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, DeleteFileRequest, - FetchFileError, FetchFileRequest, File, FilePath, FileStream, ListFilesError, - ListFilesRequest, RenameEntryError, RenameEntryRequest, + CatError, CatRequest, File, FileStream, LsError, LsRequest, MkdirError, MkdirRequest, + MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError, + TouchRequest, }, user::{ CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError, @@ -67,37 +66,17 @@ pub trait WarrenRepository: Clone + Send + Sync + 'static { } pub trait FileSystemRepository: Clone + Send + Sync + 'static { - fn list_files( + fn ls(&self, request: LsRequest) -> impl Future, LsError>> + Send; + fn cat(&self, request: CatRequest) + -> impl Future> + Send; + fn mkdir(&self, request: MkdirRequest) -> impl Future> + Send; + fn rm(&self, request: RmRequest) -> impl Future> + Send; + fn mv(&self, request: MvRequest) -> impl Future> + Send; + fn save( &self, - request: ListFilesRequest, - ) -> impl Future, ListFilesError>> + Send; - - fn create_directory( - &self, - request: CreateDirectoryRequest, - ) -> impl Future> + Send; - fn delete_directory( - &self, - request: DeleteDirectoryRequest, - ) -> impl Future> + Send; - - fn create_file( - &self, - request: CreateFileRequest, - ) -> impl Future> + Send; - fn fetch_file( - &self, - request: FetchFileRequest, - ) -> impl Future> + Send; - fn delete_file( - &self, - request: DeleteFileRequest, - ) -> impl Future> + Send; - - fn rename_entry( - &self, - request: RenameEntryRequest, - ) -> impl Future> + Send; + request: SaveRequest, + ) -> impl Future> + Send; + fn touch(&self, request: TouchRequest) -> impl Future> + Send; } pub trait AuthRepository: Clone + Send + Sync + 'static { diff --git a/backend/src/lib/domain/warren/service/auth.rs b/backend/src/lib/domain/warren/service/auth.rs index e04d043..2f2c836 100644 --- a/backend/src/lib/domain/warren/service/auth.rs +++ b/backend/src/lib/domain/warren/service/auth.rs @@ -26,17 +26,14 @@ use crate::{ }, }, warren::{ - CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, - CreateWarrenDirectoryResponse, CreateWarrenError, CreateWarrenRequest, - DeleteWarrenDirectoryError, DeleteWarrenDirectoryRequest, - DeleteWarrenDirectoryResponse, DeleteWarrenError, DeleteWarrenFileError, - DeleteWarrenFileRequest, DeleteWarrenFileResponse, DeleteWarrenRequest, - EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenFileError, - FetchWarrenFileRequest, FetchWarrenRequest, FetchWarrensRequest, - ListWarrenFilesError, ListWarrenFilesRequest, ListWarrenFilesResponse, - RenameWarrenEntryError, RenameWarrenEntryRequest, RenameWarrenEntryResponse, - UploadWarrenFilesError, UploadWarrenFilesRequest, UploadWarrenFilesResponse, - Warren, + CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest, + EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest, + FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenLsError, + WarrenLsRequest, WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest, + WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, WarrenMvResponse, + WarrenRmError, WarrenRmRequest, WarrenRmResponse, WarrenSaveError, + WarrenSaveRequest, WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest, + WarrenTouchResponse, }, }, ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService}, @@ -577,23 +574,60 @@ where .fetch_warrens(FetchWarrensRequest::new( ids.into_iter().map(|uw| uw.into_warren_id()).collect(), )) - .await; + .await + .map_err(|e| AuthError::Custom(e.into())); - result.map_err(|e| AuthError::Custom(e.into())) + result } - async fn fetch_warren_file( + async fn auth_warren_ls( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; - let path = request.path().clone(); - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + 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?; + if !user_warren.can_list_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service + .warren_ls(request) + .await + .map_err(AuthError::Custom); + + if let Ok(response) = result.as_ref() { + self.metrics.record_auth_warren_ls_success().await; + self.notifier + .auth_warren_ls(session_response.user(), response) + .await; + } else { + self.metrics.record_auth_warren_ls_failure().await; + } + + result + } + + async fn auth_warren_cat( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; + + let request = request.into_value(); + let path = request.base().path().clone(); + let user_warren = self .repository .fetch_user_warren(FetchUserWarrenRequest::new( @@ -607,106 +641,30 @@ where } let result = warren_service - .fetch_warren_file(request) + .warren_cat(request) .await .map_err(AuthError::Custom); if let Ok(_stream) = result.as_ref() { - self.metrics.record_auth_warren_fetch_file_success().await; + self.metrics.record_auth_warren_cat_success().await; self.notifier - .auth_warren_file_fetched(session_response.user(), user_warren.warren_id(), &path) + .auth_warren_cat(session_response.user(), user_warren.warren_id(), &path) .await; } else { - self.metrics.record_auth_warren_fetch_file_failure().await; + self.metrics.record_auth_warren_cat_failure().await; } result } - async fn list_warren_files( + async fn auth_warren_mkdir( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; - - let user_warren = self - .repository - .fetch_user_warren(FetchUserWarrenRequest::new( - session_response.user().id().clone(), - request.warren_id().clone(), - )) - .await?; - - if !user_warren.can_list_files() { - return Err(AuthError::InsufficientPermissions); - } - - let result = warren_service.list_warren_files(request).await; - - if let Ok(response) = result.as_ref() { - self.metrics.record_auth_warren_file_list_success().await; - self.notifier - .auth_warren_files_listed(session_response.user(), response) - .await; - } else { - self.metrics.record_auth_warren_file_list_failure().await; - } - - result.map_err(AuthError::Custom) - } - - async fn rename_warren_entry( - &self, - request: AuthRequest, - warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); - - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; - - let user_warren = self - .repository - .fetch_user_warren(FetchUserWarrenRequest::new( - session_response.user().id().clone(), - request.warren_id().clone(), - )) - .await?; - - if !user_warren.can_modify_files() { - return Err(AuthError::InsufficientPermissions); - } - - let result = warren_service.rename_warren_entry(request).await; - - if let Ok(response) = result.as_ref() { - self.metrics.record_auth_warren_entry_rename_success().await; - self.notifier - .auth_warren_entry_renamed(session_response.user(), response) - .await; - } else { - self.metrics.record_auth_warren_entry_rename_failure().await; - } - - result.map_err(AuthError::Custom) - } - - async fn create_warren_directory( - &self, - request: AuthRequest, - warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); - - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; + let request = request.into_value(); let user_warren = self .repository @@ -721,34 +679,31 @@ where return Err(AuthError::InsufficientPermissions); } - let result = warren_service.create_warren_directory(request).await; + let result = warren_service + .warren_mkdir(request) + .await + .map_err(AuthError::Custom); if let Ok(response) = result.as_ref() { - self.metrics - .record_auth_warren_directory_creation_success() - .await; + self.metrics.record_auth_warren_mkdir_success().await; self.notifier - .auth_warren_directory_created(session_response.user(), response) + .auth_warren_mkdir(session_response.user(), response) .await; } else { - self.metrics - .record_auth_warren_directory_creation_failure() - .await; + self.metrics.record_auth_warren_mkdir_failure().await; } - result.map_err(AuthError::Custom) + result } - async fn delete_warren_file( + async fn auth_warren_rm( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; + let request = request.into_value(); let user_warren = self .repository @@ -762,34 +717,31 @@ where return Err(AuthError::InsufficientPermissions); } - let result = warren_service.delete_warren_file(request).await; + let result = warren_service + .warren_rm(request) + .await + .map_err(AuthError::Custom); if let Ok(response) = result.as_ref() { - self.metrics - .record_auth_warren_file_deletion_success() - .await; + self.metrics.record_auth_warren_rm_success().await; self.notifier - .auth_warren_file_deleted(session_response.user(), response) + .auth_warren_rm(session_response.user(), response) .await; } else { - self.metrics - .record_auth_warren_file_deletion_failure() - .await; + self.metrics.record_auth_warren_rm_failure().await; } - result.map_err(AuthError::Custom) + result } - async fn delete_warren_directory( + async fn auth_warren_mv( &self, - request: AuthRequest, + request: AuthRequest, warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; + let request = request.into_value(); let user_warren = self .repository @@ -799,38 +751,35 @@ where )) .await?; - if !user_warren.can_delete_files() { + if !user_warren.can_modify_files() { return Err(AuthError::InsufficientPermissions); } - let result = warren_service.delete_warren_directory(request).await; + let result = warren_service + .warren_mv(request) + .await + .map_err(AuthError::Custom); if let Ok(response) = result.as_ref() { - self.metrics - .record_auth_warren_directory_deletion_success() - .await; + self.metrics.record_auth_warren_mv_success().await; self.notifier - .auth_warren_directory_deleted(session_response.user(), response) + .auth_warren_mv(session_response.user(), response) .await; } else { - self.metrics - .record_auth_warren_directory_deletion_failure() - .await; + self.metrics.record_auth_warren_mv_failure().await; } - result.map_err(AuthError::Custom) + result } - async fn upload_warren_files( + async fn auth_warren_save( &self, - request: AuthRequest, + request: AuthRequest>, warren_service: &WS, - ) -> Result> { - let (session, request) = request.unpack(); + ) -> Result> { + let session_response = self.fetch_auth_session((&request).into()).await?; - let session_response = self - .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) - .await?; + let request = request.into_value(); let user_warren = self .repository @@ -845,17 +794,59 @@ where return Err(AuthError::InsufficientPermissions); } - let result = warren_service.upload_warren_files(request).await; + let result = warren_service + .warren_save(request) + .await + .map_err(AuthError::Custom); if let Ok(response) = result.as_ref() { - self.metrics.record_auth_warren_files_upload_success().await; + self.metrics.record_auth_warren_save_success().await; self.notifier - .auth_warren_files_uploaded(session_response.user(), response) + .auth_warren_save(session_response.user(), response) .await; } else { - self.metrics.record_auth_warren_files_upload_failure().await; + self.metrics.record_auth_warren_save_failure().await; } - result.map_err(AuthError::Custom) + result + } + + async fn auth_warren_touch( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + 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_touch(request) + .await + .map_err(AuthError::Custom); + + if let Ok(response) = result.as_ref() { + self.metrics.record_auth_warren_save_success().await; + self.notifier + .auth_warren_touch(session_response.user(), response) + .await; + } else { + self.metrics.record_auth_warren_save_failure().await; + } + + result } } diff --git a/backend/src/lib/domain/warren/service/file_system.rs b/backend/src/lib/domain/warren/service/file_system.rs index 6927d14..eea4bcb 100644 --- a/backend/src/lib/domain/warren/service/file_system.rs +++ b/backend/src/lib/domain/warren/service/file_system.rs @@ -1,9 +1,8 @@ use crate::domain::warren::{ models::file::{ - CreateDirectoryError, CreateDirectoryRequest, CreateFileError, CreateFileRequest, - DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, DeleteFileRequest, - FetchFileError, FetchFileRequest, File, FilePath, FileStream, ListFilesError, - ListFilesRequest, RenameEntryError, RenameEntryRequest, + CatError, CatRequest, File, FileStream, LsError, LsRequest, MkdirError, MkdirRequest, + MvError, MvRequest, RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError, + TouchRequest, }, ports::{FileSystemMetrics, FileSystemNotifier, FileSystemRepository, FileSystemService}, }; @@ -41,103 +40,99 @@ where M: FileSystemMetrics, N: FileSystemNotifier, { - async fn list_files(&self, request: ListFilesRequest) -> Result, ListFilesError> { - let result = self.repository.list_files(request).await; + async fn ls(&self, request: LsRequest) -> Result, LsError> { + let result = self.repository.ls(request).await; if let Ok(files) = result.as_ref() { - self.metrics.record_list_files_success().await; - self.notifier.files_listed(files).await; + self.metrics.record_ls_success().await; + self.notifier.ls(files).await; } else { - self.metrics.record_list_files_failure().await; + self.metrics.record_ls_failure().await; } result } - async fn create_directory( - &self, - request: CreateDirectoryRequest, - ) -> Result { - let result = self.repository.create_directory(request).await; - - if let Ok(path) = result.as_ref() { - self.metrics.record_directory_creation_success().await; - self.notifier.directory_created(path).await; - } else { - self.metrics.record_directory_creation_failure().await; - } - - result - } - - async fn delete_directory( - &self, - request: DeleteDirectoryRequest, - ) -> Result { - let result = self.repository.delete_directory(request).await; - - if let Ok(path) = result.as_ref() { - self.metrics.record_directory_deletion_success().await; - self.notifier.directory_deleted(path).await; - } else { - self.metrics.record_directory_deletion_failure().await; - } - - result - } - - async fn create_file(&self, request: CreateFileRequest) -> Result { - let result = self.repository.create_file(request).await; - - if let Ok(path) = result.as_ref() { - self.metrics.record_file_creation_success().await; - self.notifier.file_created(path).await; - } else { - self.metrics.record_file_creation_failure().await; - } - - result - } - - async fn fetch_file(&self, request: FetchFileRequest) -> Result { + async fn cat(&self, request: CatRequest) -> Result { let path = request.path().clone(); - let result = self.repository.fetch_file(request).await; + let result = self.repository.cat(request).await; - if let Ok(_stream) = result.as_ref() { - self.metrics.record_file_fetch_success().await; - self.notifier.file_fetched(&path).await; + if result.is_ok() { + self.metrics.record_cat_success().await; + self.notifier.cat(&path).await; } else { - self.metrics.record_file_fetch_failure().await; + self.metrics.record_cat_failure().await; } result } - async fn delete_file(&self, request: DeleteFileRequest) -> Result { - let result = self.repository.delete_file(request).await; + async fn mkdir(&self, request: MkdirRequest) -> Result<(), MkdirError> { + let path = request.path().clone(); + let result = self.repository.mkdir(request).await; - if let Ok(path) = result.as_ref() { - self.metrics.record_file_deletion_success().await; - self.notifier.file_deleted(path).await; + if result.is_ok() { + self.metrics.record_mkdir_success().await; + self.notifier.mkdir(&path).await; } else { - self.metrics.record_file_deletion_failure().await; + self.metrics.record_mkdir_failure().await; } result } - async fn rename_entry( - &self, - request: RenameEntryRequest, - ) -> Result { + async fn rm(&self, request: RmRequest) -> Result<(), RmError> { + let path = request.path().clone(); + let result = self.repository.rm(request).await; + + if result.is_ok() { + self.metrics.record_rm_success().await; + self.notifier.rm(&path).await; + } else { + self.metrics.record_rm_failure().await; + } + + result + } + + async fn mv(&self, request: MvRequest) -> Result<(), MvError> { let old_path = request.path().clone(); - let result = self.repository.rename_entry(request).await; + let new_path = request.target_path().clone(); + let result = self.repository.mv(request).await; - if let Ok(path) = result.as_ref() { - self.metrics.record_entry_rename_success().await; - self.notifier.entry_renamed(&old_path.into(), path).await; + if result.is_ok() { + self.metrics.record_mv_success().await; + self.notifier.mv(&old_path, &new_path).await; } else { - self.metrics.record_entry_rename_failure().await; + self.metrics.record_mv_failure().await; + } + + result + } + + async fn save(&self, request: SaveRequest<'_>) -> Result { + let path = request.path().clone(); + let result = self.repository.save(request).await; + + if result.is_ok() { + self.metrics.record_save_success().await; + self.notifier.save(&path).await; + } else { + self.metrics.record_save_failure().await; + } + + result + } + + async fn touch(&self, request: TouchRequest) -> Result<(), TouchError> { + let path = request.path().clone(); + let result = self.repository.touch(request).await; + + if result.is_ok() { + self.metrics.record_touch_success().await; + self.notifier.touch(&path).await; + } else { + self.metrics.record_touch_failure().await; } result diff --git a/backend/src/lib/domain/warren/service/warren.rs b/backend/src/lib/domain/warren/service/warren.rs index 95d8491..bde4585 100644 --- a/backend/src/lib/domain/warren/service/warren.rs +++ b/backend/src/lib/domain/warren/service/warren.rs @@ -2,12 +2,12 @@ use crate::domain::warren::{ models::{ file::FileStream, warren::{ - CreateWarrenDirectoryResponse, CreateWarrenError, CreateWarrenRequest, - DeleteWarrenDirectoryResponse, DeleteWarrenError, DeleteWarrenFileResponse, - DeleteWarrenRequest, EditWarrenError, EditWarrenRequest, FetchWarrenFileError, - FetchWarrenFileRequest, FetchWarrensError, FetchWarrensRequest, - ListWarrenFilesResponse, ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, - RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesResponse, + CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest, + EditWarrenError, EditWarrenRequest, FetchWarrensError, FetchWarrensRequest, + ListWarrensError, ListWarrensRequest, WarrenCatError, WarrenCatRequest, + WarrenLsResponse, WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, + WarrenMvResponse, WarrenRmRequest, WarrenRmResponse, WarrenSaveResponse, + WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse, }, }, ports::FileSystemService, @@ -15,10 +15,8 @@ use crate::domain::warren::{ use super::{ super::models::warren::{ - CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError, - DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest, - FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest, - UploadWarrenFilesError, UploadWarrenFilesRequest, Warren, + FetchWarrenError, FetchWarrenRequest, Warren, WarrenLsError, WarrenLsRequest, + WarrenMkdirError, WarrenMkdirRequest, WarrenRmError, WarrenSaveError, WarrenSaveRequest, }, super::ports::{WarrenMetrics, WarrenNotifier, WarrenRepository, WarrenService}, }; @@ -149,182 +147,171 @@ where result } - async fn fetch_warren_file( - &self, - request: FetchWarrenFileRequest, - ) -> Result { + async fn warren_cat(&self, request: WarrenCatRequest) -> Result { let warren = self.repository.fetch_warren((&request).into()).await?; - let path = request.path().clone(); + let path = request.base().path().clone(); + let cat_request = request.build_fs_request(&warren); - let result = self - .fs_service - .fetch_file(request.to_fs_request(&warren)) - .await - .map_err(Into::into); + let result = self.fs_service.cat(cat_request).await.map_err(Into::into); - if let Ok(_stream) = result.as_ref() { - self.metrics.record_warren_fetch_file_success().await; - self.notifier.warren_file_fetched(&warren, &path).await; + if result.is_ok() { + self.metrics.record_warren_cat_success().await; + self.notifier.warren_cat(&warren, &path).await; } else { - self.metrics.record_warren_fetch_file_failure().await; + self.metrics.record_warren_cat_failure().await; } result } - async fn list_warren_files( - &self, - request: ListWarrenFilesRequest, - ) -> Result { + async fn warren_ls(&self, request: WarrenLsRequest) -> Result { let warren = self.repository.fetch_warren(request.clone().into()).await?; + let path = request.base().path().clone(); + let ls_request = request.build_fs_request(&warren); + let result = self .fs_service - .list_files(request.to_fs_request(&warren)) + .ls(ls_request) .await - .map(|files| ListWarrenFilesResponse::new(warren, files)); + .map(|files| WarrenLsResponse::new(warren, path, files)) + .map_err(Into::into); if let Ok(response) = result.as_ref() { - self.metrics.record_list_warren_files_success().await; - self.notifier.warren_files_listed(response).await; + self.metrics.record_warren_ls_success().await; + self.notifier.warren_ls(response).await; } else { - self.metrics.record_list_warren_files_failure().await; + self.metrics.record_warren_ls_failure().await; } - result.map_err(Into::into) + result } - async fn create_warren_directory( + async fn warren_mkdir( &self, - request: CreateWarrenDirectoryRequest, - ) -> Result { + request: WarrenMkdirRequest, + ) -> Result { let warren = self.repository.fetch_warren(request.clone().into()).await?; - let result = self - .fs_service - .create_directory(request.to_fs_request(&warren)) - .await - .map(|path| CreateWarrenDirectoryResponse::new(warren, path)); - - if let Ok(response) = result.as_ref() { - self.metrics - .record_warren_directory_creation_success() - .await; - self.notifier.warren_directory_created(response).await; - } else { - self.metrics - .record_warren_directory_creation_failure() - .await; - } - - result.map_err(Into::into) - } - - async fn delete_warren_directory( - &self, - request: DeleteWarrenDirectoryRequest, - ) -> Result { - let warren = self.repository.fetch_warren((&request).into()).await?; + let path = request.base().path().clone(); + let mkdir_request = request.build_fs_request(&warren); let result = self .fs_service - .delete_directory(request.to_fs_request(&warren)) + .mkdir(mkdir_request) .await - .map(|path| DeleteWarrenDirectoryResponse::new(warren, path)); + .map(|_| WarrenMkdirResponse::new(warren, path)) + .map_err(Into::into); if let Ok(response) = result.as_ref() { - self.metrics - .record_warren_directory_deletion_success() - .await; - self.notifier.warren_directory_deleted(response).await; + self.metrics.record_warren_mkdir_success().await; + self.notifier.warren_mkdir(response).await; } else { - self.metrics - .record_warren_directory_deletion_failure() - .await; + self.metrics.record_warren_mkdir_failure().await; } - result.map_err(Into::into) + result } - // TODO: Improve this - async fn upload_warren_files( + async fn warren_save<'s>( &self, - request: UploadWarrenFilesRequest, - ) -> Result { + request: WarrenSaveRequest<'s>, + ) -> Result { let warren = self.repository.fetch_warren((&request).into()).await?; - let fs_requests = request.to_fs_requests(&warren); + let path = request.base().path().clone(); + let save_request = request.build_fs_request(&warren); - let mut paths = Vec::with_capacity(fs_requests.len()); + let result = self + .fs_service + .save(save_request) + .await + .map(|base| WarrenSaveResponse::new(warren, path, base)) + .map_err(Into::into); - for (i, req) in fs_requests.into_iter().enumerate() { - let result = self.fs_service.create_file(req).await; - - let Ok(file_path) = result else { - self.metrics.record_warren_file_upload_failure().await; - self.metrics.record_warren_files_upload_failure().await; - return Err(UploadWarrenFilesError::Partial { fail_index: i }); - }; - - self.metrics.record_warren_file_upload_success().await; + if let Ok(result) = result.as_ref() { + self.metrics.record_warren_save_success().await; self.notifier - .warren_file_uploaded(&warren, &file_path) + .warren_save(result.warren(), result.path()) .await; - - paths.push(file_path); + } else { + self.metrics.record_warren_save_failure().await; } - let response = UploadWarrenFilesResponse::new(warren, paths); - - self.metrics.record_warren_files_upload_success().await; - self.notifier.warren_files_uploaded(&response).await; - - Ok(response) + result } - async fn delete_warren_file( - &self, - request: DeleteWarrenFileRequest, - ) -> Result { + async fn warren_rm(&self, request: WarrenRmRequest) -> Result { let warren = self.repository.fetch_warren((&request).into()).await?; + let path = request.base().path().clone(); + let rm_request = request.build_fs_request(&warren); + let result = self .fs_service - .delete_file(request.to_fs_request(&warren)) + .rm(rm_request) .await - .map(|path| DeleteWarrenFileResponse::new(warren, path)); + .map(|_| WarrenRmResponse::new(warren, path)) + .map_err(Into::into); if let Ok(response) = result.as_ref() { - self.metrics.record_warren_file_deletion_success().await; - self.notifier.warren_file_deleted(response).await; + self.metrics.record_warren_rm_success().await; + self.notifier.warren_rm(response).await; } else { - self.metrics.record_warren_file_deletion_failure().await; + self.metrics.record_warren_rm_failure().await; } - result.map_err(Into::into) + result } - async fn rename_warren_entry( - &self, - request: RenameWarrenEntryRequest, - ) -> Result { + async fn warren_mv(&self, request: WarrenMvRequest) -> Result { let warren = self.repository.fetch_warren((&request).into()).await?; - let old_path = request.path().clone(); + let old_path = request.base().path().clone(); + let new_path = request.base().target_path().clone(); + let mv_request = request.build_fs_request(&warren); let result = self .fs_service - .rename_entry(request.to_fs_request(&warren)) + .mv(mv_request) .await - .map(|new_path| RenameWarrenEntryResponse::new(warren, old_path, new_path)); + .map(|_| WarrenMvResponse::new(warren, old_path, new_path)) + .map_err(Into::into); if let Ok(response) = result.as_ref() { - self.metrics.record_warren_entry_rename_success().await; - self.notifier.warren_entry_renamed(response).await; + self.metrics.record_warren_mv_success().await; + self.notifier.warren_mv(response).await; } else { - self.metrics.record_warren_entry_rename_failure().await; + self.metrics.record_warren_mv_failure().await; } - result.map_err(Into::into) + result + } + + async fn warren_touch( + &self, + request: WarrenTouchRequest, + ) -> Result { + let warren = self.repository.fetch_warren((&request).into()).await?; + + let path = request.base().path().clone(); + let touch_request = request.build_fs_request(&warren); + let result = self + .fs_service + .touch(touch_request) + .await + .map(|_| WarrenTouchResponse::new(warren, path)) + .map_err(Into::into); + + if let Ok(response) = result.as_ref() { + self.metrics.record_warren_touch_success().await; + self.notifier + .warren_touch(response.warren(), response.path()) + .await; + } else { + self.metrics.record_warren_touch_failure().await; + } + + result } } diff --git a/backend/src/lib/inbound/http/errors.rs b/backend/src/lib/inbound/http/errors.rs index 4626ad9..ba1a04a 100644 --- a/backend/src/lib/inbound/http/errors.rs +++ b/backend/src/lib/inbound/http/errors.rs @@ -1,84 +1,55 @@ use crate::{ domain::warren::models::{ auth_session::{AuthError, requests::FetchAuthSessionError}, - file::{CreateDirectoryError, DeleteDirectoryError, DeleteFileError, ListFilesError}, + file::{LsError, MkdirError, RmError}, user::{CreateUserError, LoginUserError, RegisterUserError, VerifyUserPasswordError}, user_warren::requests::FetchUserWarrenError, warren::{ - CreateWarrenDirectoryError, DeleteWarrenDirectoryError, DeleteWarrenFileError, - FetchWarrenError, FetchWarrensError, ListWarrenFilesError, RenameWarrenEntryError, - UploadWarrenFilesError, + FetchWarrenError, FetchWarrensError, WarrenLsError, WarrenMkdirError, WarrenMvError, + WarrenRmError, WarrenSaveError, }, }, inbound::http::responses::ApiError, }; -impl From for ApiError { - fn from(value: CreateDirectoryError) -> Self { +impl From for ApiError { + fn from(value: MkdirError) -> 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()) - } + MkdirError::Exists => match value { + MkdirError::Exists => Self::BadRequest("The directory already exists".to_string()), + MkdirError::Unknown(error) => Self::InternalServerError(error.to_string()), }, - CreateDirectoryError::Unknown(error) => Self::InternalServerError(error.to_string()), + MkdirError::Unknown(error) => Self::InternalServerError(error.to_string()), } } } -impl From for ApiError { - fn from(value: CreateWarrenDirectoryError) -> Self { +impl From for ApiError { + fn from(value: WarrenMkdirError) -> Self { match value { - CreateWarrenDirectoryError::FileSystem(fs) => fs.into(), - CreateWarrenDirectoryError::FetchWarren(err) => err.into(), - CreateWarrenDirectoryError::Unknown(error) => { - Self::InternalServerError(error.to_string()) - } + WarrenMkdirError::FileSystem(fs) => fs.into(), + WarrenMkdirError::FetchWarren(err) => err.into(), + WarrenMkdirError::Unknown(error) => Self::InternalServerError(error.to_string()), } } } -impl From for ApiError { - fn from(value: DeleteDirectoryError) -> Self { +impl From for ApiError { + fn from(value: RmError) -> 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()), + RmError::NotFound => Self::NotFound("The directory does not exist".to_string()), + RmError::NotEmpty => Self::BadRequest("The directory is not empty".to_string()), + RmError::Unknown(e) => Self::InternalServerError(e.to_string()), } } } -impl From for ApiError { - fn from(value: DeleteWarrenDirectoryError) -> Self { +impl From for ApiError { + fn from(value: WarrenRmError) -> Self { match value { - DeleteWarrenDirectoryError::FileSystem(fs) => fs.into(), - DeleteWarrenDirectoryError::FetchWarren(err) => err.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::FetchWarren(err) => err.into(), - DeleteWarrenFileError::Unknown(error) => Self::InternalServerError(error.to_string()), + WarrenRmError::FileSystem(fs) => fs.into(), + WarrenRmError::FetchWarren(err) => err.into(), + WarrenRmError::Unknown(error) => Self::InternalServerError(error.to_string()), } } } @@ -94,23 +65,23 @@ impl From for ApiError { } } -impl From for ApiError { - fn from(value: ListFilesError) -> Self { +impl From for ApiError { + fn from(value: LsError) -> Self { match value { - ListFilesError::NotFound(_) => { + LsError::NotFound(_) => { Self::NotFound("Could not find the requested directory".to_string()) } - ListFilesError::Unknown(e) => Self::InternalServerError(e.to_string()), + LsError::Unknown(e) => Self::InternalServerError(e.to_string()), } } } -impl From for ApiError { - fn from(value: ListWarrenFilesError) -> Self { +impl From for ApiError { + fn from(value: WarrenLsError) -> Self { match value { - ListWarrenFilesError::FileSystem(fs_error) => fs_error.into(), - ListWarrenFilesError::FetchWarren(err) => err.into(), - ListWarrenFilesError::Unknown(error) => Self::InternalServerError(error.to_string()), + WarrenLsError::FileSystem(fs_error) => fs_error.into(), + WarrenLsError::FetchWarren(err) => err.into(), + WarrenLsError::Unknown(error) => Self::InternalServerError(error.to_string()), } } } @@ -121,14 +92,14 @@ impl From for ApiError { } } -impl From for ApiError { - fn from(value: RenameWarrenEntryError) -> Self { +impl From for ApiError { + fn from(value: WarrenMvError) -> Self { Self::InternalServerError(value.to_string()) } } -impl From for ApiError { - fn from(value: UploadWarrenFilesError) -> Self { +impl From for ApiError { + fn from(value: WarrenSaveError) -> Self { Self::InternalServerError(value.to_string()) } } diff --git a/backend/src/lib/inbound/http/handlers/warrens/mod.rs b/backend/src/lib/inbound/http/handlers/warrens/mod.rs index 27a8c11..4965d52 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/mod.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/mod.rs @@ -1,17 +1,16 @@ -mod create_warren_directory; -mod delete_warren_directory; -mod delete_warren_file; -mod fetch_file; mod fetch_warren; -mod list_warren_files; mod list_warrens; -mod rename_warren_entry; mod upload_warren_files; +mod warren_cat; +mod warren_ls; +mod warren_mkdir; +mod warren_move; +mod warren_rm; use axum::{ Router, extract::DefaultBodyLimit, - routing::{delete, get, patch, post}, + routing::{get, post}, }; use crate::{ @@ -20,30 +19,28 @@ use crate::{ }; use fetch_warren::fetch_warren; -use list_warren_files::list_warren_files; use list_warrens::list_warrens; +use warren_ls::list_warren_files; -use create_warren_directory::create_warren_directory; -use delete_warren_directory::delete_warren_directory; +use warren_mkdir::create_warren_directory; +use warren_rm::warren_rm; -use delete_warren_file::delete_warren_file; -use fetch_file::fetch_file; -use rename_warren_entry::rename_warren_entry; -use upload_warren_files::upload_warren_files; +use upload_warren_files::warren_save; +use warren_cat::fetch_file; +use warren_move::warren_move; pub fn routes() -> Router> { Router::new() .route("/", get(list_warrens)) .route("/", post(fetch_warren)) - .route("/files/fetch", get(fetch_file)) - .route("/files", post(list_warren_files)) - .route("/files/directory", post(create_warren_directory)) - .route("/files/directory", delete(delete_warren_directory)) + .route("/files/cat", get(fetch_file)) + .route("/files/ls", post(list_warren_files)) + .route("/files/mkdir", post(create_warren_directory)) + .route("/files/rm", post(warren_rm)) .route( - "/files/upload", + "/files/save", // 1073741824 bytes = 1GB - post(upload_warren_files).route_layer(DefaultBodyLimit::max(1073741824)), + post(warren_save).route_layer(DefaultBodyLimit::max(1073741824)), ) - .route("/files/file", delete(delete_warren_file)) - .route("/files/rename", patch(rename_warren_entry)) + .route("/files/mv", post(warren_move)) } 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 deleted file mode 100644 index 328d2eb..0000000 --- a/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs +++ /dev/null @@ -1,96 +0,0 @@ -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, FileName, FileNameError, FilePath, - FilePathError, - }, - warren::RenameWarrenEntryRequest, - }, - ports::{AuthService, WarrenService}, - }, - inbound::http::{ - AppState, - handlers::extractors::SessionIdHeader, - 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 { - 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 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()) - } - }, - } - } -} - -pub async fn rename_warren_entry( - State(state): State>, - SessionIdHeader(session): SessionIdHeader, - Json(request): Json, -) -> Result, ApiError> { - let domain_request = AuthRequest::new(session, request.try_into_domain()?); - - state - .auth_service - .rename_warren_entry(domain_request, state.warren_service.as_ref()) - .await - .map(|_| ApiSuccess::new(StatusCode::OK, ())) - .map_err(ApiError::from) -} 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 d3e60ae..37aad5c 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 @@ -1,14 +1,21 @@ -use axum::{body::Bytes, extract::State, http::StatusCode}; -use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart}; +use std::str::FromStr as _; + +use anyhow::{anyhow, bail}; +use axum::{extract::State, http::StatusCode}; +use axum_extra::extract::Multipart; +use bytes::Bytes; +use futures_util::{StreamExt as _, TryStreamExt}; use thiserror::Error; -use uuid::Uuid; use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError}, - warren::{UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesRequest}, + file::{ + AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath, + FilePathError, SaveRequest, + }, + warren::{UploadFile, UploadFileStream, WarrenSaveRequest}, }, ports::{AuthService, WarrenService}, }, @@ -19,70 +26,94 @@ use crate::{ }, }; -#[derive(Debug, TryFromMultipart)] -#[try_from_multipart(rename_all = "camelCase")] -pub struct UploadWarrenFilesHttpRequestBody { - warren_id: Uuid, - path: String, - files: Vec>, -} - -#[derive(Debug, Clone, Error)] -enum ParseUploadWarrenFilesHttpRequestError { +#[derive(Debug, Error)] +enum ParseWarrenSaveHttpRequestError { + #[error(transparent)] + Multipart(#[from] axum_extra::extract::multipart::MultipartError), + #[error("The form was submitted with a missing or invalid field")] + Field, #[error(transparent)] FileName(#[from] FileNameError), #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] - FileList(#[from] UploadFileListError), - #[error(transparent)] AbsoluteFilePath(#[from] AbsoluteFilePathError), } -impl UploadWarrenFilesHttpRequestBody { - fn try_into_domain( - self, - ) -> Result { - let path = FilePath::new(&self.path)?; - - let mut files = Vec::with_capacity(self.files.len()); - - for file in self.files { - let raw_file_name = file.metadata.file_name.ok_or(FileNameError::Empty)?; - let file_name = FileName::new(&raw_file_name)?; - let data = file.contents.to_vec().into_boxed_slice(); - files.push(UploadFile::new(file_name, data)); +async fn try_into_domain<'s>( + mut multipart: Multipart, +) -> Result, ParseWarrenSaveHttpRequestError> { + let warren_id = { + let field = multipart + .next_field() + .await? + .ok_or(ParseWarrenSaveHttpRequestError::Field)?; + let name = field.name().ok_or(ParseWarrenSaveHttpRequestError::Field)?; + if name != "warrenId" { + return Err(ParseWarrenSaveHttpRequestError::Field); } + uuid::Uuid::from_str(&field.text().await?) + .map_err(|_| ParseWarrenSaveHttpRequestError::Field)? + }; + let path: AbsoluteFilePath = { + let field = multipart + .next_field() + .await? + .ok_or(ParseWarrenSaveHttpRequestError::Field)?; + let name = field.name().ok_or(ParseWarrenSaveHttpRequestError::Field)?; + if name != "path" { + return Err(ParseWarrenSaveHttpRequestError::Field); + } + FilePath::new(&field.text().await?)?.try_into()? + }; - let files = UploadFileList::new(files)?; + let stream = Box::pin(multipart.into_stream().map(|r| { + let field = match r { + Ok(field) => field, + Err(e) => return Err(anyhow!("Failed to get field: {e:?}")), + }; - Ok(UploadWarrenFilesRequest::new( - self.warren_id, - path.try_into()?, - files, - )) - } + if field.name().is_none_or(|name| name != "files") { + bail!("Invalid field name"); + }; + + let Some(Ok(file_name)) = field.file_name().map(FileName::new) else { + bail!("Invalid file name"); + }; + + let file = UploadFile::new( + file_name, + field + .into_stream() + .map_err(tokio::io::Error::other) + .map_ok(|bytes| Bytes::from(bytes)), + ); + + Ok(file) + })); + + let stream = UploadFileStream::new(stream); + + Ok(WarrenSaveRequest::new( + warren_id, + SaveRequest::new(path, stream), + )) } -impl From for ApiError { - fn from(value: ParseUploadWarrenFilesHttpRequestError) -> Self { +impl From for ApiError { + fn from(value: ParseWarrenSaveHttpRequestError) -> Self { match value { - ParseUploadWarrenFilesHttpRequestError::FilePath(err) => match err { + ParseWarrenSaveHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseUploadWarrenFilesHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenSaveHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } }, - ParseUploadWarrenFilesHttpRequestError::FileList(err) => match err { - UploadFileListError::Empty => { - ApiError::BadRequest("There has to be at least 1 file present".to_string()) - } - }, - ParseUploadWarrenFilesHttpRequestError::FileName(err) => match err { + ParseWarrenSaveHttpRequestError::FileName(err) => match err { FileNameError::Slash => { ApiError::BadRequest("A file's name contained an invalid character".to_string()) } @@ -90,20 +121,26 @@ impl From for ApiError { ApiError::BadRequest("File names must not be empty".to_string()) } }, + ParseWarrenSaveHttpRequestError::Field => ApiError::BadRequest( + "The form was submitted with a missing or invalid field".to_string(), + ), + ParseWarrenSaveHttpRequestError::Multipart(e) => { + ApiError::InternalServerError(e.to_string()) + } } } } -pub async fn upload_warren_files( +pub async fn warren_save( State(state): State>, SessionIdHeader(session): SessionIdHeader, - TypedMultipart(multipart): TypedMultipart, + multipart: Multipart, ) -> Result, ApiError> { - let domain_request = AuthRequest::new(session, multipart.try_into_domain()?); + let domain_request = AuthRequest::new(session, try_into_domain(multipart).await?); state .auth_service - .upload_warren_files(domain_request, state.warren_service.as_ref()) + .auth_warren_save(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/fetch_file.rs b/backend/src/lib/inbound/http/handlers/warrens/warren_cat.rs similarity index 67% rename from backend/src/lib/inbound/http/handlers/warrens/fetch_file.rs rename to backend/src/lib/inbound/http/handlers/warrens/warren_cat.rs index 54c3ccf..5d36937 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/fetch_file.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/warren_cat.rs @@ -12,8 +12,8 @@ use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, FilePath, FilePathError, FileStream}, - warren::FetchWarrenFileRequest, + file::{AbsoluteFilePathError, CatRequest, FilePath, FilePathError, FileStream}, + warren::WarrenCatRequest, }, ports::{AuthService, WarrenService}, }, @@ -37,28 +37,28 @@ impl From> for FetchWarrenFileHttpResponseBody { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FetchWarrenFileHttpRequestBody { +pub(super) struct WarrenCatHttpRequestBody { warren_id: Uuid, path: String, } #[derive(Debug, Clone, Error)] -pub enum ParseFetchWarrenFileHttpRequestError { +pub enum ParseWarrenCatHttpRequestError { #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] AbsoluteFilePath(#[from] AbsoluteFilePathError), } -impl From for ApiError { - fn from(value: ParseFetchWarrenFileHttpRequestError) -> Self { +impl From for ApiError { + fn from(value: ParseWarrenCatHttpRequestError) -> Self { match value { - ParseFetchWarrenFileHttpRequestError::FilePath(err) => match err { + ParseWarrenCatHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseFetchWarrenFileHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenCatHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } @@ -67,13 +67,11 @@ impl From for ApiError { } } -impl FetchWarrenFileHttpRequestBody { - fn try_into_domain( - self, - ) -> Result { +impl WarrenCatHttpRequestBody { + fn try_into_domain(self) -> Result { let path = FilePath::new(&self.path)?.try_into()?; - Ok(FetchWarrenFileRequest::new(self.warren_id, path)) + Ok(WarrenCatRequest::new(self.warren_id, CatRequest::new(path))) } } @@ -86,16 +84,13 @@ impl From for Body { pub async fn fetch_file( State(state): State>, SessionIdHeader(session): SessionIdHeader, - Query(request): Query, + Query(request): Query, ) -> Result { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state .auth_service - .fetch_warren_file( - AuthRequest::new(session, domain_request), - state.warren_service.as_ref(), - ) + .auth_warren_cat(domain_request, state.warren_service.as_ref()) .await .map(|contents| contents.into()) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs b/backend/src/lib/inbound/http/handlers/warrens/warren_ls.rs similarity index 72% rename from backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs rename to backend/src/lib/inbound/http/handlers/warrens/warren_ls.rs index 7779516..c7fec2a 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/warren_ls.rs @@ -7,8 +7,11 @@ use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType}, - warren::ListWarrenFilesRequest, + file::{ + AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType, + LsRequest, + }, + warren::WarrenLsRequest, }, ports::{AuthService, WarrenService}, }, @@ -20,7 +23,7 @@ use crate::{ }; #[derive(Debug, Clone, Error)] -pub enum ParseListWarrenHttpRequestError { +pub enum ParseWarrenLsHttpRequestError { #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] @@ -29,31 +32,28 @@ pub enum ParseListWarrenHttpRequestError { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ListWarrenFilesHttpRequestBody { +pub(super) struct WarrenLsHttpRequestBody { warren_id: Uuid, path: String, } -impl ListWarrenFilesHttpRequestBody { - fn try_into_domain(self) -> Result { - let path = FilePath::new(&self.path)?; +impl WarrenLsHttpRequestBody { + fn try_into_domain(self) -> Result { + let path = FilePath::new(&self.path)?.try_into()?; - Ok(ListWarrenFilesRequest::new( - self.warren_id, - path.try_into()?, - )) + Ok(WarrenLsRequest::new(self.warren_id, LsRequest::new(path))) } } -impl From for ApiError { - fn from(value: ParseListWarrenHttpRequestError) -> Self { +impl From for ApiError { + fn from(value: ParseWarrenLsHttpRequestError) -> Self { match value { - ParseListWarrenHttpRequestError::FilePath(err) => match err { + ParseWarrenLsHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseListWarrenHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenLsHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } @@ -99,13 +99,13 @@ impl From<&Vec> for ListWarrenFilesResponseData { pub async fn list_warren_files( State(state): State>, SessionIdHeader(session): SessionIdHeader, - Json(request): Json, + Json(request): Json, ) -> Result, ApiError> { let domain_request = AuthRequest::new(session, request.try_into_domain()?); state .auth_service - .list_warren_files(domain_request, state.warren_service.as_ref()) + .auth_warren_ls(domain_request, state.warren_service.as_ref()) .await .map(|response| ApiSuccess::new(StatusCode::OK, response.files().into())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/warren_mkdir.rs similarity index 65% rename from backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs rename to backend/src/lib/inbound/http/handlers/warrens/warren_mkdir.rs index 375d6fc..f375aa5 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/warren_mkdir.rs @@ -7,8 +7,8 @@ use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::CreateWarrenDirectoryRequest, + file::{AbsoluteFilePathError, FilePath, FilePathError, MkdirRequest}, + warren::WarrenMkdirRequest, }, ports::{AuthService, WarrenService}, }, @@ -21,28 +21,28 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CreateWarrenDirectoryHttpRequestBody { +pub(super) struct WarrenMkdirHttpRequestBody { warren_id: Uuid, path: String, } #[derive(Debug, Clone, Error)] -pub enum ParseCreateWarrenDirectoryHttpRequestError { +pub(super) enum ParseWarrenMkdirHttpRequestError { #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] AbsoluteFilePath(#[from] AbsoluteFilePathError), } -impl From for ApiError { - fn from(value: ParseCreateWarrenDirectoryHttpRequestError) -> Self { +impl From for ApiError { + fn from(value: ParseWarrenMkdirHttpRequestError) -> Self { match value { - ParseCreateWarrenDirectoryHttpRequestError::FilePath(err) => match err { + ParseWarrenMkdirHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseCreateWarrenDirectoryHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenMkdirHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } @@ -51,15 +51,13 @@ impl From for ApiError { } } -impl CreateWarrenDirectoryHttpRequestBody { - fn try_into_domain( - self, - ) -> Result { +impl WarrenMkdirHttpRequestBody { + fn try_into_domain(self) -> Result { let path = FilePath::new(&self.path)?; - Ok(CreateWarrenDirectoryRequest::new( + Ok(WarrenMkdirRequest::new( self.warren_id, - path.try_into()?, + MkdirRequest::new(path.try_into()?), )) } } @@ -67,13 +65,13 @@ impl CreateWarrenDirectoryHttpRequestBody { pub async fn create_warren_directory( State(state): State>, SessionIdHeader(session): SessionIdHeader, - Json(request): Json, + Json(request): Json, ) -> Result, ApiError> { let domain_request = AuthRequest::new(session, request.try_into_domain()?); state .auth_service - .create_warren_directory(domain_request, state.warren_service.as_ref()) + .auth_warren_mkdir(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/warren_move.rs similarity index 55% rename from backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs rename to backend/src/lib/inbound/http/handlers/warrens/warren_move.rs index 965bdd6..8c2313b 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/warren_move.rs @@ -7,8 +7,8 @@ use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::DeleteWarrenDirectoryRequest, + file::{AbsoluteFilePath, AbsoluteFilePathError, FilePath, FilePathError, MvRequest}, + warren::WarrenMvRequest, }, ports::{AuthService, WarrenService}, }, @@ -21,29 +21,41 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DeleteWarrenDirectoryHttpRequestBody { +pub struct RenameWarrenEntryHttpRequestBody { warren_id: Uuid, path: String, - force: bool, + target_path: String, } #[derive(Debug, Clone, Error)] -pub enum ParseDeleteWarrenDirectoryHttpRequestError { +pub enum ParseWarrenMvHttpRequestError { #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] AbsoluteFilePath(#[from] AbsoluteFilePathError), } -impl From for ApiError { - fn from(value: ParseDeleteWarrenDirectoryHttpRequestError) -> Self { +impl RenameWarrenEntryHttpRequestBody { + fn try_into_domain(self) -> Result { + let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?; + let target_path: AbsoluteFilePath = FilePath::new(&self.target_path)?.try_into()?; + + Ok(WarrenMvRequest::new( + self.warren_id, + MvRequest::new(path, target_path), + )) + } +} + +impl From for ApiError { + fn from(value: ParseWarrenMvHttpRequestError) -> Self { match value { - ParseDeleteWarrenDirectoryHttpRequestError::FilePath(err) => match err { + ParseWarrenMvHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseDeleteWarrenDirectoryHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenMvHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } @@ -52,31 +64,17 @@ impl From for ApiError { } } -impl DeleteWarrenDirectoryHttpRequestBody { - fn try_into_domain( - self, - ) -> Result { - let path = FilePath::new(&self.path)?; - - Ok(DeleteWarrenDirectoryRequest::new( - self.warren_id, - path.try_into()?, - self.force, - )) - } -} - -pub async fn delete_warren_directory( +pub async fn warren_move( State(state): State>, SessionIdHeader(session): SessionIdHeader, - Json(request): Json, + Json(request): Json, ) -> Result, ApiError> { let domain_request = AuthRequest::new(session, request.try_into_domain()?); state .auth_service - .delete_warren_directory(domain_request, state.warren_service.as_ref()) + .auth_warren_mv(domain_request, state.warren_service.as_ref()) .await - .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) + .map(|_| ApiSuccess::new(StatusCode::OK, ())) .map_err(ApiError::from) } diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs b/backend/src/lib/inbound/http/handlers/warrens/warren_rm.rs similarity index 65% rename from backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs rename to backend/src/lib/inbound/http/handlers/warrens/warren_rm.rs index c232019..32f4039 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/warren_rm.rs @@ -7,8 +7,8 @@ use crate::{ domain::warren::{ models::{ auth_session::AuthRequest, - file::{AbsoluteFilePathError, FilePath, FilePathError}, - warren::DeleteWarrenFileRequest, + file::{AbsoluteFilePathError, FilePath, FilePathError, RmRequest}, + warren::WarrenRmRequest, }, ports::{AuthService, WarrenService}, }, @@ -21,28 +21,29 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DeleteWarrenFileHttpRequestBody { +pub(super) struct WarrenRmHttpRequestBody { warren_id: Uuid, path: String, + force: bool, } #[derive(Debug, Clone, Error)] -pub enum ParseDeleteWarrenFileHttpRequestError { +pub(super) enum ParseWarrenRmHttpRequestError { #[error(transparent)] FilePath(#[from] FilePathError), #[error(transparent)] AbsoluteFilePath(#[from] AbsoluteFilePathError), } -impl From for ApiError { - fn from(value: ParseDeleteWarrenFileHttpRequestError) -> Self { +impl From for ApiError { + fn from(value: ParseWarrenRmHttpRequestError) -> Self { match value { - ParseDeleteWarrenFileHttpRequestError::FilePath(err) => match err { + ParseWarrenRmHttpRequestError::FilePath(err) => match err { FilePathError::InvalidPath => { ApiError::BadRequest("The file path must be valid".to_string()) } }, - ParseDeleteWarrenFileHttpRequestError::AbsoluteFilePath(err) => match err { + ParseWarrenRmHttpRequestError::AbsoluteFilePath(err) => match err { AbsoluteFilePathError::NotAbsolute => { ApiError::BadRequest("The file path must be absolute".to_string()) } @@ -51,29 +52,27 @@ impl From for ApiError { } } -impl DeleteWarrenFileHttpRequestBody { - fn try_into_domain( - self, - ) -> Result { +impl WarrenRmHttpRequestBody { + fn try_into_domain(self) -> Result { let path = FilePath::new(&self.path)?; - Ok(DeleteWarrenFileRequest::new( + Ok(WarrenRmRequest::new( self.warren_id, - path.try_into()?, + RmRequest::new(path.try_into()?, self.force), )) } } -pub async fn delete_warren_file( +pub async fn warren_rm( State(state): State>, SessionIdHeader(session): SessionIdHeader, - Json(request): Json, + Json(request): Json, ) -> Result, ApiError> { let domain_request = AuthRequest::new(session, request.try_into_domain()?); state .auth_service - .delete_warren_file(domain_request, state.warren_service.as_ref()) + .auth_warren_rm(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/outbound/file_system.rs b/backend/src/lib/outbound/file_system.rs index b1f6a9f..f8d35ca 100644 --- a/backend/src/lib/outbound/file_system.rs +++ b/backend/src/lib/outbound/file_system.rs @@ -1,6 +1,7 @@ use std::os::unix::fs::MetadataExt; use anyhow::{Context, anyhow, bail}; +use futures_util::TryStreamExt; use rustix::fs::statx; use tokio::{ fs, @@ -11,12 +12,14 @@ use tokio_util::io::ReaderStream; use crate::{ config::Config, domain::warren::{ - models::file::{ - AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError, - CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, - DeleteFileRequest, FetchFileError, FetchFileRequest, File, FileMimeType, FileName, - FilePath, FileStream, FileType, ListFilesError, ListFilesRequest, RenameEntryError, - RenameEntryRequest, + models::{ + file::{ + AbsoluteFilePath, CatError, CatRequest, File, FileMimeType, FileName, FilePath, + FileStream, FileType, LsError, LsRequest, MkdirError, MkdirRequest, MvError, + MvRequest, RelativeFilePath, RmError, RmRequest, SaveError, SaveRequest, + SaveResponse, TouchError, TouchRequest, + }, + warren::UploadFileStream, }, ports::FileSystemRepository, }, @@ -129,49 +132,65 @@ 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) -> io::Result { + async fn mkdir(&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) + fs::create_dir(&file_path).await } - /// Actually removes a directory from the underlying file system + /// Actually removes a file or directory from the underlying file system /// /// * `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) -> io::Result { + async fn rm(&self, path: &AbsoluteFilePath, force: bool) -> io::Result<()> { let file_path = self.get_target_path(path); - if force { - fs::remove_dir_all(&file_path).await?; - } else { - fs::remove_dir(&file_path).await?; + if fs::metadata(&file_path).await?.is_file() { + return fs::remove_file(&file_path).await; } - Ok(file_path) + if force { + fs::remove_dir_all(&file_path).await + } else { + fs::remove_dir(&file_path).await + } } - async fn write_file(&self, path: &AbsoluteFilePath, data: &[u8]) -> anyhow::Result { - let path = self.get_target_path(path); + async fn save( + &self, + path: &AbsoluteFilePath, + stream: &mut UploadFileStream<'_>, + ) -> anyhow::Result> { + let base_path = self.get_target_path(path); - let mut file = fs::OpenOptions::new() - .write(true) - .create(true) - .open(&path) - .await?; + let paths = Vec::new(); - file.write_all(data).await?; + while let Ok(Some(mut upload_file)) = stream.try_next().await { + // TODO: Refactor this result question mark chain thing + let file_name_as_path: RelativeFilePath = + FilePath::new(upload_file.file_name().as_str())?.try_into()?; + let file_path = base_path.join(&file_name_as_path); - Ok(path) + let mut file = fs::OpenOptions::new() + .write(true) + .create(true) + .open(&file_path) + .await?; + + while let Ok(Some(chunk)) = upload_file.try_next().await { + tracing::info!("Writing chunk (len: {}) to {file_path}", chunk.len()); + file.write(&chunk).await?; + } + } + + Ok(paths) } - async fn fetch_file(&self, path: &AbsoluteFilePath) -> anyhow::Result { + async fn cat(&self, path: &AbsoluteFilePath) -> anyhow::Result { let path = self.get_target_path(path); let file = fs::OpenOptions::new() @@ -192,45 +211,30 @@ impl FileSystem { Ok(stream) } - /// Actually removes a file from the underlying file system - /// - /// * `path`: The file's absolute path (absolute not in relation to the root file system but `self.base_directory`) - async fn remove_file(&self, path: &AbsoluteFilePath) -> anyhow::Result { - let path = self.get_target_path(path); - - fs::remove_file(&path).await?; - - Ok(path) - } - - async fn rename( - &self, - path: &AbsoluteFilePath, - new_name: &FileName, - ) -> anyhow::Result { + async fn mv(&self, path: &AbsoluteFilePath, target_path: &AbsoluteFilePath) -> io::Result<()> { let current_path = self.get_target_path(path); + let target_path = self.get_target_path(target_path); - let new_path = { - let mut c = current_path.to_string(); - let last_slash_index = c.rfind('/').unwrap(); - c.drain((last_slash_index + 1)..); - c.push_str(new_name.as_str()); - - FilePath::new(&c)? - }; - - if fs::try_exists(&new_path).await? { - bail!("File exists"); + if !fs::try_exists(¤t_path).await? { + return Err(io::ErrorKind::NotFound.into()); } - fs::rename(current_path, &new_path).await?; + if fs::try_exists(&target_path).await? { + return Err(io::ErrorKind::AlreadyExists.into()); + } - Ok(new_path) + fs::rename(current_path, &target_path).await + } + + async fn touch(&self, path: &AbsoluteFilePath) -> io::Result<()> { + let path = self.get_target_path(path); + + tokio::fs::File::create(&path).await.map(|_| ()) } } impl FileSystemRepository for FileSystem { - async fn list_files(&self, request: ListFilesRequest) -> Result, ListFilesError> { + async fn ls(&self, request: LsRequest) -> Result, LsError> { let files = self.get_all_files(request.path()).await.map_err(|err| { anyhow!(err).context(format!( "Failed to get the files at path: {}", @@ -241,81 +245,56 @@ impl FileSystemRepository for FileSystem { Ok(files) } - async fn create_directory( - &self, - request: CreateDirectoryRequest, - ) -> Result { - 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 { - 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()) + async fn cat(&self, request: CatRequest) -> Result { + self.cat(request.path()) .await - .map_err(|e| { - anyhow!( - "Failed to write {} byte(s) to path {}: {e:?}", - request.data().len(), - request.path() - ) - })?; - - Ok(file_path) + .map_err(|e| anyhow!("Failed to fetch file {}: {e:?}", request.path()).into()) } - async fn fetch_file(&self, request: FetchFileRequest) -> Result { - let contents = self - .fetch_file(request.path()) + async fn mkdir(&self, request: MkdirRequest) -> Result<(), MkdirError> { + self.mkdir(request.path()) .await - .map_err(|e| anyhow!("Failed to fetch file {}: {e:?}", request.path()))?; - - Ok(contents) + .map_err(|e| match e.kind() { + std::io::ErrorKind::AlreadyExists => MkdirError::Exists, + _ => anyhow!("Failed to create directory at path: {}", request.path()).into(), + }) } - async fn delete_file(&self, request: DeleteFileRequest) -> Result { - let deleted_path = self - .remove_file(request.path()) + async fn rm(&self, request: RmRequest) -> Result<(), RmError> { + self.rm(request.path(), request.force()) .await - .context(format!("Failed to delete file at {}", request.path()))?; - - Ok(deleted_path) + .map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound => RmError::NotFound, + std::io::ErrorKind::DirectoryNotEmpty => RmError::NotEmpty, + _ => anyhow!("Failed to delete file at {}: {e:?}", request.path()).into(), + }) } - async fn rename_entry( - &self, - request: RenameEntryRequest, - ) -> Result { - let new_path = self - .rename(request.path(), request.new_name()) + async fn mv(&self, request: MvRequest) -> Result<(), MvError> { + self.mv(request.path(), request.target_path()) .await - .map_err(|e| { - anyhow!( - "Failed to rename {} to {}: {e:?}", + .map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound => MvError::NotFound, + _ => anyhow!( + "Failed to move {} to {}: {e:?}", request.path(), - request.new_name() + request.target_path() ) - })?; + .into(), + }) + } - Ok(new_path) + async fn touch(&self, request: TouchRequest) -> Result<(), TouchError> { + self.touch(request.path()) + .await + .map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound => TouchError::NotFound, + _ => anyhow!("Failed to touch path {}: {e:?}", request.path()).into(), + }) + } + + async fn save(&self, request: SaveRequest<'_>) -> Result { + let (path, mut stream) = request.unpack(); + Ok(self.save(&path, &mut stream).await.map(SaveResponse::new)?) } } diff --git a/backend/src/lib/outbound/metrics_debug_logger.rs b/backend/src/lib/outbound/metrics_debug_logger.rs index 1fc1304..a390b4d 100644 --- a/backend/src/lib/outbound/metrics_debug_logger.rs +++ b/backend/src/lib/outbound/metrics_debug_logger.rs @@ -52,114 +52,104 @@ impl WarrenMetrics for MetricsDebugLogger { tracing::debug!("[Metrics] Fetch warrens failed"); } - async fn record_warren_fetch_file_success(&self) { - tracing::debug!("[Metrics] Fetch warren file succeeded"); - } - async fn record_warren_fetch_file_failure(&self) { - tracing::debug!("[Metrics] Fetch warren file failed"); - } - - async fn record_list_warren_files_success(&self) { + async fn record_warren_ls_success(&self) { tracing::debug!("[Metrics] Warren list files succeeded"); } - - async fn record_list_warren_files_failure(&self) { + async fn record_warren_ls_failure(&self) { tracing::debug!("[Metrics] Warren list files failed"); } - async fn record_warren_directory_creation_success(&self) { - tracing::debug!("[Metrics] Warren directory creation succeeded"); + async fn record_warren_cat_success(&self) { + tracing::debug!("[Metrics] Fetch warren file succeeded"); + } + async fn record_warren_cat_failure(&self) { + tracing::debug!("[Metrics] Fetch warren file failed"); } - async fn record_warren_directory_creation_failure(&self) { + async fn record_warren_mkdir_success(&self) { + tracing::debug!("[Metrics] Warren directory creation succeeded"); + } + async fn record_warren_mkdir_failure(&self) { tracing::debug!("[Metrics] Warren directory creation failed"); } - async fn record_warren_directory_deletion_success(&self) { - tracing::debug!("[Metrics] Warren directory deletion succeeded"); - } - - async fn record_warren_directory_deletion_failure(&self) { - tracing::debug!("[Metrics] Warren directory deletion failed"); - } - - async fn record_warren_file_upload_success(&self) { - tracing::debug!("[Metrics] Warren file upload succeeded"); - } - async fn record_warren_file_upload_failure(&self) { - tracing::debug!("[Metrics] Warren file upload failed"); - } - - async fn record_warren_files_upload_success(&self) { - tracing::debug!("[Metrics] Warren files upload succeded"); - } - async fn record_warren_files_upload_failure(&self) { - tracing::debug!("[Metrics] Warren files upload failed at least partially"); - } - - async fn record_warren_file_deletion_success(&self) { + async fn record_warren_rm_success(&self) { tracing::debug!("[Metrics] Warren file deletion succeeded"); } - async fn record_warren_file_deletion_failure(&self) { + async fn record_warren_rm_failure(&self) { tracing::debug!("[Metrics] Warren file deletion failed"); } - async fn record_warren_entry_rename_success(&self) { + async fn record_warren_mv_success(&self) { tracing::debug!("[Metrics] Warren entry rename succeeded"); } - async fn record_warren_entry_rename_failure(&self) { + async fn record_warren_mv_failure(&self) { tracing::debug!("[Metrics] Warren entry rename failed"); } + + async fn record_warren_save_success(&self) { + tracing::debug!("[Metrics] Warren file upload succeeded"); + } + async fn record_warren_save_failure(&self) { + tracing::debug!("[Metrics] Warren file upload failed"); + } + + async fn record_warren_touch_success(&self) { + tracing::debug!("[Metrics] Warren entry touch succeeded"); + } + async fn record_warren_touch_failure(&self) { + tracing::debug!("[Metrics] Warren entry touch failed"); + } } impl FileSystemMetrics for MetricsDebugLogger { - async fn record_list_files_success(&self) { - tracing::debug!("[Metrics] File list succeeded"); + async fn record_ls_success(&self) { + tracing::debug!("[Metrics] Ls succeeded"); } - async fn record_list_files_failure(&self) { - tracing::debug!("[Metrics] File list failed"); + async fn record_ls_failure(&self) { + tracing::debug!("[Metrics] Ls failed"); } - async fn record_directory_creation_success(&self) { - tracing::debug!("[Metrics] Directory creation succeeded"); + async fn record_cat_success(&self) { + tracing::debug!("[Metrics] Cat succeeded"); } - async fn record_directory_creation_failure(&self) { - tracing::debug!("[Metrics] Directory creation failed"); + async fn record_cat_failure(&self) { + tracing::debug!("[Metrics] Cat failed"); } - async fn record_directory_deletion_success(&self) { - tracing::debug!("[Metrics] Directory deletion succeeded"); + async fn record_mkdir_success(&self) { + tracing::debug!("[Metrics] Mkdir succeeded"); } - async fn record_directory_deletion_failure(&self) { - tracing::debug!("[Metrics] Directory deletion failed"); + async fn record_mkdir_failure(&self) { + tracing::debug!("[Metrics] Mkdir failed"); } - async fn record_file_creation_success(&self) { - tracing::debug!("[Metrics] File creation succeeded"); + async fn record_rm_success(&self) { + tracing::debug!("[Metrics] Rm succeeded"); } - async fn record_file_creation_failure(&self) { - tracing::debug!("[Metrics] File creation failed"); + async fn record_rm_failure(&self) { + tracing::debug!("[Metrics] Rm failed"); } - async fn record_file_fetch_success(&self) { - tracing::debug!("[Metrics] File fetch succeeded"); + async fn record_mv_success(&self) { + tracing::debug!("[Metrics] Mv succeeded"); } - async fn record_file_fetch_failure(&self) { - tracing::debug!("[Metrics] File fetch failed"); + async fn record_mv_failure(&self) { + tracing::debug!("[Metrics] Mv failed"); } - async fn record_file_deletion_success(&self) { - tracing::debug!("[Metrics] File deletion succeeded"); + async fn record_save_success(&self) { + tracing::debug!("[Metrics] Save succeeded"); } - async fn record_file_deletion_failure(&self) { - tracing::debug!("[Metrics] File deletion failed"); + async fn record_save_failure(&self) { + tracing::debug!("[Metrics] Save failed"); } - async fn record_entry_rename_success(&self) { - tracing::debug!("[Metrics] Entry rename succeeded"); + async fn record_touch_success(&self) { + tracing::debug!("[Metrics] Touch succeeded"); } - async fn record_entry_rename_failure(&self) { - tracing::debug!("[Metrics] Entry rename failed"); + async fn record_touch_failure(&self) { + tracing::debug!("[Metrics] Touch failed"); } } @@ -269,13 +259,6 @@ impl AuthMetrics for MetricsDebugLogger { tracing::debug!("[Metrics] User warren deletion failed"); } - async fn record_auth_warren_fetch_file_success(&self) { - tracing::debug!("[Metrics] Warren file fetch succeeded"); - } - async fn record_auth_warren_fetch_file_failure(&self) { - tracing::debug!("[Metrics] Warren file fetch failed"); - } - async fn record_auth_fetch_user_warren_list_success(&self) { tracing::debug!("[Metrics] Auth warren list succeeded"); } @@ -297,45 +280,52 @@ impl AuthMetrics for MetricsDebugLogger { tracing::debug!("[Metrics] Auth warren fetch failed"); } - async fn record_auth_warren_file_list_success(&self) { - tracing::debug!("[Metrics] Auth warren file list succeeded"); + async fn record_auth_warren_ls_success(&self) { + tracing::debug!("[Metrics] Auth warren ls succeeded"); } - async fn record_auth_warren_file_list_failure(&self) { - tracing::debug!("[Metrics] Auth warren file list failed"); + async fn record_auth_warren_ls_failure(&self) { + tracing::debug!("[Metrics] Auth warren ls failed"); } - async fn record_auth_warren_directory_creation_success(&self) { - tracing::debug!("[Metrics] Auth warren directory creation succeeded"); + async fn record_auth_warren_cat_success(&self) { + tracing::debug!("[Metrics] Warren file fetch succeeded"); } - async fn record_auth_warren_directory_creation_failure(&self) { - tracing::debug!("[Metrics] Auth warren directory creation failed"); + async fn record_auth_warren_cat_failure(&self) { + tracing::debug!("[Metrics] Warren file fetch failed"); } - async fn record_auth_warren_directory_deletion_success(&self) { - tracing::debug!("[Metrics] Auth warren directory deletion succeeded"); + async fn record_auth_warren_mkdir_success(&self) { + tracing::debug!("[Metrics] Auth warren mkdir succeeded"); } - async fn record_auth_warren_directory_deletion_failure(&self) { - tracing::debug!("[Metrics] Auth warren directory deletion failed"); + async fn record_auth_warren_mkdir_failure(&self) { + tracing::debug!("[Metrics] Auth warren mkdir failed"); } - async fn record_auth_warren_file_deletion_success(&self) { - tracing::debug!("[Metrics] Auth warren file deletion succeeded"); + async fn record_auth_warren_rm_success(&self) { + tracing::debug!("[Metrics] Auth warren rm succeeded"); } - async fn record_auth_warren_file_deletion_failure(&self) { - tracing::debug!("[Metrics] Auth warren file deletion failed"); + async fn record_auth_warren_rm_failure(&self) { + tracing::debug!("[Metrics] Auth warren rm failed"); } - async fn record_auth_warren_entry_rename_success(&self) { - tracing::debug!("[Metrics] Auth warren entry rename succeeded"); + async fn record_auth_warren_mv_success(&self) { + tracing::debug!("[Metrics] Auth warren mv succeeded"); } - async fn record_auth_warren_entry_rename_failure(&self) { - tracing::debug!("[Metrics] Auth warren entry rename failed"); + async fn record_auth_warren_mv_failure(&self) { + tracing::debug!("[Metrics] Auth warren mv failed"); } - async fn record_auth_warren_files_upload_success(&self) { - tracing::debug!("[Metrics] Auth warren files upload succeeded"); + async fn record_auth_warren_save_success(&self) { + tracing::debug!("[Metrics] Auth warren save succeeded"); } - async fn record_auth_warren_files_upload_failure(&self) { - tracing::debug!("[Metrics] Auth warren files upload failed"); + async fn record_auth_warren_save_failure(&self) { + tracing::debug!("[Metrics] Auth warren save failed"); + } + + async fn record_auth_warren_touch_success(&self) { + tracing::debug!("[Metrics] Auth warren touch succeeded"); + } + async fn record_auth_warren_touch_failure(&self) { + tracing::debug!("[Metrics] Auth warren touch failed"); } } diff --git a/backend/src/lib/outbound/notifier_debug_logger.rs b/backend/src/lib/outbound/notifier_debug_logger.rs index 096214d..ae986ff 100644 --- a/backend/src/lib/outbound/notifier_debug_logger.rs +++ b/backend/src/lib/outbound/notifier_debug_logger.rs @@ -3,12 +3,12 @@ use uuid::Uuid; use crate::domain::warren::{ models::{ auth_session::requests::FetchAuthSessionResponse, - file::{AbsoluteFilePath, File, FilePath}, + file::{AbsoluteFilePath, File}, user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User}, user_warren::UserWarren, warren::{ - CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse, - ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren, + Warren, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse, WarrenRmResponse, + WarrenSaveResponse, WarrenTouchResponse, }, }, ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier}, @@ -46,7 +46,7 @@ impl WarrenNotifier for NotifierDebugLogger { tracing::debug!("[Notifier] Fetched warren {}", warren.name()); } - async fn warren_file_fetched(&self, warren: &Warren, path: &AbsoluteFilePath) { + async fn warren_cat(&self, warren: &Warren, path: &AbsoluteFilePath) { tracing::debug!( "[Notifier] Fetched file {} in warren {}", path, @@ -54,7 +54,7 @@ impl WarrenNotifier for NotifierDebugLogger { ); } - async fn warren_files_listed(&self, response: &ListWarrenFilesResponse) { + async fn warren_ls(&self, response: &WarrenLsResponse) { tracing::debug!( "[Notifier] Listed {} file(s) in warren {}", response.files().len(), @@ -62,7 +62,7 @@ impl WarrenNotifier for NotifierDebugLogger { ); } - async fn warren_directory_created(&self, response: &CreateWarrenDirectoryResponse) { + async fn warren_mkdir(&self, response: &WarrenMkdirResponse) { tracing::debug!( "[Notifier] Created directory {} in warren {}", response.path(), @@ -70,31 +70,11 @@ impl WarrenNotifier for NotifierDebugLogger { ); } - async fn warren_directory_deleted(&self, response: &DeleteWarrenDirectoryResponse) { - tracing::debug!( - "[Notifier] Deleted directory {} in warren {}", - response.path(), - response.warren().name() - ); + async fn warren_save(&self, warren: &Warren, path: &AbsoluteFilePath) { + tracing::debug!("[Notifier] Saved file {} to warren {}", path, warren.name()); } - async fn warren_file_uploaded(&self, warren: &Warren, path: &FilePath) { - tracing::debug!( - "[Notifier] Uploaded file {} to warren {}", - path, - warren.name() - ); - } - - async fn warren_files_uploaded(&self, response: &UploadWarrenFilesResponse) { - tracing::debug!( - "[Notifier] Uploaded {} file(s) to warren {}", - response.paths().len(), - response.warren().name() - ); - } - - async fn warren_file_deleted(&self, response: &DeleteWarrenFileResponse) { + async fn warren_rm(&self, response: &WarrenRmResponse) { tracing::debug!( "[Notifier] Deleted file {} from warren {}", response.path(), @@ -102,7 +82,7 @@ impl WarrenNotifier for NotifierDebugLogger { ); } - async fn warren_entry_renamed(&self, response: &RenameWarrenEntryResponse) { + async fn warren_mv(&self, response: &WarrenMvResponse) { tracing::debug!( "[Notifier] Renamed file {} to {} in warren {}", response.old_path(), @@ -110,36 +90,44 @@ impl WarrenNotifier for NotifierDebugLogger { response.warren().name(), ); } + + async fn warren_touch(&self, warren: &Warren, path: &AbsoluteFilePath) { + tracing::debug!( + "[Notifier] Touched file {} in warren {}", + path, + warren.name() + ); + } } impl FileSystemNotifier for NotifierDebugLogger { - async fn files_listed(&self, files: &Vec) { + async fn ls(&self, files: &Vec) { tracing::debug!("[Notifier] Listed {} file(s)", files.len()); } - async fn directory_created(&self, path: &FilePath) { - tracing::debug!("[Notifier] Created directory {}", path); - } - - async fn directory_deleted(&self, path: &FilePath) { - tracing::debug!("[Notifier] Deleted directory {}", path); - } - - async fn file_created(&self, path: &FilePath) { - tracing::debug!("[Notifier] Created file {}", path); - } - - async fn file_fetched(&self, path: &AbsoluteFilePath) { + async fn cat(&self, path: &AbsoluteFilePath) { tracing::debug!("[Notifier] Fetched file {path}"); } - async fn file_deleted(&self, path: &FilePath) { + async fn mkdir(&self, path: &AbsoluteFilePath) { + tracing::debug!("[Notifier] Created directory {}", path); + } + + async fn rm(&self, path: &AbsoluteFilePath) { tracing::debug!("[Notifier] Deleted file {}", path); } - async fn entry_renamed(&self, old_path: &FilePath, new_path: &FilePath) { + async fn mv(&self, old_path: &AbsoluteFilePath, new_path: &AbsoluteFilePath) { tracing::debug!("[Notifier] Renamed file {} to {}", old_path, new_path); } + + async fn save(&self, path: &AbsoluteFilePath) { + tracing::debug!("[Notifier] Saved file {}", path); + } + + async fn touch(&self, path: &AbsoluteFilePath) { + tracing::debug!("[Notifier] Touched file {}", path); + } } impl AuthNotifier for NotifierDebugLogger { @@ -281,19 +269,14 @@ impl AuthNotifier for NotifierDebugLogger { ); } - async fn auth_warren_file_fetched( - &self, - user: &User, - warren_id: &Uuid, - path: &AbsoluteFilePath, - ) { + async fn auth_warren_cat(&self, user: &User, warren_id: &Uuid, path: &AbsoluteFilePath) { tracing::debug!( "[Notifier] User {} fetched file {path} in warren {warren_id}", user.id(), ); } - async fn auth_warren_files_listed(&self, user: &User, response: &ListWarrenFilesResponse) { + async fn auth_warren_ls(&self, user: &User, response: &WarrenLsResponse) { tracing::debug!( "[Notifier] Listed {} file(s) in warren {} for authenticated user {}", response.files().len(), @@ -302,11 +285,7 @@ impl AuthNotifier for NotifierDebugLogger { ); } - async fn auth_warren_directory_created( - &self, - user: &User, - response: &CreateWarrenDirectoryResponse, - ) { + async fn auth_warren_mkdir(&self, user: &User, response: &WarrenMkdirResponse) { tracing::debug!( "[Notifier] Created directory {} in warren {} for authenticated user {}", response.path(), @@ -315,29 +294,16 @@ impl AuthNotifier for NotifierDebugLogger { ); } - async fn auth_warren_directory_deleted( - &self, - user: &User, - response: &DeleteWarrenDirectoryResponse, - ) { + async fn auth_warren_save(&self, user: &User, response: &WarrenSaveResponse) { tracing::debug!( - "[Notifier] Deleted directory {} in warren {} for authenticated user {}", + "[Notifier] Uploaded file {} to warren {} for authenticated user {}", response.path(), response.warren().name(), user.id(), ); } - async fn auth_warren_files_uploaded(&self, user: &User, response: &UploadWarrenFilesResponse) { - tracing::debug!( - "[Notifier] Uploaded {} file(s) to warren {} for authenticated user {}", - response.paths().len(), - response.warren().name(), - user.id(), - ); - } - - async fn auth_warren_file_deleted(&self, user: &User, response: &DeleteWarrenFileResponse) { + async fn auth_warren_rm(&self, user: &User, response: &WarrenRmResponse) { tracing::debug!( "[Notifier] Deleted file {} from warren {} for authenticated user {}", response.path(), @@ -346,7 +312,7 @@ impl AuthNotifier for NotifierDebugLogger { ); } - async fn auth_warren_entry_renamed(&self, user: &User, response: &RenameWarrenEntryResponse) { + async fn auth_warren_mv(&self, user: &User, response: &WarrenMvResponse) { tracing::debug!( "[Notifier] Renamed file {} to {} in warren {} for authenticated user {}", response.old_path(), @@ -355,4 +321,13 @@ impl AuthNotifier for NotifierDebugLogger { user.id(), ); } + + async fn auth_warren_touch(&self, user: &User, response: &WarrenTouchResponse) { + tracing::debug!( + "[Notifier] Touched file {} in warren {} for authenticated user {}", + response.path(), + response.warren().name(), + user.id() + ) + } } diff --git a/frontend/components/actions/UploadDialog.vue b/frontend/components/actions/UploadDialog.vue index 6a3fbfe..1da83bb 100644 --- a/frontend/components/actions/UploadDialog.vue +++ b/frontend/components/actions/UploadDialog.vue @@ -54,7 +54,7 @@ function onFilesChanged(event: Event) { if (uploadStore.destination == null) { uploadStore.destination = warrenStore.current; - } else if (currentAndUploadRouteMatch.value) { + } else if (!currentAndUploadRouteMatch.value) { toast.warning('Upload', { description: 'The unfinished items belong to a different directory. Remove them before attempting to upload to a different directory.', diff --git a/frontend/components/actions/UploadListEntry.vue b/frontend/components/actions/UploadListEntry.vue index 4e7df9a..8c4c2e2 100644 --- a/frontend/components/actions/UploadListEntry.vue +++ b/frontend/components/actions/UploadListEntry.vue @@ -24,7 +24,10 @@ function createObjectUrl(file: File): string { { 'p-2': !file.data.type.startsWith('image/') }, ]" > -
+
{ const { data, error } = await useFetch< ApiResponse<{ files: DirectoryEntry[] }> - >(getApiUrl(`warrens/files`), { + >(getApiUrl(`warrens/files/ls`), { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ @@ -60,7 +60,7 @@ export async function createDirectory( path += directoryName; - const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { + const { status } = await useFetch(getApiUrl(`warrens/files/mkdir`), { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ @@ -99,8 +99,8 @@ export async function deleteWarrenDirectory( path += directoryName; - const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { - method: 'DELETE', + const { status } = await useFetch(getApiUrl(`warrens/files/rm`), { + method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ warrenId, @@ -139,12 +139,13 @@ export async function deleteWarrenFile( path += fileName; - const { status } = await useFetch(getApiUrl(`warrens/files/file`), { - method: 'DELETE', + const { status } = await useFetch(getApiUrl(`warrens/files/rm`), { + method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, + force: false, }), }); @@ -174,7 +175,7 @@ export async function uploadToWarren( onProgress: ((loaded: number, total: number) => void) | undefined ): Promise<{ success: boolean }> { const xhr = new XMLHttpRequest(); - xhr.open('POST', getApiUrl(`warrens/files/upload`)); + xhr.open('POST', getApiUrl(`warrens/files/save`)); xhr.upload.onprogress = (e) => { onProgress?.(e.loaded, e.total); }; @@ -236,15 +237,17 @@ export async function renameWarrenEntry( if (!path.endsWith('/')) { path += '/'; } + let targetPath = path; + targetPath += newName; path += currentName; - const { status } = await useFetch(getApiUrl(`warrens/files/rename`), { - method: 'PATCH', + const { status } = await useFetch(getApiUrl(`warrens/files/mv`), { + method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, - newName, + targetPath, }), }); @@ -277,7 +280,7 @@ export async function fetchFile( path += fileName; const { data, error } = await useFetch( - getApiUrl(`warrens/files/fetch?warrenId=${warrenId}&path=${path}`), + getApiUrl(`warrens/files/cat?warrenId=${warrenId}&path=${path}`), { method: 'GET', headers: getApiHeaders(),