From d950f5ebf9c0963ab9b9d996d01582aae0c676d2 Mon Sep 17 00:00:00 2001 From: Cameron Garnham Date: Sun, 15 Oct 2023 23:12:50 +0200 Subject: [PATCH 001/309] develop: bump to version 3.0.0-alpha.3-develop --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1046838d..e40c29b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "torrust-index" -version = "3.0.0-alpha.2" +version = "3.0.0-alpha.3-develop" dependencies = [ "argon2", "async-trait", @@ -3042,7 +3042,7 @@ dependencies = [ [[package]] name = "torrust-index-located-error" -version = "3.0.0-alpha.2" +version = "3.0.0-alpha.3-develop" dependencies = [ "log", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 89c35e18..21f27a3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ license = "AGPL-3.0-only" publish = true repository = "https://github.com/torrust/torrust-tracker" rust-version = "1.72" -version = "3.0.0-alpha.2" +version = "3.0.0-alpha.3-develop" [profile.dev.package.sqlx-macros] @@ -69,7 +69,7 @@ text-to-png = "0" thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" -torrust-index-located-error = { version = "3.0.0-alpha.2", path = "packages/located-error" } +torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } tower-http = { version = "0", features = ["compression-full", "cors"] } urlencoding = "2" uuid = { version = "1", features = ["v4"] } From 9777d2687610c4fb838853fa1e5940e90934c446 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 27 Oct 2023 11:26:03 +0100 Subject: [PATCH 002/309] fix: docker compose --- compose.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compose.yaml b/compose.yaml index 613ed0b0..df6cc286 100644 --- a/compose.yaml +++ b/compose.yaml @@ -5,17 +5,17 @@ services: image: torrust-index:release tty: true environment: - - TORRUST_INDEX_CONFIG=${TORRUST_TRACKER_CONFIG} - - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-sqlite3} + - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} + - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - TORRUST_INDEX_TRACKER_API_TOKEN=${TORRUST_INDEX_TRACKER_API_TOKEN:-MyAccessToken} networks: - server_side ports: - 3001:3001 volumes: - - ./storage/tracker/lib:/var/lib/torrust/index:Z - - ./storage/tracker/log:/var/log/torrust/index:Z - - ./storage/tracker/etc:/etc/torrust/index:Z + - ./storage/index/lib:/var/lib/torrust/index:Z + - ./storage/index/log:/var/log/torrust/index:Z + - ./storage/index/etc:/etc/torrust/index:Z depends_on: - tracker - mailcatcher From 406abbf5ea1a8b9c838b1c68031a1be7b7d500bb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 27 Oct 2023 11:39:42 +0100 Subject: [PATCH 003/309] refactor: rename struct field to avoid clippy error --- tests/common/contexts/torrent/asserts.rs | 5 +---- tests/common/contexts/torrent/responses.rs | 2 +- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/common/contexts/torrent/asserts.rs b/tests/common/contexts/torrent/asserts.rs index d0f1a8cf..261df54e 100644 --- a/tests/common/contexts/torrent/asserts.rs +++ b/tests/common/contexts/torrent/asserts.rs @@ -15,10 +15,7 @@ pub fn assert_expected_torrent_details(torrent: &TorrentDetails, expected_torren ("info_hash", torrent.info_hash == expected_torrent.info_hash), ("title", torrent.title == expected_torrent.title), ("description", torrent.description == expected_torrent.description), - ( - "category.category_id", - torrent.category.category_id == expected_torrent.category.category_id, - ), + ("category.category_id", torrent.category.id == expected_torrent.category.id), ("category.name", torrent.category.name == expected_torrent.category.name), ("file_size", torrent.file_size == expected_torrent.file_size), ("seeders", torrent.seeders == expected_torrent.seeders), diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index f95d67ce..a83dd84c 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -70,7 +70,7 @@ pub struct TorrentDetails { #[derive(Deserialize, PartialEq, Debug)] pub struct Category { - pub category_id: CategoryId, + pub id: CategoryId, pub name: String, pub num_torrents: u64, } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 1ebf8f70..fa2b5053 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -182,7 +182,7 @@ mod for_guests { title: test_torrent.index_info.title.clone(), description: test_torrent.index_info.description, category: Category { - category_id: software_predefined_category_id(), + id: software_predefined_category_id(), name: test_torrent.index_info.category, num_torrents: 19, // Ignored in assertion }, From 3550c447c9819ed268229d0e2fecf6a15de900df Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 26 Sep 2023 17:31:49 +0100 Subject: [PATCH 004/309] fix: [#226] user serde-bencode v0.2.4 There was a problem with list (nodes) containing other list (tuples) with different value types. ```json { "info": { "length": 8, "name": "minimal.txt", "piece length": 16384, "pieces": "30 9A 84 51 4A F1 09 D0 8C 34 42 11 26 67 7F 84 B3 23 4D 78" }, "nodes": [ [ "188.163.121.224", 56711 ], [ "162.250.131.26", 13386 ] ] } ``` --- Cargo.lock | 301 +++++++++++++++++++++---------------- src/models/torrent_file.rs | 5 +- 2 files changed, 171 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e40c29b4..371ff73a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -30,14 +30,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -230,9 +231,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -254,9 +255,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -364,11 +365,11 @@ dependencies = [ [[package]] name = "chumsky" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.14.2", "stacker", ] @@ -432,9 +433,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -584,7 +585,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "memchr", ] @@ -739,9 +740,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -754,9 +755,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -764,15 +765,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -792,15 +793,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -809,21 +810,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -913,16 +914,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "allocator-api2", ] @@ -932,7 +933,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.2", ] [[package]] @@ -1051,7 +1052,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1073,16 +1074,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1138,14 +1139,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.2", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -1214,9 +1215,9 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "pem", - "ring", + "ring 0.16.20", "serde", "serde_json", "simple_asn1", @@ -1242,12 +1243,12 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d47084ad58f99c26816d174702f60e873f861fcef3f9bd6075b4ad2dd72d07d5" +checksum = "a466bc111374ccf4d90877dba636924a2185e67e5be4b35d32043199365097b2" dependencies = [ "async-trait", - "base64 0.21.4", + "base64 0.21.5", "chumsky", "email-encoding", "email_address", @@ -1264,7 +1265,7 @@ dependencies = [ "quoted_printable", "rustls", "rustls-pemfile", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1309,9 +1310,9 @@ checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1400,9 +1401,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -1545,7 +1546,7 @@ version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1605,9 +1606,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -1677,9 +1678,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" dependencies = [ "memchr", "thiserror", @@ -1688,9 +1689,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" dependencies = [ "pest", "pest_generator", @@ -1698,9 +1699,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" dependencies = [ "pest", "pest_meta", @@ -1711,9 +1712,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" dependencies = [ "once_cell", "pest", @@ -1881,18 +1882,18 @@ checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1902,9 +1903,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1923,7 +1924,7 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1990,11 +1991,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "ron" version = "0.7.1" @@ -2017,16 +2032,14 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" dependencies = [ - "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", @@ -2064,11 +2077,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.19" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -2077,12 +2090,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring", + "ring 0.17.5", "rustls-webpki", "sct", ] @@ -2093,17 +2106,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2169,12 +2182,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2208,9 +2221,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -2236,9 +2249,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -2268,9 +2281,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] @@ -2389,9 +2402,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -2399,9 +2412,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -2462,7 +2475,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "atoi", "byteorder", "bytes", @@ -2544,8 +2557,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" dependencies = [ "atoi", - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "bytes", "crc", @@ -2587,8 +2600,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" dependencies = [ "atoi", - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "crc", "dotenvy", @@ -2733,9 +2746,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", @@ -2789,18 +2802,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -2888,7 +2901,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -2937,9 +2950,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -2960,9 +2973,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" dependencies = [ "serde", "serde_spanned", @@ -2972,18 +2985,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" dependencies = [ "indexmap 2.0.2", "serde", @@ -3032,7 +3045,7 @@ dependencies = [ "text-to-png", "thiserror", "tokio", - "toml 0.8.2", + "toml 0.8.5", "torrust-index-located-error", "tower-http", "urlencoding", @@ -3071,7 +3084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "async-compression", - "bitflags 2.4.0", + "bitflags 2.4.1", "bytes", "futures-core", "futures-util", @@ -3099,9 +3112,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.39" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", @@ -3281,6 +3294,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -3327,9 +3346,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", ] @@ -3503,10 +3522,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] @@ -3623,6 +3642,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 98e57b92..3ce4b925 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -12,7 +12,7 @@ pub struct Torrent { #[serde(default)] pub announce: Option, #[serde(default)] - pub nodes: Option>, + pub nodes: Option>, #[serde(default)] pub encoding: Option, #[serde(default)] @@ -30,9 +30,6 @@ pub struct Torrent { pub created_by: Option, } -#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub struct TorrentNode(String, i64); - #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct TorrentInfoDictionary { pub name: String, From d42d0f9c87196c216ffb9997b96d190720362fc2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 27 Oct 2023 15:41:17 +0100 Subject: [PATCH 005/309] chore: update cargo dependencies --- Cargo.lock | 51 ++++++++++++++++----------------------------------- Cargo.toml | 4 ++-- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 371ff73a..3efc781a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,13 +1211,13 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09" dependencies = [ "base64 0.21.5", "pem", - "ring 0.16.20", + "ring", "serde", "serde_json", "simple_asn1", @@ -1654,11 +1654,12 @@ dependencies = [ [[package]] name = "pem" -version = "1.1.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" dependencies = [ - "base64 0.13.1", + "base64 0.21.5", + "serde", ] [[package]] @@ -1981,21 +1982,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.5" @@ -2006,7 +1992,7 @@ dependencies = [ "getrandom", "libc", "spin 0.9.8", - "untrusted 0.9.0", + "untrusted", "windows-sys", ] @@ -2095,7 +2081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring 0.17.5", + "ring", "rustls-webpki", "sct", ] @@ -2115,8 +2101,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2186,8 +2172,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -3288,12 +3274,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3474,14 +3454,15 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "which" -version = "4.4.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" dependencies = [ "either", "home", "once_cell", "rustix", + "windows-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21f27a3e..802cadaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ futures = "0" hex = "0" hyper = "0" indexmap = "2" -jsonwebtoken = "8" +jsonwebtoken = "9" lazy_static = "1.4.0" lettre = { version = "0", features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls", "tokio1-rustls-tls"] } log = "0" @@ -78,4 +78,4 @@ uuid = { version = "1", features = ["v4"] } rand = "0" tempfile = "3" uuid = { version = "1", features = ["v4"] } -which = "4" +which = "5" From 821708f999d31b96176c7b3b724141a9b05063a1 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 31 Oct 2023 18:44:19 +0100 Subject: [PATCH 006/309] chore: updated cargo dependency toml to 0.8.6 --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3efc781a..a0128ba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2959,9 +2959,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", @@ -2980,9 +2980,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.0.2", "serde", @@ -3031,7 +3031,7 @@ dependencies = [ "text-to-png", "thiserror", "tokio", - "toml 0.8.5", + "toml 0.8.6", "torrust-index-located-error", "tower-http", "urlencoding", @@ -3675,4 +3675,4 @@ checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", "pkg-config", -] +] \ No newline at end of file From 84f778d8c1bc015567e02cd069378d43c13dd3cf Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 3 Nov 2023 23:26:32 +0100 Subject: [PATCH 007/309] chore: bump serde_json to 1.0.108 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0128ba2..54241163 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2246,9 +2246,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", From b738291e3b9de20921c3c51b002b6d22168cb1c4 Mon Sep 17 00:00:00 2001 From: MMelchor Date: Thu, 28 Sep 2023 00:01:20 +0200 Subject: [PATCH 008/309] feat: [#303] store in the database the torrent fields creation_date created_by encoding API responses now contain the three field in the - Torrent details endpoint - Torrent list endpoint --- ...ust_add_creation_date_field_to_torrent.sql | 1 + ...1551_torrust_add_created_by_to_torrent.sql | 1 + ...211559_torrust_add_encoding_to_torrent.sql | 1 + ...ust_add_creation_date_field_to_torrent.sql | 1 + ...1551_torrust_add_created_by_to_torrent.sql | 1 + ...211559_torrust_add_encoding_to_torrent.sql | 1 + src/databases/mysql.rs | 16 ++++++++++++++-- src/databases/sqlite.rs | 19 +++++++++++++++++-- src/models/response.rs | 6 ++++++ src/models/torrent.rs | 3 +++ src/models/torrent_file.rs | 9 ++++++--- src/services/torrent_file.rs | 12 +++++++++--- tests/common/contexts/torrent/file.rs | 3 ++- tests/common/contexts/torrent/responses.rs | 6 ++++++ .../web/api/v1/contexts/torrent/contract.rs | 3 +++ 15 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql create mode 100644 migrations/mysql/20230921211551_torrust_add_created_by_to_torrent.sql create mode 100644 migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql create mode 100644 migrations/sqlite3/20230921211523_torrust_add_creation_date_field_to_torrent.sql create mode 100644 migrations/sqlite3/20230921211551_torrust_add_created_by_to_torrent.sql create mode 100644 migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql diff --git a/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql b/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql new file mode 100644 index 00000000..adc688e7 --- /dev/null +++ b/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE torrust_torrents ADD COLUMN creation_date BIGINT UNSIGNED NULL; diff --git a/migrations/mysql/20230921211551_torrust_add_created_by_to_torrent.sql b/migrations/mysql/20230921211551_torrust_add_created_by_to_torrent.sql new file mode 100644 index 00000000..33cb06d9 --- /dev/null +++ b/migrations/mysql/20230921211551_torrust_add_created_by_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE torrust_torrents ADD COLUMN created_by TEXT NULL; diff --git a/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql b/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql new file mode 100644 index 00000000..2250b31c --- /dev/null +++ b/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE torrust_torrents ADD COLUMN "encoding" TEXT NULL; diff --git a/migrations/sqlite3/20230921211523_torrust_add_creation_date_field_to_torrent.sql b/migrations/sqlite3/20230921211523_torrust_add_creation_date_field_to_torrent.sql new file mode 100644 index 00000000..d413f4b0 --- /dev/null +++ b/migrations/sqlite3/20230921211523_torrust_add_creation_date_field_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE "torrust_torrents" ADD COLUMN "creation_date" BIGINT NULL; diff --git a/migrations/sqlite3/20230921211551_torrust_add_created_by_to_torrent.sql b/migrations/sqlite3/20230921211551_torrust_add_created_by_to_torrent.sql new file mode 100644 index 00000000..8f34bce2 --- /dev/null +++ b/migrations/sqlite3/20230921211551_torrust_add_created_by_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE "torrust_torrents" ADD COLUMN "created_by" TEXT NULL; diff --git a/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql b/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql new file mode 100644 index 00000000..1bebfd91 --- /dev/null +++ b/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql @@ -0,0 +1 @@ +ALTER TABLE "torrust_torrents" ADD COLUMN "encoding" TEXT NULL; \ No newline at end of file diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 0c5175a6..c9df5dc4 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -387,6 +387,9 @@ impl Database for Mysql { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -465,8 +468,11 @@ impl Database for Mysql { root_hash, `source`, comment, - date_uploaded - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())", + date_uploaded, + creation_date, + created_by, + encoding + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(), ?, ?, ?)", ) .bind(uploader_id) .bind(metadata.category_id) @@ -754,6 +760,9 @@ impl Database for Mysql { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -782,6 +791,9 @@ impl Database for Mysql { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 02abfc24..18fa17ca 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -377,6 +377,9 @@ impl Database for Sqlite { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -455,8 +458,11 @@ impl Database for Sqlite { root_hash, `source`, comment, - date_uploaded - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')))", + date_uploaded, + creation_date, + created_by, + encoding + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')), ?, ?, ?)", ) .bind(uploader_id) .bind(metadata.category_id) @@ -469,6 +475,9 @@ impl Database for Sqlite { .bind(root_hash) .bind(torrent.info.source.clone()) .bind(torrent.comment.clone()) + .bind(torrent.creation_date.clone()) + .bind(torrent.created_by.clone()) + .bind(torrent.encoding.clone()) .execute(&mut *tx) .await .map(|v| v.last_insert_rowid()) @@ -743,6 +752,9 @@ impl Database for Sqlite { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -770,6 +782,9 @@ impl Database for Sqlite { tt.size AS file_size, tt.name, tt.comment, + tt.creation_date, + tt.created_by, + tt.encoding, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt diff --git a/src/models/response.rs b/src/models/response.rs index 7d408b79..e82acb45 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -63,6 +63,9 @@ pub struct TorrentResponse { pub tags: Vec, pub name: String, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } impl TorrentResponse { @@ -85,6 +88,9 @@ impl TorrentResponse { tags: vec![], name: torrent_listing.name, comment: torrent_listing.comment, + creation_date: torrent_listing.creation_date, + created_by: torrent_listing.created_by, + encoding: torrent_listing.encoding, } } } diff --git a/src/models/torrent.rs b/src/models/torrent.rs index 1c2d10cc..40a3f48d 100644 --- a/src/models/torrent.rs +++ b/src/models/torrent.rs @@ -25,6 +25,9 @@ pub struct TorrentListing { pub leechers: i64, pub name: String, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } #[derive(Debug, Display, PartialEq, Eq, Error)] diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 3ce4b925..4978cfc6 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -88,12 +88,12 @@ impl Torrent { info: info_dict, announce: None, nodes: None, - encoding: None, + encoding: db_torrent.encoding.clone(), httpseeds: None, announce_list: Some(torrent_announce_urls), - creation_date: None, + creation_date: db_torrent.creation_date.clone(), comment: db_torrent.comment.clone(), - created_by: None, + created_by: db_torrent.created_by.clone(), } } @@ -303,6 +303,9 @@ pub struct DbTorrent { pub private: Option, pub root_hash: i64, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } #[allow(clippy::module_name_repetitions)] diff --git a/src/services/torrent_file.rs b/src/services/torrent_file.rs index 338ba6e6..6c0be1cb 100644 --- a/src/services/torrent_file.rs +++ b/src/services/torrent_file.rs @@ -19,6 +19,9 @@ pub struct CreateTorrentRequest { // Other fields of the root level metainfo dictionary pub announce_urls: Vec>, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } impl CreateTorrentRequest { @@ -35,12 +38,12 @@ impl CreateTorrentRequest { info: info_dict, announce: None, nodes: None, - encoding: None, + encoding: self.encoding.clone(), httpseeds: None, announce_list: Some(self.announce_urls.clone()), - creation_date: None, + creation_date: self.creation_date.clone(), comment: self.comment.clone(), - created_by: None, + created_by: self.created_by.clone(), } } @@ -93,6 +96,9 @@ pub fn generate_random_torrent(id: Uuid) -> Torrent { files: torrent_files, announce_urls: torrent_announce_urls, comment: None, + creation_date: None, + created_by: None, + encoding: None, }; create_torrent_req.build_torrent() diff --git a/tests/common/contexts/torrent/file.rs b/tests/common/contexts/torrent/file.rs index b5f58339..5d8a541a 100644 --- a/tests/common/contexts/torrent/file.rs +++ b/tests/common/contexts/torrent/file.rs @@ -12,8 +12,9 @@ use which::which; pub struct TorrentFileInfo { pub name: String, pub comment: Option, - pub creation_date: Option, + pub creation_date: Option, pub created_by: Option, + pub encoding: Option, pub source: Option, pub info_hash: String, pub torrent_size: u64, diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index a83dd84c..b1ef0882 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -41,6 +41,9 @@ pub struct ListItem { pub leechers: i64, pub name: String, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } #[derive(Deserialize, PartialEq, Debug)] @@ -66,6 +69,9 @@ pub struct TorrentDetails { pub tags: Vec, pub name: String, pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, } #[derive(Deserialize, PartialEq, Debug)] diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index fa2b5053..0b8fcf18 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -213,6 +213,9 @@ mod for_guests { tags: vec![], name: test_torrent.index_info.name.clone(), comment: test_torrent.file_info.comment.clone(), + creation_date: test_torrent.file_info.creation_date.clone(), + created_by: test_torrent.file_info.created_by.clone(), + encoding: test_torrent.file_info.encoding.clone(), }; assert_expected_torrent_details(&torrent_details_response.data, &expected_torrent); From 0a485365f2b5c3c5c211160f2b6039d4b3325c3a Mon Sep 17 00:00:00 2001 From: MMelchor Date: Thu, 28 Sep 2023 14:06:57 +0200 Subject: [PATCH 009/309] refactor: [#303] store in the database the torrent fields creation_date created_by encoding Fixed linting errors --- src/databases/sqlite.rs | 2 +- src/models/torrent_file.rs | 2 +- src/services/torrent_file.rs | 2 +- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 18fa17ca..39d8b9b9 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -475,7 +475,7 @@ impl Database for Sqlite { .bind(root_hash) .bind(torrent.info.source.clone()) .bind(torrent.comment.clone()) - .bind(torrent.creation_date.clone()) + .bind(torrent.creation_date) .bind(torrent.created_by.clone()) .bind(torrent.encoding.clone()) .execute(&mut *tx) diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 4978cfc6..0d060095 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -91,7 +91,7 @@ impl Torrent { encoding: db_torrent.encoding.clone(), httpseeds: None, announce_list: Some(torrent_announce_urls), - creation_date: db_torrent.creation_date.clone(), + creation_date: db_torrent.creation_date, comment: db_torrent.comment.clone(), created_by: db_torrent.created_by.clone(), } diff --git a/src/services/torrent_file.rs b/src/services/torrent_file.rs index 6c0be1cb..3824ea9e 100644 --- a/src/services/torrent_file.rs +++ b/src/services/torrent_file.rs @@ -41,7 +41,7 @@ impl CreateTorrentRequest { encoding: self.encoding.clone(), httpseeds: None, announce_list: Some(self.announce_urls.clone()), - creation_date: self.creation_date.clone(), + creation_date: self.creation_date, comment: self.comment.clone(), created_by: self.created_by.clone(), } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 0b8fcf18..59f487b3 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -213,7 +213,7 @@ mod for_guests { tags: vec![], name: test_torrent.index_info.name.clone(), comment: test_torrent.file_info.comment.clone(), - creation_date: test_torrent.file_info.creation_date.clone(), + creation_date: test_torrent.file_info.creation_date, created_by: test_torrent.file_info.created_by.clone(), encoding: test_torrent.file_info.encoding.clone(), }; From aadde22fdc8525f95c1167196c263fbb7fee6e0c Mon Sep 17 00:00:00 2001 From: MMelchor Date: Thu, 28 Sep 2023 19:18:38 +0200 Subject: [PATCH 010/309] refactor: [#303] store in the database the torrent fields creation date created_by encoding Fix mysql type for creation_date column --- ...0230921211523_torrust_add_creation_date_field_to_torrent.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql b/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql index adc688e7..8a286401 100644 --- a/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql +++ b/migrations/mysql/20230921211523_torrust_add_creation_date_field_to_torrent.sql @@ -1 +1 @@ -ALTER TABLE torrust_torrents ADD COLUMN creation_date BIGINT UNSIGNED NULL; +ALTER TABLE torrust_torrents ADD COLUMN creation_date BIGINT NULL; From c3d69f4e8e13b8fec5df747f6995d1c7826095e5 Mon Sep 17 00:00:00 2001 From: MMelchor Date: Fri, 29 Sep 2023 00:23:41 +0200 Subject: [PATCH 011/309] feat: [#303] store in the database the torrent fields creation date created_by encoding Added bindings to the add torrent query in the mysql implementation for the new added fields --- src/databases/mysql.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index c9df5dc4..de2739d3 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -485,6 +485,9 @@ impl Database for Mysql { .bind(root_hash) .bind(torrent.info.source.clone()) .bind(torrent.comment.clone()) + .bind(torrent.creation_date) + .bind(torrent.created_by.clone()) + .bind(torrent.encoding.clone()) .execute(&mut *tx) .await .map(|v| i64::try_from(v.last_insert_id()).expect("last ID is larger than i64")) From c22f661f05091a21b8d4179d625adc9d7e2d6413 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 9 Nov 2023 17:18:53 +0100 Subject: [PATCH 012/309] refactor: [#303] store in the database the torrent fields creation date created by encoding -Added backticks for mysql so the encoding column name is not interpreted as a reserved keyword --- .../20230921211559_torrust_add_encoding_to_torrent.sql | 2 +- .../20230921211559_torrust_add_encoding_to_torrent.sql | 2 +- src/databases/mysql.rs | 8 ++++---- src/databases/sqlite.rs | 8 ++++---- tests/e2e/web/api/v1/contexts/torrent/asserts.rs | 8 -------- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql b/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql index 2250b31c..5ee5af02 100644 --- a/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql +++ b/migrations/mysql/20230921211559_torrust_add_encoding_to_torrent.sql @@ -1 +1 @@ -ALTER TABLE torrust_torrents ADD COLUMN "encoding" TEXT NULL; +ALTER TABLE torrust_torrents ADD COLUMN `encoding` TEXT NULL; diff --git a/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql b/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql index 1bebfd91..d15b9369 100644 --- a/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql +++ b/migrations/sqlite3/20230921211559_torrust_add_encoding_to_torrent.sql @@ -1 +1 @@ -ALTER TABLE "torrust_torrents" ADD COLUMN "encoding" TEXT NULL; \ No newline at end of file +ALTER TABLE "torrust_torrents" ADD COLUMN `encoding` TEXT NULL; \ No newline at end of file diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index de2739d3..dae509d0 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -389,7 +389,7 @@ impl Database for Mysql { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -471,7 +471,7 @@ impl Database for Mysql { date_uploaded, creation_date, created_by, - encoding + `encoding` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(), ?, ?, ?)", ) .bind(uploader_id) @@ -765,7 +765,7 @@ impl Database for Mysql { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -796,7 +796,7 @@ impl Database for Mysql { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 39d8b9b9..434a9048 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -379,7 +379,7 @@ impl Database for Sqlite { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -461,7 +461,7 @@ impl Database for Sqlite { date_uploaded, creation_date, created_by, - encoding + `encoding` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')), ?, ?, ?)", ) .bind(uploader_id) @@ -754,7 +754,7 @@ impl Database for Sqlite { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt @@ -784,7 +784,7 @@ impl Database for Sqlite { tt.comment, tt.creation_date, tt.created_by, - tt.encoding, + tt.`encoding`, CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders, CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers FROM torrust_torrents tt diff --git a/tests/e2e/web/api/v1/contexts/torrent/asserts.rs b/tests/e2e/web/api/v1/contexts/torrent/asserts.rs index 376f9c82..e268c9d6 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/asserts.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/asserts.rs @@ -26,14 +26,6 @@ pub async fn canonical_torrent_for( uploaded_torrent.announce = Some(build_announce_url(&tracker_url, &tracker_key)); uploaded_torrent.announce_list = Some(build_announce_list(&tracker_url, &tracker_key)); - // These fields are not persisted in the database yet. - // See https://github.com/torrust/torrust-index/issues/284 - // They are ignore when the user uploads the torrent. So the stored - // canonical torrent does not contain them. - uploaded_torrent.encoding = None; - uploaded_torrent.creation_date = None; - uploaded_torrent.created_by = None; - uploaded_torrent } From c535652373698c444512ca21222849563c83f314 Mon Sep 17 00:00:00 2001 From: pcarles Date: Sun, 12 Nov 2023 02:03:01 +0100 Subject: [PATCH 013/309] fix: rework error handling for add_category Fixes #253 --- src/databases/database.rs | 1 - src/databases/mysql.rs | 12 +----------- src/databases/sqlite.rs | 12 +----------- src/errors.rs | 1 - src/services/category.rs | 11 ++++++++--- .../from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs | 11 +---------- 6 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/databases/database.rs b/src/databases/database.rs index 0d6e8c3e..18b2a56a 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -81,7 +81,6 @@ pub enum Error { UsernameTaken, EmailTaken, UserNotFound, - CategoryAlreadyExists, CategoryNotFound, TagAlreadyExists, TagNotFound, diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index dae509d0..8fb0ce12 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -249,17 +249,7 @@ impl Database for Mysql { .execute(&self.pool) .await .map(|v| i64::try_from(v.last_insert_id()).expect("last ID is larger than i64")) - .map_err(|e| match e { - sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); - if err.message().contains("Duplicate entry") && err.message().contains("name") { - database::Error::CategoryAlreadyExists - } else { - database::Error::Error - } - } - _ => database::Error::Error, - }) + .map_err(|_| database::Error::Error) } async fn get_category_from_id(&self, category_id: i64) -> Result { diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 434a9048..4e955151 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -239,17 +239,7 @@ impl Database for Sqlite { .execute(&self.pool) .await .map(|v| v.last_insert_rowid()) - .map_err(|e| match e { - sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); - if err.message().contains("UNIQUE") && err.message().contains("name") { - database::Error::CategoryAlreadyExists - } else { - database::Error::Error - } - } - _ => database::Error::Error, - }) + .map_err(|_| database::Error::Error) } async fn get_category_from_id(&self, category_id: i64) -> Result { diff --git a/src/errors.rs b/src/errors.rs index c92a0361..24a9661e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -288,7 +288,6 @@ pub fn map_database_error_to_service_error(error: &database::Error) -> ServiceEr database::Error::UsernameTaken => ServiceError::UsernameTaken, database::Error::EmailTaken => ServiceError::EmailTaken, database::Error::UserNotFound => ServiceError::UserNotFound, - database::Error::CategoryAlreadyExists => ServiceError::CategoryAlreadyExists, database::Error::CategoryNotFound => ServiceError::InvalidCategory, database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists, database::Error::TagNotFound => ServiceError::InvalidTag, diff --git a/src/services/category.rs b/src/services/category.rs index 5abe8aa6..b2a6f733 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -44,10 +44,15 @@ impl Service { return Err(ServiceError::CategoryNameEmpty); } - match self.category_repository.add(trimmed_name).await { - Ok(id) => Ok(id), + // Try to get the category by name to check if it already exists + match self.category_repository.get_by_name(trimmed_name).await { + // Return ServiceError::CategoryAlreadyExists when the category already exists + Ok(_) => Err(ServiceError::CategoryAlreadyExists), Err(e) => match e { - DatabaseError::CategoryAlreadyExists => Err(ServiceError::CategoryAlreadyExists), + DatabaseError::CategoryNotFound => match self.category_repository.add(trimmed_name).await { + Ok(id) => Ok(id), + Err(_) => Err(ServiceError::DatabaseError), + }, _ => Err(ServiceError::DatabaseError), }, } diff --git a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs index 37a06d5e..cbaed29f 100644 --- a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs +++ b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs @@ -116,16 +116,7 @@ impl SqliteDatabaseV2_0_0 { .execute(&self.pool) .await .map(|v| v.last_insert_rowid()) - .map_err(|e| match e { - sqlx::Error::Database(err) => { - if err.message().contains("UNIQUE") && err.message().contains("name") { - database::Error::CategoryAlreadyExists - } else { - database::Error::Error - } - } - _ => database::Error::Error, - }) + .map_err(|_| database::Error::Error) } pub async fn insert_category(&self, category: &CategoryRecordV2) -> Result { From 5f96ccecdba379479010f1c94e4d1382a77cfad8 Mon Sep 17 00:00:00 2001 From: pcarles Date: Sun, 12 Nov 2023 02:25:56 +0100 Subject: [PATCH 014/309] fix: update doc --- src/services/category.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/category.rs b/src/services/category.rs index b2a6f733..ca6bd933 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -28,6 +28,8 @@ impl Service { /// It returns an error if: /// /// * The user does not have the required permissions. + /// * The category name is empty. + /// * The category already exists. /// * There is a database error. pub async fn add_category(&self, category_name: &str, user_id: &UserId) -> Result { let user = self.user_repository.get_compact(user_id).await?; From 5dc001771e24adee7d8461d5a9f801ff8c1ef45d Mon Sep 17 00:00:00 2001 From: pcarles Date: Sun, 12 Nov 2023 02:38:44 +0100 Subject: [PATCH 015/309] fix: add comments --- src/services/category.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/category.rs b/src/services/category.rs index ca6bd933..ec3e5ca2 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -48,9 +48,10 @@ impl Service { // Try to get the category by name to check if it already exists match self.category_repository.get_by_name(trimmed_name).await { - // Return ServiceError::CategoryAlreadyExists when the category already exists + // Return ServiceError::CategoryAlreadyExists if the category exists Ok(_) => Err(ServiceError::CategoryAlreadyExists), Err(e) => match e { + // Otherwise try to create it DatabaseError::CategoryNotFound => match self.category_repository.add(trimmed_name).await { Ok(id) => Ok(id), Err(_) => Err(ServiceError::DatabaseError), From f575b273bfa5d2fcfa4f53a0b1d6e66fb933caba Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 13 Nov 2023 09:33:41 +0000 Subject: [PATCH 016/309] chore: update dependencies --- Cargo.lock | 184 +++++++++++++++++++++++++---------------------------- 1 file changed, 87 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54241163..8c94b306 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,7 +140,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", "serde", @@ -337,11 +337,10 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ - "jobserver", "libc", ] @@ -451,9 +450,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" @@ -615,9 +614,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ "libc", "windows-sys", @@ -648,9 +647,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" dependencies = [ "simd-adler32", ] @@ -805,7 +804,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -850,9 +849,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -997,9 +996,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" dependencies = [ "bytes", "fnv", @@ -1134,9 +1133,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.2", @@ -1174,15 +1173,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.1.22" @@ -1191,9 +1181,9 @@ checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -1275,9 +1265,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" @@ -1304,9 +1294,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -1542,9 +1532,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1563,7 +1553,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1574,9 +1564,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -1708,7 +1698,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1745,7 +1735,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1975,9 +1965,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.36" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" dependencies = [ "bytemuck", ] @@ -2088,9 +2078,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64 0.21.5", ] @@ -2207,9 +2197,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -2235,13 +2225,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2330,9 +2320,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -2382,9 +2372,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" @@ -2477,7 +2467,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.2", + "indexmap 2.1.0", "log", "memchr", "native-tls", @@ -2694,9 +2684,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -2803,7 +2793,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2876,9 +2866,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -2894,13 +2884,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2959,9 +2949,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -2980,11 +2970,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -3008,7 +2998,7 @@ dependencies = [ "futures", "hex", "hyper", - "indexmap 2.0.2", + "indexmap 2.1.0", "jsonwebtoken", "lazy_static", "lettre", @@ -3031,7 +3021,7 @@ dependencies = [ "text-to-png", "thiserror", "tokio", - "toml 0.8.6", + "toml 0.8.8", "torrust-index-located-error", "tower-http", "urlencoding", @@ -3116,7 +3106,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3372,9 +3362,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3382,24 +3372,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -3409,9 +3399,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3419,28 +3409,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -3579,9 +3569,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.17" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -3625,22 +3615,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.15" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.15" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3675,4 +3665,4 @@ checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", "pkg-config", -] \ No newline at end of file +] From f35a06788d3458faf77a1219fa3c2087de5f247a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 13 Nov 2023 13:03:06 +0000 Subject: [PATCH 017/309] fix: [#342] reestablish E2E Tests --- .dockerignore | 4 +-- .github/workflows/testing.yaml | 34 +++++++++++++++++++ compose.yaml | 5 ++- .../container/e2e/mysql/e2e-env-down.sh | 6 ++++ .../container/e2e/mysql/e2e-env-reset.sh | 4 +-- .../container/e2e/mysql/e2e-env-restart.sh | 4 +-- .../container/e2e/mysql/e2e-env-up.sh | 17 ++++++---- .../dev-tools/container/e2e/run-e2e-tests.sh | 15 ++++---- .../container/e2e/sqlite/e2e-env-down.sh | 5 +++ .../container/e2e/sqlite/e2e-env-reset.sh | 4 +-- .../container/e2e/sqlite/e2e-env-restart.sh | 4 +-- .../container/e2e/sqlite/e2e-env-up.sh | 18 +++++----- contrib/dev-tools/container/install.sh | 4 +-- contrib/dev-tools/init/install-local.sh | 2 +- 14 files changed, 90 insertions(+), 36 deletions(-) create mode 100755 contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh create mode 100755 contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh diff --git a/.dockerignore b/.dockerignore index b67eebd8..a084bf6b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,13 +10,13 @@ /config-tracker.local.toml /config.local.toml /config.toml +/contrib/dev-tools/container/ /cspell.json /data_v2.db* /data.db /data.db* -/docker/ /project-words.txt /README.md /rustfmt.toml /storage/ -/target/ +/target/ \ No newline at end of file diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 5ae97af4..d64e9110 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -132,3 +132,37 @@ jobs: - id: coverage name: Generate Coverage Report run: cargo llvm-cov nextest --tests --benches --examples --workspace --all-targets --all-features + + integration: + name: Integrations + runs-on: ubuntu-latest + needs: check + + strategy: + matrix: + toolchain: [stable, nightly] + + steps: + - id: checkout + name: Checkout Repository + uses: actions/checkout@v4 + + - id: setup + name: Setup Toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.toolchain }} + components: llvm-tools-preview + + - id: cache + name: Enable Job Cache + uses: Swatinem/rust-cache@v2 + + # Temporary Cleaning to avoid Rust Compiler Bug + - id: clean + name: Make Build Clean + run: cargo clean + + - id: test + name: Run Integration Tests + run: ./contrib/dev-tools/container/e2e/run-e2e-tests.sh diff --git a/compose.yaml b/compose.yaml index df6cc286..a7f9e881 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,7 +2,10 @@ name: torrust services: index: - image: torrust-index:release + build: + context: . + dockerfile: ./Containerfile + target: debug tty: true environment: - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh new file mode 100755 index 00000000..9db1ca2f --- /dev/null +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.mysql.toml) \ + docker compose down + diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh index afe138ac..75408e4d 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh @@ -2,7 +2,7 @@ # Delete the databases and recreate them. -docker compose down +./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh # Index @@ -29,4 +29,4 @@ if ! [ -f "./storage/tracker/lib/database/torrust_tracker_e2e_testing.db" ]; the sqlite3 ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db "VACUUM;" fi -./docker/bin/e2e/mysql/e2e-env-up.sh +./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh index 92088547..48163040 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh @@ -1,4 +1,4 @@ #!/bin/bash -docker compose down -./docker/bin/e2e/mysql/e2e-env-up.sh +./contrib/dev-tools/container/e2e/mysql/e2e-env-downp.sh +./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index 9b83c782..5124e5dc 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -1,13 +1,16 @@ #!/bin/bash -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ docker compose build -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ - TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.mysql.local.toml) \ +USER_ID=${USER_ID:-1000} \ + # Index + TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ + TORRUST_INDEX_DATABASE_DRIVER="mysql" \ + TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_IDX_BACK_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG=$(cat config-tracker.local.toml) \ - TORRUST_TRACKER_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-mysql} \ - TORRUST_TRACKER_API_ADMIN_TOKEN=${TORRUST_TRACKER_API_ADMIN_TOKEN:-MyAccessToken} \ + # Tracker + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.mysql.toml) \ + TORRUST_TRACKER_DATABASE_DRIVER="mysql" \ + TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ docker compose up -d - diff --git a/contrib/dev-tools/container/e2e/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/run-e2e-tests.sh index cca2640a..04c3b679 100755 --- a/contrib/dev-tools/container/e2e/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/run-e2e-tests.sh @@ -42,13 +42,13 @@ cargo install imdl || exit 1 # Install app (no docker) that will run the test suite against the E2E testing # environment (in docker). cp .env.local .env || exit 1 -./bin/install.sh || exit 1 +./contrib/dev-tools/init/install-local.sh || exit 1 # TEST USING SQLITE echo "Running E2E tests using SQLite ..." # Start E2E testing environment -./docker/bin/e2e/sqlite/e2e-env-up.sh || exit 1 +./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 wait_for_container_to_be_healthy torrust-mysql-1 10 3 # todo: implement healthchecks for tracker and index and wait until they are healthy @@ -60,16 +60,16 @@ sleep 20s docker ps # Run E2E tests with shared app instance -TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./config-idx-back.sqlite.local.toml" cargo test || exit 1 +TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 # Stop E2E testing environment -docker compose down +./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 # TEST USING MYSQL echo "Running E2E tests using MySQL ..." # Start E2E testing environment -./docker/bin/e2e/mysql/e2e-env-up.sh || exit 1 +./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh || exit 1 wait_for_container_to_be_healthy torrust-mysql-1 10 3 # todo: implement healthchecks for tracker and index and wait until they are healthy @@ -91,7 +91,8 @@ echo "Creating MySQL database $MYSQL_DATABASE for for E2E testing ..." mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" # Run E2E tests with shared app instance -TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./config-idx-back.mysql.local.toml" cargo test || exit 1 +TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./share/default/config/index.container.mysql.toml" cargo test || exit 1 # Stop E2E testing environment -docker compose down +./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 + diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh new file mode 100755 index 00000000..1a2aebed --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ + docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh index f0ff3a2d..e5890ac1 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh @@ -2,7 +2,7 @@ # Delete the databases and recreate them. -docker compose down +./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh rm -f ./storage/database/torrust_index_e2e_testing.db rm -f ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db @@ -20,4 +20,4 @@ if ! [ -f "./storage/tracker/lib/database/torrust_tracker_e2e_testing.db" ]; the sqlite3 ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db "VACUUM;" fi -./docker/bin/e2e/sqlite/e2e-env-up.sh +./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh index 768f50cb..7a9e55d2 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh @@ -1,4 +1,4 @@ #!/bin/bash -docker compose down -./docker/bin/e2e/sqlite/e2e-env-up.sh +./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index b55cd564..e5c67632 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -1,13 +1,15 @@ #!/bin/bash -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ docker compose build -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ - TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.sqlite.local.toml) \ - TORRUST_IDX_BACK_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG=$(cat config-tracker.local.toml) \ - TORRUST_TRACKER_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-sqlite3} \ - TORRUST_TRACKER_API_ADMIN_TOKEN=${TORRUST_TRACKER_API_ADMIN_TOKEN:-MyAccessToken} \ +USER_ID=${USER_ID:-1000} \ + # Index + TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ + TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ + TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ + # Tracker + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ + TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ + TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ docker compose up -d - diff --git a/contrib/dev-tools/container/install.sh b/contrib/dev-tools/container/install.sh index a5896937..5493b7c0 100755 --- a/contrib/dev-tools/container/install.sh +++ b/contrib/dev-tools/container/install.sh @@ -1,4 +1,4 @@ #!/bin/bash -./docker/bin/build.sh -./bin/install.sh +./contrib/dev-tools/container/e2e/bin/build.sh +./contrib/dev-tools/init/install-local.sh diff --git a/contrib/dev-tools/init/install-local.sh b/contrib/dev-tools/init/install-local.sh index 3396c047..2368de3b 100755 --- a/contrib/dev-tools/init/install-local.sh +++ b/contrib/dev-tools/init/install-local.sh @@ -8,5 +8,5 @@ mkdir -p ./storage/index/lib/database # Generate the sqlite database if it does not exist if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then # todo: it should get the path from tracker.toml and only do it when we use sqlite - sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" + sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" fi From 61af32daa80af55562faab72c2f17c0862d6d227 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 13 Nov 2023 13:38:16 +0000 Subject: [PATCH 018/309] fix: [#343] Torrust architecture image --- README.md | 4 +++- docs/images/torrust-index-architecture.jpg | Bin 0 -> 55630 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/images/torrust-index-architecture.jpg diff --git a/README.md b/README.md index 50030d01..c1a9b10c 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,10 @@ The core purpose of a [BitTorrent][bittorrent] Index is to maintain a database t The __Torrust Index__ serves a [high-level api][api] for our [Torrust Index GUI][gui] client. It also connects to the [management api][api_tracker] of our [Torrust Tracker][tracker], to provide statistic and whitelisting functionally. -## Key Features +![Torrust Index Architecture](./docs/images/torrust-index-architecture.jpg) +## Key Features +** - [x] High Quality and Modern Rust Codebase. - [x] [Documentation][docs] Generated from Code Comments. - [x] [Comprehensive Suit][coverage] of Unit and Functional Tests. diff --git a/docs/images/torrust-index-architecture.jpg b/docs/images/torrust-index-architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6baf696acc1c437560e6de659d9ed9f95d1b102 GIT binary patch literal 55630 zcmeFZ2UJr_*FPKudj$jpL9SE@?b17_H0cHsLPrTTp;zgaD^d&y7!VMU4haMaB=n-v zo3wy*rFW!v_=5MU_`Kftx$pO1-+!%leb2DS%sEreo;hds?AiObXaD2=C%{D&MP)_6 zks|=W5z-%EfB48pWjQ&MJ6f8G%4!OKX4C;lf&6y>z}C*iNlWS0Z@PN=zn%E>XN`lr zdoX8*gZOU(sok}%gVq6n4xYa$^WWV*WoGUSBNg~U`e$__J)A`BB~tp5<)7*62Witk z(^3a%cNYg2QkgpkX(zCj94T!^N?*4;lr}w-hB-JLlz&DlBV}vjdho1+a6s`{b32GO zDLzU1X8E74o$VtCPEi3?lrE~z`oE`u`)d~QdHu#!H`uQ8$ej{Zukm#}}{aFHR z0Oo+-0LlP601UuON(lgd2k-+#_WJ;G0J5V8;oz5y6v&U0AB2;~j~_dJ>g4Iur%s+a zb^7duv!~CTKXdBTIf`@VFI>Dxaq;w7N-D~WRHXF9gGP=V)FdN6Nve49%&9Y^yZ^(m zUkRW%c_i}`H`x(pz)^}LWE4mCs{o9oZ|w+xjP!HlTX#*(t!0qh#dAj-Q}7NqJ2SPesejuj?LliRQA1=uOZ)7gx8ueBv0j0MrrH z$s&A9>tR?Vq2rXGj?<$TiMgyorq1CJZS8F0+Bi9wzr2~nN=V?ve29bIg)^m*(uOZ0QBFCnls?et%Zt z*SE#!mZw)c`ga^_=yAt2^?GH8FR0`foIX(`o~(BdVsdQmOUxP?Utiu>3}o;8FC8GK ze~*=zipd_VAaA{LtmcDl`2cqq1Tw*;%H5Kb6^UAKF-~;n5KM$JJ4>2@r|$6mhIFqS zj;Jr7UJ2f~zwoe{Bf^eriuTJF?$`gx_}@1q9%0iV@ zT@9XTdRcR#J^Fy}FO-3i;L*ig2sP+N(t|@^&{1>D76$bR+AmA}&VgPkZSf^UM_c~k_v97 zimEC9LjC&C3;W_7H!uBoT zHUfz>X^!F!$&boIpK|Nm2AbNm$(A#x3o-;`l<<_eOoA65BL1&@J{phpg*|&R#4PAC z>3Pg+rvxRRK#F6EpZ;#}csP=tJ_rwa+Ub~tH&DwSiW7k*&6aTXPTOTq=$0qx3Uz1- zwLb|A67u3j&u@V*OqOaEhGSR5h?(q!+4w)1`d zq<8Z_8KkxHf7y^MPgr-^8YFkxD3Ny}>2M>h=F45$?Om;7jgH>-=_%ofebv)qZ6GZn zyPc`cx)R=oV=0tz>cr9++D_HuG1lWTcg&%x#Q5@+b)8Y!@R3LKdYMU1%vCQ{Ju^SFZ41kGZWFJLpg$5{%=EDZSV!2)#~ln!#wG`(x0$md%|+5 zTTCc8kojp0}Xh%;__WxB9iq{ zze_GdCc7|9CB|LYzP8Grdq&s450I@EdVdMqEU1dP&f{`U!+i-e8mYwF6vWQ)gf-MD zS-&Q!s0A#4Ght#Q?WHNIC(YW`{22COX3HMLJG^eJCrrNTOG?;07@^w<6>kU#P;)p- z#??8CeQJO&?%XAH5I+Pi?)vHosxW<5f z&t)yZ;F(>Ar|Es7-jOQ7`ef>}lAt$jL8=HCtp1swh~y|VD>%)g*b$pT!zllzYI1J< z6g@`MAssUAT3(br6&^jau;*geGus0Kz128=;eXQnr=Q3FsPMa-{Id})$M5x_p1eRh zVCKkJmmj`#8@o$$pbYqT;i&NY4+LAg5(1)xLym!LoZPrhEHx4!acgd4P4nu>SSV=!PBvMOQxtnHYyk)2tb@!*r zYJ~~Y9T=0FIYW0}_TG!{m^Be;-_=$#q1x4VMNA5B&r)4c6YTW~9`7|lTQPps>yVyz zvMb*!Z<49Kq+s25Sm1oYhGuMkXg&{%zI4yZg(;)H?FvV_69zjR&GSmVG|#zT{~d{k z?vsY7Y)FV}LUkViYatC$wpY0ov?Xe<_SNy5UE3#@y~5U>q~OA`9%rvnjIXoIol!Rb zR-5z^LKh?7cr+F5hCjoFVHy0M;UBk{f0%t?e)uJPEB-%@k31n}>*J;rA|sQM9;Ge& zmVTIK@MB8sLGZ2y#ioFgR)jO+FNK#>A_K5a8)5o*jE>QDTze<@4tUVMJ9<|xbYw-6 z$9ynX+a*ZI*dI^zE*yHF;w$&zlS?U9^~4XN@dzEvRE`AF<8h%f>y>QA5F|93r}N}| zWK}S(T;a>Or?X@-D*Jf&6wh?GCz?b~PFm@-@c8-9tk3Q8-6VvK%?j?Y6dO?THf++q zkDh9p578mK`P$LTjC$>G9+jlTLWB7{*ruH<7dm&=9~F?-HvuM1A5XSN@?XrK44&z0 zEdes8pHRkI6acZ)Bl8y zOH8m1K$)=O#NEt5{8F5(UMTu5Vd@sABxZW%BdI!^s~>n1jPS?ZAa_Q(8?)2T z)bE_^yA)l>{f%RjJAPh|m>0%aNvlddHO*_y8-A2y0GK2;m_ zc01W0%Vwm-@tE!HoH1(3=A5zOeZv-m2j9t3bA>6-)A76u2IY@W9eWDm=9iH4d8df( z=;tJQ#MxsJo@QyD+xvjHD+DjnXtr_2PqE?e5qsgRg*ro`}ocTeB8 zR@P6IPeuKT$5{st`(ZS)eU4Z7Jc3#Kduir+_o=R`lNJ5-%p>$&p>958TTr*Xg?`1G zUHgDjf3hZoQi;sNU9J*8_j#7zX8iuxEUVkT)&A2e@V8_m;oT+UPrRr6W<457LzLs* ze0sBeAMkPcL7l|Vy{q<4GOhU}7XI9|{GHU`s86Ex500u>=(s1)bwNBz76&2~Im`$j z{O$8d87EFJd*s6iF(s>e6RLKE)?3p-=;BO+OTWu^4t((H&p^UCkl~92t zdc>;BmnAk6rZm_;Trtfb!FPL54eMEEKE!Obv+gQ!l_gwU+HhVJ`#2cS-f<7C7Pc9t=e!R=$Vqcc`@fR z3o!Cag0}xC*D)1-DKAhrPNb;MWfpbji%X_0K-IyQ)#bfVS#1CW(w{4ZR_J-T2D!G*1ymSB?Y>{1+A9*4_iT#znFoAb199iJUF zegS67?y%KudpV$&?4e)*3wi%p@=OEQ0C!vMPNPqYf*uQ4Ufu>G-6g!}Toren7GRp+ z2<2x4Ct+NXL>Ey6IPlnDwaeqqZZ5-y*c8``!1;k4TTfu_RC}$sgnGu`e-&hBF#34& z_Fx_!1%+eke6mEaGWCMP}#8M%lgvOvi^a1qe*xC(<*i{JWF(&FB zorosJcgnvFSk1b?OY7}kE4dI@jZjN`PX#-o2N<$#?|!HBcZs}|PJNVuoI}BYMeS0* zFBAgnImbm|SwI>+a{r(a}v_zV^#%g#dEL@$8)k)N+ z+YHLv7jy9&?c|6*DjYX`qs+@Hj7d!Ll<&yWE^nikZ|MOql3K6P(F`A6v%MZgS7+z; zwBOZ$p+HB%AFJY;nSxqMd44+!Jb~Qi5Ttd())#Y5-T*9xo-UqJ4s?LrBW7DRVY{hA z2xH_#VtBIy787QNy7=~tC{&rNyAo#5fn&ZOCIP%mOYz2%&YXq?k`$8{)aYmvnue`& zM5QIUvrtcmM2AZ=Llm*~96=|EWzomW_Wd)Zhe^E z4GoqpN{3@ZHQf7++)cf;%q+~p_ip)IL)JrMzb zNh2~TQkP9qF2(nS z2KRFDGSrY1g}$xx@(kLOu$Q?0rLB^)wnhoOaCDyvm`m<8eWlV89QK=Orlw;Bo*w|W5 zMI9>dm$-gsP*~K>A8oqv;7&5?uN%fq~(`sy(SDFn93W2rdz+z&D z?J-;S&h_m*ai5&u%O#ldh9@;@GcQ%^Q4!|Xrcs)VQj?wV?O9JH7Ej%P#M{SV>iDD0 znH#ppVPWsIXI`0sx#zH*xVt1lVQ8}=P&+xfoo3vK|iC3ZA)eh544bg7u za2&|K0L=@@?NF%LS^{$HDCfU2a9Eo%IhTGX)=zh!VFE@d?rRj?pjf%nK-_zBwEW~g z02ieJlFWKpla7n_6G`p0_np_+Rh?Gf2Ym9mUWofVjK*=f+NIyo1E#yPI*eW-(pVM^ zHTM$4qWpqujyW)$22OLKSRrfEb->JYdnxUk%rc?{pAiv>+N(?s--q|l#GZIFoal_yZ~K6LKd+Ig zi7k(iz0Sw`00(+z;dd7bsUs#N7Z6>SWWE-J{aH{`?B@DWocDy!oM+{Mgt&ZRQ{lQb z?p4FP0mDplRm4wh(Mv()#IhVp)%rvx{8c&5>($ z<3;A3l$Aa>PZ74`;#zua%M8#q>#;teS!Guin6QwbsU`fu}lWKpl6|T#Ux_k68_>l+#E!sS~oQmMN!r+15toD6DAc3 zrjNh;q@NmC|JbsSDBN4ilI+=5W^_e=vfE8O1gSsHf02kYEF7)JNDl-iB~yzkfETY{wQhSR3$`!b%-NWG91!fEFf`I^ zUZidv%tYu`hT6u6@Gz>#pha(I-Cnb3HrCmYVe``%Ur%(NI_~et>01m(=^#9t7QIqk zyR0XzGq#cPZpHo!{h4i-iC%`)CdWj_6sCrp8qwaORw4QQoV;5R5891*!Tjr&c`3k} zN;-90-scxIHb!+;u+2~)`%GJVdLQ-Y8O1M1nR06j&c_`FxvwJWgS^TZT&XZKbW%nQ z##4gvI7}S<(z5e1^L-THlLH1((6eFDj+QiMb@@_qJ|8R|R$!)Qo)V&Wwc<+rdJ%L8 z3Pj0e)i{zA7s%dR2B*-@$XLd+G`**NCpRULQv3w5o~Bi(qy;{+1O;)Mf@tRfe#lUc zgK;$Ob4=GZ(K*wG0y@qEDp2qbXxHSoeqaf>ehhTW{T`wc=IIZWzBgi+DZ{^}T=T;9LAPuaY!`L8Lk1^7T=Is^Mm+qk*!)*&4qvl? z;~eeEZRU0}b@Xok(DeRP`G~+Tl^Oirnyc2toY&n8Z`5pm|6skw7i;aC`P%)rzuITf zZ=3({+D~PEXka^lq=EP>^Hl?Zv|6`pl}&ZGpWm%AW3>O0AX>y^rGMimWzS&>52q=z z_1C>ybK>}RL&Zr0grDC`G?6gbzUn|y6VI}IRTGCdDB{i+d{p|XNoM=5xINi@UbaU6 z?S#O@nu-8P!+uEJ@hb&AllX*x3>^?|VFr>XdWTEbk$^z4XmrjkW{&a`b1jNVMI;MJ zEIOxDiLI+C6oi?!k&=excNcP6_y`-rT%AYWXw?yW<{n3EsQ!TjgLU3FQu4{A!B^;d z>0`TTpr-?)r4@4IGtFHn+-b|{vxu`s@nfDgli3-gI!VGH;uYTMupF)&LZ3OI(LOC- zZ*HYiNXSv-G#qXfd+AP1XHOQ4J^%@4CIy=Z%2S1iTBlQ#W7!_U6WYAz-6f|MsJL~k zx;Jz6hrwxM>JfI+JtkkuR8r&XEo!qD^=)W^xXjOT=7T$3X8qYYb|d*IsJ1SSKRB`2 zjOfks0yR6?WMmctnOey)z|YRNoQmu17AiTpqXJ*9Ni2jkvc;Eqgn{}#+@2iVZG%u> z#VTk#QP6nw6gfgrlVd6{{YWHS@j-W}GE!4>bLL(17fIHw+Pxj%cH{7yydXKzT_k2r zj21o~S?yK)Sw9k^=|<4AV$uVthbz$TUQN2pGGzjhyOJ1TCO;o`YXayjt}z%M91sVC zcbZX$w9V?dq`YvF0aU5C;)tc)Gm8eN@L`iY&As?N{u#a*b&-J%s3Q>@xEGAb$^=*e8DS3#4M z1`Y^!Fcu)u@61-ae1?r(IoQ~0o=`mQt^#jRj^)-W@QJeIP`Z_rl#ZmPrcFIx7j7W| z3mb+ulxenBJ&Ioi@uBVSH+8sQvAdnn0NGO)wyXcti5GE75;5~~)3Fs=xU)E?UK1S- zn*mV@SYMyXfRD^W+@|Zgtux^)i?v*gt(n7MuoDp9?BxII&^sdc0%g-QD|gx0tK!g# z-7um1yS_>$wy|mPpS4hzzG#bB+)qIUZ1!il%|W@SK_1$+Fdvh z^Zax8vHPpIRr&hlOk3f1;Sw*ybqAX2DZ)v}55s_rxdMeLxjeF}S1KbfSJR*M8wd~? zZ!~6 za>hBH4`20Wtty`tNTCmHM zSI(Hpj0TYhj`TEnazVH5Oy`~x=MC0N&(hmMJI!g-!gbbQR@vSvq_`xnQ{xaH1;VFwHcx)Z7U>|$qK z6Hc}Db3KSNvkS67(HSKzJNUGJc8yuw1QBUDI@<-@d2ac7<~W<8=Szwp5D0j+%OrKn zI)m?#;kL%&E7VG6{`pdU&KVlkA)gprZhx8Vk-AvGTOdF56Y4U=-w>|DYM&?5cP7KI z%1NC54VPC;x&ZTB3aAWw*3)NS_)QFct~M2Z%@z1OLFR+aIXHxiOo*a7^@SgoJiv9>CQ3^iwwQ|c&cs{7vwtDfOC zLVl*d7jA;XfUH$l9OND8?d6 z=Z^j)eb71vvJf_J6n#le^R)8SF-1I5!I`vI{G;~3Jf-=~JQeIB|bq>`v3_-@?EzvISix1sZ`m5Hn6t&Iv!_+gzOV1spR{&P)62DNZej?!#r2zDF)bQydQ{Gbu+g1Oa`tH|G{YP!~zgTT{RwR9TPdH`uva5)a zoO7m|Z3JjIay2PPGzH>%eHAt~;pJhfF!nRXg6 z;7^Dn3az??4B&)Imf0I|_{ofnY$s+s2n3?O^l)Uy+f9yl}21$m2v0}{H_!JL!uXo zGyD3=6atM3b&0jfB)3!VC{8qNSTfL{3m#ZLv#aDl(4Dj_>{8vSyN5Jh-3PoQ{_ZUS zgL>)23-UBtcS+?)X1ZznB8rX9DX8k_wy&%#byR-jY?C?ZzTSD-h_ur#4msmf9!{^X zuN!;z;;kw(N5xKg1brvTQbW@4Nl;we{zl}lVk8_NGLj~Ksr1%e!lvc?MI86w2Q?z3 zq<2JftKUYmB`ei#-gIL$kcn1hUB-ZO2dtou5>+5W+7$II==7>qrxEBI=r>q0Q!}1S zPL2o8De=!axo|jY?ca>dv{St39QL=R9gOk`Ic|5*<#-QYOP9NqLfqVD@WPWY`P_W%rFS}s|k-vS%_Rh#wU}#Nu|J*arEb8aLC0zNF z`KicAg?#{A)UI|mBWU{WH=2(cRq42!SFH?=g@Ogp<-5^ZogW>yh0j{^m^a|846AI^ zAo63TlJUw688!UWE#S$af*)8CfAoLBk}&^_CGlkZW-;?DFj75aF#~_ImK$&_#@v)|q?6)PAz9YawXB70aK+gE zPj9B#{MkYtm6IS0hSsRRAPniA8&S(iZXYaFfRecX(Zcu@A0f_G6RagPIPn!9!RSI zKGX3o#9~80^eam2(?K`LB>x2ymyzR%H{QJZUomllJNtl39cQ&lgqnG?UgKMZsTXuG zLoEFz0@q)XushNXNCZ8}N&uob6>@-wOZbL|WBUsax2CH*+QS7T;aPYuz;|pPqO-nZ z=l)^Y!UKX2!i$N4yFv-0dQ6=i)TW1);A6Uf<{!eK-6L@&;P#DrsZSP+MY{hGOW?3P z7i&;D4t7(>B7r)t`s5+pUp0i(;eXZ)`~<|%62{0Z6w@xFNa}F*1I_WDK{5YV2n=f_ z-E+>HZn8Zfq~Huowh2d#Oug7o+K_Mc|201P1B7P&0;%O%UA&oBu$KC>Dus%dV=I5U zyA`qikzM4aW&Sp8bAAQ5gSKiPpcALRb^-WlAhFwjgVy*73G%OWe!D($7>YpxS73}6VyKr>_W`*RM-E{zQV_q* z`26AV74M;UV_AvbuclvSbG&vRunoMRI3OEL!T`*(?gQqujH+5fj8~(M7PNg{MB8A`b^gUGJi*Tr9lhj7$V=?$2iMYJACLZglNGNH(@xB6zCwkPpo+48Q0ZV>73a(NZk)=0WJdA> z)?&`hoh#*EVg|MZ%%R){h2dNUrDsiqHJ;wIwxjn;7n|4UD{H|O&$$*^gtrqr4XG!4 zB(sbYqryPXkW?OwO`S?B&Nfjp3}G}QStm1#c~krM>J_#4rD)H;y_S>qo*&~Ac_tMx zFYI`y@*^xVbq-;~j4wMmrYPzYF-Ue{=6#@St1;cwQInDrIp;~A-Oc-!DW`mA57e)R zudkMhMeH8?9RHkc`wjX5D~hDCrO;{6!_0{J3#KtXEdNoCF zR~tM~wg>mri>aV`kr#%vuteiaZd#>$NwibGpXE$jl@n5{|IDN-n0->O^4pyKbsUM1 zD4?%%Avs#I7>^Td9&u57?M@iXx0tiM99xe;A#O6~1WJakq^iAK6qggWzo73wZBz!4 z3)ShLg7{1a>2@Wt?E`A5RM^lX#|FEV+|q^$yE*%MkFB1tgq4!AWL4%c#ixV(vI#;$y+bqWAfln zQzBViW3KpKpA3-<$6fi_5yZS(jkM0`I%*F+lUwU9jjGK10Q{8#*cw>D_4mr@mIx3W zLVd1gz%`bsF?W*@5^5%tr4TsxGGWvhk`oXv!#=-u3 z+2s%pmT3I1I9S%d!@=6?%w*dCz`+K6!@<`45(ismfK5Gwg9U!W!P5U%9PEz|gNhN_ zj__A?O6v3xE7txfOj%7?ebWJj&hI{LZft2V6EhgiIato7d7i^&%_S+hIAp_HQ}nGd z{o*>%g)27Wth@=5!m9E5QQ;w~z#;b76DgPX0nUtzVsT5>Z#-{?t_w5(knUB(?)7PJ zr;f|^*LrJkc*odWeGc1g!aZQQ91>*_UGA)j(O=cH4De2;VN0C z;I0x^`G~M9RJ0cC(-S;D6no_9GBZa~_5sXwducB*l&`k4jz{bSvuwrO1y+9T9XaBO zi4`5ktF!M5xXzG_70}+}$`fS2&oz9t2LBjGH2!C^qO0{cJ%=@l^t@Fx`}Ur_fu?$=e+|+LX% zBXc2f?HkQMtq9uRSA@S_*zfu|Df#qK$~PSnOEg2xU+!ShwM)R;ZA2Opir$F}Y8G>f z>TNhJAJ+jwpCy8q!27%CWM?x=-=ul%0yBx%_{xottwg2xeQXRJRCJo~9xm>V|LC&i zbF;l0YDTmDq$b|tp6hOPSi3|VPI$UNeG>(bZo~oa^A2by`^i%pzR&84-lc8uE$%!) zwZwFpDW!~Mug1`KiRt-9r$Un1@8cJEd<4yepo_v%p*mQL?I; zbr7dSWVaZ#UkCYxSM?aK=g8@H^M#t-`HVPU*lta9%{g8@8$RawBs{r-8J6Gf#Pu>b z4~z)tC{;!{bnDdvbXjc9gjEe)zr z$BdFNDAYxgWg&^vB{^;cX|EvWmHA1@X9;T`=8_HgbdkLYl*1>krdA`+bi;Wzy%L#0SXhClE+=~}wZ-IRr zC7Bbw8Xh>`JQdCz>o2;6K85vQe~y=Z`bbFzJrck#yK}q0q#j}q_jRT=;y$O|n}ejz zDlotb(R!XnWTOeE8-R5rL$J0cv+|`jX^LhqhJm)`1AXfx_>TCW!@>5018PCOi^_jZ zcv2B%=ks~`mMbQlTQeYi)}p6+*@9bqFLqrsPPhIy z97$#RDlnbS_*&%1A+hKk2~P0C1Lh|o=Ua3C8k4Gjhk%e~?jip2(@Z-0lR_osC-1)- z-`Yg}Qj_v4FF=PMI+<^ke-V@a5R6DrmG2%fZ+@!GZul$gM;hNRS{(JBjIi$C6h>zU<2_f>e$RWW91Yt82-_~g; z{bXL22R;V=vx(*TXL6cY`c&UxnJHSd4FMjMWuO_eI*xo1CJn;oe2IF#P1ePAkL z!$Oy$f`A11U-4m39rVgdq9>j{ZR#lNwD^Z4+@`6 zgdVFV9NlOR7Ob_?jR)EI zk64$q*DD#Q3fR>rdO@)8eZZ}aj#Me@_7heo#~+#4-ygG`xgi*ws^GJJ(o9PqE38^t z5KNBFR0dDDI5*PcSC)B)5-7#}TCROP)D$HZ{BTA7uaA?ve^nX02x(f8l+wt1(5Z+OMUm<{dto7gy8x~I{4OylU=_;ZXtgqCH%w~zSaLP@gXlYT-cA@&QFz( zn*Uk}YP#_&L* z!4FaDC-+>W!^n<#)>UU-KeOaN`P3=sd0!$fM~u$ihDfb-XuER*@@IeKT%!uj3~x@l zU4ZVe@d(?_@J2Fum?tLR=Imb}vbhU*+wjUOuBtLZ={8=4O^zd->iL|*$I{Iuc+F>1Z`bvZ` z9k$?f9BaV{ZE>8;PK!6l>NLxPzjsK$k?=Quj|_3ezW`#Q?Z?}o8V;CI8= zHwlS!#@(T1GW)PZf6VH?>)*G$Ly|YO7BnN^(&m{RBv9Lvesnd zag~5=)b(AITUcG4N*lvbzO)p-%QrmFe5 z2)(gs1~EC(JXAH;@%l^vdr>GwFXsa@T5e6gmZ|LSSTHzG^TxFGkYsy$+`ZenHNZ~s z`b)v}w30E%fx5ul4;?&C?PaE}4d#%9Xfh{L!U zMVXViof7e@w!F}L??wF+i{emaC9g*!joM1b*wkT}mq!(pH1ZeA-Y_R6?E_9`Pl8P- zlU}w^FHi5RYq>UL7NIn5nx-{a%U&z9JC9EF4^g>33fHz9L%`fcMVfkOeJWS(GItvb ziotN^g7?;WjPMF3>X2#rK+4lk+irr&8yQeYr1i&9<0neDV|lk{?QzoDmATxpm#YN0 zwym%pFK{!73F__LpbhY}`x}f!% zp<@Ao7vBQI);1X$Wba?92}Ubl<-E zuu6NPy~#9Ppi?TwzuF>2hoDkiuv8E|6jPIAH&Ny@j;C&1xHh(HGaPlSQ(P!0LVi^% zmy4CyWE!%lq=t8E$ehb7w zDy#b`9eS}>^&A;!PupSza&;G+ZLxg=vCn15wsmuAugF>l4HIQ3(G-}pCu}Q8KnamC zuIMypeXNcQN;e{zNa0~n-!w;n10R+g7kQthZy8}7Gs76cmz;3UmVvlXT$B?c_W5aS zF{XDd`D3<@I5ie7PA$^sT>O5i@4QEFpCGq!nrE!Scr?mIyIEOSF!jk)iAn!B!VE4h z0v?Ck1qH>O4m#m*Ql@Ce%>&VS<-&z6S_^!?@CoL`@CA^88+7Z@dpK;92IrRdF3fO6 zhd&^*BusP?tmKMpml2$s51CM|o{OB)>K0xyu%q`;rl#ufjZAso5W#$Z%N=48a6#eG z6>M)<>^;wl#E)2UPZd>*d(XRC2S2G;B}^B>QuOEtE%7a(mM-(v@a+cl6G^T4bp}5T z0nBPra_8z=ADgQ=cPY}(IY#X2nk1?&+vrt52@uFv{=oXkC_|qy-n4B`bH~!aw#US& z+3u1XC*00AG0j~P&2=rre4dNG>Q$3hL}DOeJFO8HU;Y>W`Dc#gO81`)Up>!YwOE?) zTNRgGVOdw%+DxkJrR@2xDe70PMa)jN%#x8tJM41}v3BTy6C;O@+|U&3UscsywTB8_ z;p`|)3tG4LY1j$zk=zk8Bm_o<>vmg`VCLJr^4g^HS@=CaWV$sov}dJjWBWrMUnMl^ z)b*FJcGxzaHQ3=pHJN(b-4J=OlyGd*7!6x*ijouVV=o+Zs>|-`?|OtD6C90RfqdZy zTK6@fGaBH8dP%{H&@E0+7$%&4b{;n;Bt}q>&Na^r$-UfN zQIx|~N(;2hthnW}a%2YJajPok@%y1mo<1r3@Oka{&s$M;rMI`#(lYVHHP4{SlxA`G z>s@805x(O{cg3AS;XYfFbDJ`%K3%@Q*}9kdPdFsu#$LSW>V>0=*n)!CQV@}1x?^A@ zdLD%UQTaZXK1p(p{C}^kB_pdk*DzwX^v0kS#fcEUu*nI2YSKxL z8kQSG`Z2QB$|uUzgI8>ddWIWUhP3!q+4e{fKdyqPy;dts7PE@Vf<1 zpt$cAW%fc$(s4GOuaRp?r(DX98oyOckdSu=cB%x83tZnUz2rO?izOYp@DNO1aCDgI zLT8pXTg7nX*%*VGYabX#E@c*^@+`%;n3lcak;*}MjKcYtmzTud<=rMr9OJ5KBG?}l z1f=z&hn|(kvD!-BgLfBan)L79dcJHs9Y3?f#39l@Ak|-fk@}4Tx2U+io64E0HP0C^ zsdC9oh z@sXEOm}7uvnJeDBhQB*4RoeOf0YqoY%J!;QM7MM+$X3LOyd9j_!kqWKL_aZHkDq++O4kH!uK-6EUTA zRmPKKeUU9kR-B_5~q5gX#uLQ^`xsi8MI3%clTL1KECTO>DWNJ9(idv0qydM3>t5b$+ zxyS)(qxNHkR0e59cY0iUJ2JW%g*ru4J)$`?7wa6SfPCC2ue7Gfcs6I#FEHn#_!U#; z0(WNgwXP{Y%EHJr3s+A^NPvZ|F;w;DCM41u+xbG9L{u`(eMo3nIRDiQVo%>fh0?8A z>Pm%GvY=9?Q_UEm@!F@B;%mwXfAM+3jznF-2?IK2dnWYN9N1;7#tVCa65C=4ZI6nJ z(JtZxO6SCFXt)MYQH-EO&pXB$amogo`NBLpjb~7hkb#o9ia?Oh7wNmFmRAcdCFovB za+CDwUH45-TJ;(B@&=WM)KW!kux6p!MZ=azmNgNU!OYKGpmK_+7BTdDN2!{zha-g| zUI~NTwy+$~y){`Vt}Dl0kWMPZ>vA}krw2XOo#aJG^IV0gswS#A(Ou>m_p0a_f}mwu zq-PzcbsN4-hJ(fYVQa&YczGc*jOB7~E^t1_Cul#A>{LlPogq1E4>+lhDL>_*UTiA%L?m&AjMD>1I zqn*4fM4cf!EF>h})g7n>1Oi2u+dxorxz(ARV3SsCte-u6Z}#BeP;5Dm->qWX-7?Jf z8_${H?iH@Ip}yXVl7n+{50|}Kyn)|6Jyo!_mNQQ>Ga?U{J#n~^gQIs3m3@1-kt2tX zEeS7Y`ck?;QD7l@PQF0@I&YDV9@I}o)#&Z&o41$VXxAX;bAs5M7S2>Wj~4yjJ)_4=U=$4dzE0&D%uCaB57UV-I?ya&tIz0R&%(-CRF-d-XslaR;HPenppBU+R z@}71ok8U5}VX}K7?~FfRhlSwYQpFa{Z{+U(G>_zQO!4YIOpNWgc!X7&eSrC*oP&#K zA9K?JSW|87KBWS)ZG8jD4`Oyc3hx1-(7Z>1WxMYg8?db*{j@(%KlZBNO-F+#gE8T8 zj3DD|OKvr@5M$l8(sm#kKUIu(8^|DksxB7Qq3$&-sOHqqn}m)m_qMf^GhOtPE zn^A(E@rbAl!`d=xhc!bcLsRL?PR$B7ZsjHS+P}v}n?~2fn!e}?k``&0D>~LC@~pD`xy<6KI(Zd*f^VhU2B=j!|u;h5lzEm}mDkr=VG z;(T)WoFYm#+(`m`e>f{0T9i?WI{hq6{N-q0s;87vOnz#N_0z>rM3tCU#e2Oc1KiZB zJ;bzT2mMC2;>6POYbtHmzEQ^t>L4IW(HW|QCP+Z~IMRC{A#{|c5Q;$P&59HQ z1_(%3dJ+<(gpyDc=}mzo)PVFJdKdiS%ri62{@&;P-o5v+_wju1vGWI6>)_5>ce2`b zU-x^3lZKw1&2n~c@2BIz=K3(;9sXKM0z6GcVp1ba9 zd=PP8-CaAZrN9nbNYD()%gHNK{!~nl2}E~emeMv|MEM|I#cc&6Pi^Wn32W;aGxFyW z=7o|ecLK_lH4Wh*Rogf53{mzCd5hN11qhrbpK>)`-GxzB3&+pKpSA7Guk*Cl&z3(d zqRC90$onu5{N_u#pRQ6x+cvn+!;VUi)o5xeS^?GItx;{52**nA)ZVX)3P>f0b=M$Igx>NTj5IFe2h}td( zo1=G)*)+ZG%p%#491#XjM%c&$>FO@tg3%mmwjr#}Q#94jndFn}SH~yhTGsZvVnGj9 zeVqy>=-TeG=8`W3OU?*O#l+y;tx`p=(k>zF??N${9g$L$98nTaNJ&CY4F_lA8?79Q|JZsTd}}dBTc+d=Pr4aVl6Y<(pnMrBDFNt>8YLO{5OVT3o1{Xn`%}CAAHz6LsJ}5N$&=CoA&4rW1IyaPk~4L zX(fa}0E(jsmO{##Bl5lW+6kIad1AX>oyDbl_MF_)ezcvNVExhq2MEE(BXBYOW2l-3 zzZHgxMPU%M_O6JeQQLmWJ?Faz+2;=@+N$MNM9;-e4_N1eJUiHo9_%BO*aju!bk$6t zCS<8D_1G$%=4`I%Do9%&22cXjM_EPA8rsL6w|yO=c-YR+*gZXw+PB4gm|_-o|HD_N zN{NuieC}1a=UWM?`!7BgH$N}>q>|2{CC-M`tHz9dc%DQsBc9Jsm6x?HjuNn4(As0_ z8Xw?|R~oli2x!1xr@6iA`k6PYq!t;3lQtzoat`ygP2PDAup0DNOAe3(cnd>_VMGcKGyY)`I>-2J!FXnroqO>$vuKbxj6jiafzGe zbt?NfTWhjO%_{qDX3L1<^`CA2`NC201_E*rsr}-Ir4ewic3TnYQAT(X8=^8*&=pq@)S1h2`zvXwboLuUxKiD-m z-$s7ZxkhwA7bZIvq{gkAjs)1$NCuVDy0$40-N^4|=l&2aolF7lv^ws(^puWwX^ zw{JY*>huvkN>Rl5bcvRy*G}(S$*eUE1|$;Ge@Y?|IvAzyK#pp>Q~SiU!9%`&x0pWj zL!$$=ItZvy+^ynhnYym%VAR{39uiU1ZxxVpKQ-T=4LuH#M@eaF9)eOtM9lirA9U~5 zfN}byl4ieqe55h3!BfcGP<8`!NkJ1aKwxkg3O5K9!ZvG)5Wx&ghBm*2>1~4<`51dp z#6}=C$p?xFKJ_?+ziEaVWepE;Yn3Fw8h%9*>CrmSC@VtiJPRz1&m?+h34QrO3KI@} z$@k@mi7~{v+&R8aqj`!mZEx>O%)KnJ1#lY*W*@lI&CNvsVLzoUl)%w9dM{Z%9UpcP zN^BT3$N5>7QK=wdN%XuiL(Gok4+EM@%88mZ;MCN%qp8~U2An>flJZ@`1zgV`QYYhb z_17{l)CGnoPN@uu^)$_nAW$YvJG(q2%rX&!kJYI3?M*+m*?I9eM`J=21Pnwa5C|%0 zdx4k}4VXUpn(pC!(UB7Q`I(w_BH!Z4lns0Ro3R}`hD~qv45r?w6np6z1K{B>-p)-4 zN42Zqno=pHo*(ZFEtvpsg@q9}v8!Z3rykNOt?S51VJUpPwSlou~sj}B+U zJ>b`-AT<6Ulg|UJvcoo&LG>lvi5cJ#y5IbQZ*sOk=^y~c4HTe&8j!c4kgmqWfC(4( zW}-f_4G(6Z+W5Y(7<#uznjb=O%wU@Lrn>k!ZEfpgKZn%zza)^QdZjQpHzmY0vW-@i zGd*p198;S}R=s~&u4A*#Xg#}TwoA&Nf;~qt8I~wdOKiOj#NQj5LcFA1bFgv9D}?n= zAzy@rcR^q&Q=?%QxVby=8N2uJC6HQ;TMzOQ6QLRfu^L(w7c2y15|We+H&Qy1Qjz&3 zR_4X1J52ly;}#Dz2sDWfws{ZxJY%#P#SM`$C7B9~!QnWBVw!lqotP4O+(6x)L*=*& zZ~Dh)2o>TAjgy(c`Qlk+A(%|Y6*{V&elYnMjS_<(@0x8xn4L47pj}+lH6m#h-Y;W1d z@8@-{x*L~0R|8@`G>C6oZL0FU=Um&m!5f1gjrv6CX*L0I+`f7ij8=+;YW45u1P_m< z;M5GBL1>J*)bp3Flvs(&a}1Uo#7=Jg5F{ROMc&ircw3^}VXzyD71Uvqd7M!cg&%S! zt*)HA>b!DlIldV?nEKM%11|Ae7k7Aw>ityg2llzad*K#$cY8m*@265;kv~fCfMtE-Ze%Cn3Vc1FVWN(XVBW#DsO@5WIP)+$762V zJ6vy~YBXh=QS(}UuZcgMA^#yJaUxIz^!i0v=YuBEx%|iD^K%z0h`UY@IN^p)L|Sjv z!pE+NG`z-IHVCKI=e)(!sZw9Kqi4=BzRQt_N_fyWA{b-QntkuKB6h-_Yf58r#2pOL z$iiUgm@5IfxZHY1)q%LTyRN8nYNqp&(#-?4_R@P9iTMF&GwhdI)556HN30&D!|H2_GX&^)~90lsYyO%d=%W7NhkGHl_STIksF*O{$j-aV9Sze7$Ry4v+PI zuLQp1br56f^W^|a$q(BbuTh=cxYG2+GfdS4=s5b7$w>F#33jV-l!sDg=gg%_w9G z@9jPDKlmJUS!Q)~#*0@y{vdd7bHCMsNjoHZ&vbd6Am5O=Fq2d_(p(JFgs#u7F$!gh z(rg*hQ-zW;#z5$z40PAjiZLcynCg2T)>RP1QKqHYt8#os&V4S5ArTZf1Ol zoGs^nVb_VNEUXwJxAV0h7v=uk%o8HYSG7>vf}2AW;ME`|eRt0D-c7siJag9KeOCG6 zEefha5!bZ2YdmKzv<4CLjfzpz5PrnXRTSE(Hd?=uHl=3L|C`LGO!Oa+?_aLyPKbEB zS&(^^$Fg;)t%5z;2O*=D0d(vyLm?~?39As|7Mt zWQE2TeR(%Rl32sX*wHSPHT-at3dTESmQh%;GzA6+#WzKF=k>9iu-2K#KGTd|Uo2&{ z+2)|ACU{28&cQgits;l_vnP=9oT)wr)_o3h%7&?9H_Nlf{q(qjWt#Bf5j0KLT5G!| zQViBxaf5@0qOJz^hH!AOfA#YyH+`mj!jbk;tbp>A@TrT$7a;2>_YzcZEWCss>HVkRG=6Ad9?sWe2G=rGQy zQ@`8}s3QKZmq8x>4-2e>d9*j-6xBD-k8e8y{7lf!3p-+Cf#D?=+mEZS8)hZS3C(UP zoM3=G-W6biYz3QL2SVk28rA@+kijCj$q*LJ_$BG&b&cYdE+>FIiO{7#SiqRm$x4on ziFAI}ceW9JSa+FT&LWgWgV^P4y4?=Z7H0;P;b*j2*Sz?IZx2uUtyMjY3rjF(y{XpW zsJ1d*SW*+SJ-FOPjwA*%_vCYe^KuEE6WK{(DH`f-xW=NyeSuPZclkWtt_BC0)#g2u z4N`&;`8>uY6%hHCoC?P_D=)q_uLJbsB_3_cWE3Qxo1JxX=q^$+fb}}_+Y@iSf4B)A^H4B<3fNW{hY}!x*8zQ-K=umH{U)-_7k!oCxZYI4 z;6QBOpnXj1Et`d!a)3HwffF;~MJU#b)f5QwmZw8Tli(XCBkv)`&@waL73=g$*@*MdA?0yVZe=$-HM_7%usg^=1}*SK&^sF zO(28W4l+7Sce)W{@!(6~P*nfj3{khd6a@vXX-wjq#b*eOVyplj9y`Fw<|hx$Zt}hJ z{Q&d4(iAb^$-FJcHuJ@h=%Homj?5Yj$_sSW7I00j08q2LIY%{W=IY)}DYJa1Njhg+ zd?UQ>Q=Z7-=k7(lv7!x;sIW?zZ@1!xE-r?o>{b5$6U{%H^nAMkv)zOpoUzNKpS|)| z6P-^-b^rQ!(4S32e>pAr=coVa4h&)y#%`-m)Gx=r+ql7!$*&vCNSv9QXf>%3f}@A4 zD1q{zA;9uV(1`RR!&6Uj`YV%gqG-++)p>&~7oUkfiM#6IHVYzI`QFL5ZW$%qkiSCP z3SM^E$WCvgePu!t`D25|{=W&#>_5yK- z#yPPt6x*(tC-{auKJ~6eB=E6oSNpEEXLPmPkfpVWnCs(Oc5sxVJr4qD*|CJ2ozBAV zyPu36tuMNS8%rKm5HyHVs82}mO~35(1w*hNrC9fo*0&{=X?p>?0e;)%@?5(ZuZ6H( z+3}8r$wcOcO<7ByxyaADhF*_negG;G!lZ(-c{bDyo_ZVlu>t5@8>Uk)9Qqw?I6ECW zBr~ccW%Mg4B?{3=aEEBc07vym$g0Mas|&SAQb$tjQkxCSgTRIKv%Wd=zWMVIMphIB z-zp^WZ$hes?dYvn4cR@V)z!B%VhjU7Ca>p#+jvRl%zuo_lk4VcITM_2mWPFu2Id_+ zBNkH?>cDLujm53BIW!;k@e52NSe0Z+9^Ae*FJ<2Sr{>0()-`{q+l={1Y1?7+pse}B zX~NLFY8x9`E?|y7#PQ1Ud*K=PlCO0k zlo@k>wj@2I$$d@pisd=9HJ;;BOF?|?V|e8K+=0NLsAAsMWnqJn8&8^D<;B}Ve$07C zC(FnUB6M;e)2ty}ooB^=?FADAz1h}xd3)Q(A@1$nRaWG~OK@ScWsn2$d45?1bG4Mf z=?9N7Sy8#G!jZ9wDO#iaCL=Hu=v@8Tqdf|i7r7C}ci+Bu?cI--*qM@nm=u=*-DU=D zo@cd?+W9bAcy%ZM19-&snTR=i6W?*uyQ{!1mrN3D3Yuz~oss|8et%vN615l`h|Q=G z;T|Hc0r=qVIPH9vn;JL=PMYmo`(d5ox0XvJkR%6ZD!UmCUk)wo0d|{W=k4J57*jre zXsC7aryx%LoM=B)tD&h_hX z@nar5Tj*E0=JsoEo5g;Er@++&=XuOEmrO39mgaVS0S{zB`l%R*n&FQ>>Vvz5X7f)D z|6-zDK)C$O7X);_1;?bsopINhi(^k#OyHJ^qMKHMSZifwhwRA;>I~?kO#az23p9UT zcJ^DN+bmKQPQ{T14Qgr%Fu(I$H}3TeB}QAkb#YOWT^YDgJ9J!aar4)@r1X!;S80&; z78>ni=cja`ts(d9GE20Z>}(K9oSh~Av5KRgfU64-O~hapBhmq+N3GvK9R8`Q)0Zn5 zTa_VFV9G1|uB?rvWFfE&YLiwvYVPL1a3(Wki}zC(md0X|E210nZAY(0#tOkSxAv~m z7zPP{TDLsnT8?dKjt6G%hn%Np*Q&Bz(WAyLZKx6xjTnY(f33#se9;TIVWC>_>2Tis z7gqdttBMn=Qj?wc4@YnBqaB$vNPn#se7Eh~gd@t`=?2yFJ{5fNKR4WMmhh+S!OlW> zppsB2$Cy)s-f=cFT>djt16=6O?op%!zW=vv!uOhSi1v4q)PGba9idJ#NMhp6?yqor zD1vNzCl8t9C8<&1Lb2J_S0-Jn0-Z{mHi_tnzS*t2Mn&y#eD15M#^qTbH-59|TQ-ps zQyHnqp&~;?M??S8mz@F4C03k*6`tbx7pv${C10c)W3D_|E^hbE477&dV-4e+hEI`5 zfbz9s3iq~IU>0vt4HSh+zw8Pa@4;azBp2zCVZjPo-E;~ z&}&h^mlJ8dvZhAO`^t2s8O}5}@xk*{N%DZF+Gyolf@5V;g5Gl#ntmLaNkp+A z0=BiYd^Wn`))&l&>wAkCv9h>EG0J5Q;z{Ff+SHrK&9YCV)RdburrR38fKd_%#F-q* zq4@Gdm}GCqF(c=>Iq%`CS6lDo2Dbj-7v#Y4BC@@lZl}}74}GIx@>%}kuqtb*>3i=o zJ8BeHn*}@JJxKpHyRo`j?2L|Y6;9n%Ynd6T#JlG^OlRVCd~&&IRY!Qp(=~L+DJL-i zkZI_V6O$pQ&tH{;v}jO+R<&Vf{3(H8WBZ%5+T91Ovr_wR!0E-7tfI;f+S~w_feQe& z0Y`?p?4_szvR4rRuv9$oP@&E09`~EtK1X)5gYt}Z@2G1qyp*v<8>V1fU28zBAZ;1u zX9M=a2rZP?r(SNFU8W@KUr}{?yy+RP5ZImoN0wvqu5Hs9MVSVE1QKJTjFybe{{2WX z+_McH^_Y6T2{aX!F~vz&Xl#ec>O|*SmLDeeW+m2(XZKbJ_UXeqe^&DW3f6Z&e=oeA z_!!|LWH_5R!5`MY^RVpO785@H$?uEQJ8+(@9;Nuc_^bB*h+v08r*md^aQ*^L{r-1z z6|Y5q`M3S~U#$NN6(rX&yrO=y)B0AB9Qvu~cM|I#eg4mA-2T|c-{1eIAo*JRZ<7B1 zgM#EM{x##tnk>o3dyUG<8!f|ngQ`E`TdxnESO*Wf@Q_*`5Squmt$XI2P2BoE#OC`Z zrg1BM*p%da(|KL*aonb3Whv2w(yQuQRNu$9Q9N67&K}7~J}|Nm{mT(~_8+F&hLF^G z&GQ364|won!Wap-LEff%)IML(X2N}RJ7A{RdZa^RzRiOinHh@Vf zHO(E@+uEIV{PM<$q^U@gu1v#z?vxD@)ZAX+=f>RuQR+rJ<9f3|iE*XqS>z#l`C zCLLHhyCu4a;5p(+jXbPUHrrvhEieZDw#|51w=3l~MP)I0HBLZCvQLlPVGuw9avno> z-2l1-1N;+kYNZD}DUMW$O#JKed!Q{R&Wltn{$?C-T7b?Qee_sdRsbuqff6)a zD@0)PfgF|0k6hS}=)6tV<|JCK5M5l)bS+PAhooA11$r!Fr*a~pV|ie42al%~0ZyNS zE?S#&m6PN$NY@L!<8yngFgY0auT1mJVy=df5$1pF>3{pqE8+ZVA|)is`i2#8hNJf5 zy*wX?wOaHis&IAWgg43;W|1zzaE$|CejcPydOjI-K-G{k`!1 z_g;MSmi(X6x_{8o|GP`;n3!0c2v!bd_eTPJ!_^%U%sPAC;0?JU2_^EJhbKB3V2Crr z8&D&0oJ*CzGSG`0e zRj^s-I_a@I56Wzl=m56fp3Xz~ePxRLo3!#GSv5T10iyDJ01?=l%jxR`%AMVxj7DYC94% zFIzr!o-Ee(j7EK|6Xo!j;>hTCC5R8p?*$KI*$i%Pi|)wyN#Td)wc2agvC4ZFqZ5HS z@Dk}jCEpU@<2pbPgl7QHdI)E~Ul7#>b=`%?=c>7PW*ZEFN^nX$&e^ah~DfLn*byA*#nmFWI@#|g+vF%U4 zzw}RJwQX`j(Dj@CUzrS@4xW2{Wn%iK*zGuXMYVUJ?(T5dyDQ%GkkrVb#G0LxqJ9YD!Y=O+ z^+D4JHS9<6;JZU>!6qEB;(5h$)ZT#_6CMswN#6KIVpe3&5?3cqVPR8$>ntFGk5q}UUsMly9= zh(IX$63wRzyn|ea)Q`?FSbwkgH5*l;LEvV$PA})TmeHfb)L4n^#7~o>4cTh8aV8?WF#@P&H76FU__p|Z6jq_0focbGIuRM^4QRS$L)MbW=b^zPOGV-A_T zls~#Bs~YVwPXCnl)?rUSzBn_5ARCRy&{m~-3u9nm7E-=D?8Ut2bTjX~vVzIlr}Vpm z2=b_H#RQpx$dzUO_}8aV8rAa`*+-VJ!>SIx#1m51;-j2sr`wvG62o1=ofDjj5=e8VjM^p-WXZu9`Bj; zP(WIj_T2PzIZ1vuBdqz%;!sDcHzeLqC$qOWaSSn#;^I!g!`yO9O-)C|{p8HAVdAnf z9<2}A(JrXLKxm67gO3 z_h6WeF2ru!+45DMrtZ}se>LspJ>BSX!!9lHB<>ERaKntg4x&E43g+mdbi!*NzeR5xh zWpo@SK3B1s$n-;4|E8}^k|#Ho?zdhi+AaL$0+Q|USoI0Lu9Q$=La3nO#ZA*lwy#Xf z6_sz9_z&g?wXs=!jViZpn5-Kwy|4hN;_f~^Zo8lwZ8zTb@$;v7iDZZ3d>z5&OSlT1 zx(lsm9dL&kGY7|?E=VS;$h|NLtPpH*OH2v7>VwC|oCq5a%sO%{9ts>{HQoL&)`pqT z?DGiSsofyR8C?Y^fgb8OQ@`({l3g18g5A{T9HaulwT<36umjfX6UK zWD4f^Y#+Wj%>d)4=4;2NMuo5zpyP%j9NSZ^vb{7CZlc60B6+?tO=Hn1vjZ!bnkAQ@ z`S6o0Rriu&q|qfx+2>*}RE)r(vn9p6}SC75M-SfoS z1q0gJoE`k$q4akAL|=dZb^lk(r!v{8d(4Dv1GfqQS72LUx>ob6y}*c6h6^c)l+4q| zmz6Im_iJLkDt=KH56iw(7B=Iuo>Qgx*;U50ZD6==XNWCq=xA*Of^mOgWqZSVe)iA3 z=-gW{RG&V?mfZm~#VjzzPOTuN~htbw&9eO_b-9#2l*`q=XRSgyR1C%5n zrdfL@2RK63?5VZ`zRRPrLh~9?-7~}b7KHRKK$mJWGG6CPc*lZ%T}FAW>bkp&;5}h) zBnZo>V89SYfJn%jZq)|Z0-}B_(a27T17Zqd20*@}qjPh! ziapY4>b9)12A4LIJW`hG>aLDDK(OzmUvDhTfciq6qRIn3FtZ-16pdngf zOR@U2p=Ws;nsnhlvdaKkQExvuBh|XgD2HJCw?Kx^wnbDci*{?|q@NDV4L@{j-erE? zL2LZ6kPtm6t>#ojgk;}>gt#NVWlb>`Sl;LMXcOq;BQ4;0i;c}sxy$CMn%d8a+-Dae z7bxe#G6(0~h}LfDs8>x==3LF+Ko)w9=Grauc1>_?Z0q7> zrIfy`m-WfUYAwsFiky0t%=Al8+Uvtc)Nf zspnN3$bnr200Ib%RYU4pU1yE3Gx_3o%SWJu@} zWdD3(j|~~ZK9VHn0}AiYW}65&xo!Ms}ao+T7a?-)7JRbv*3Bz+Hz?9Q7n*Q7L65z+t ziQ$5Wsy}|J(iHVd`-``%2cp}7)a4|zAc^Z@ z|G0_$_V%xd%sP4*XKm;2x4ZrGB3H@uZ6LPL@En6=J*oV}^mvojM2g%88Mj~$7l!SV zdyM*OR(|c%_MgX|JXgxQ2OgDCJ)MzDpYhQZOIYc`kA_gq2K$!l4E7UNez3FoaYLP( z@vF{ClN6D%c7yA>od78nA!+ob%pC)z;A{48a1}^|s-*T~z3mz!*P|Rk|q?kEJ7~ z&mk*n$f>;N6R19|xVNXiKie+&EaT~<`Z{4mVZ_eHZB3%LH1Vja|3W}yKt&75pzHX< z?~mtqY(fSCjZ);-d1jX(NAF*M_#K?k82BB${q^0l=g?K0b2Dts)gpXGSJ%DTbU@It z{@MV4er|l6Ch*dBRxxxP0^#)F`iaP}RJl*tmyIGDS&atQnqxc?dYjo{I{iNdN-YJU zW;X&cc`Yx%JCO5yE1BrQ^{Bf6j`@cE4uxHAr_*gG-3G;oG500N7&48wpm&fMd|{avDAqTqn?=zmZNl=YBtr@Z0%iRSYfrQbPSm%D z_Zpi{1>acc%sVsR;zsTh~EFC;x2%W#nZYx#gnVY`tF9|Wr?ZxqzJHjQ&x(V?V%GB9%F|WN8 zq+*Ij*%~$;#Z^6&KjT($t9m)Q@qE0B;@sKHsw4M)u&q9` zuLdRG4M*Su^Y)iIA{eFb?(Suoh7-Lk=V%e3CLwJYPD7}z&6bzAwVT?2cZdE z0uNXe)SUSSuen_oHwDU;C$0>|wQQ7rG&^ycI1ceBH!-<;&((>Qgy$lc(iw z_$8`ERNM0+F)(>mu0GM3x74C(gJCoofk9}JgH3dbb<&!p@WJd4fWLHK%jkN%|EuWXiGc#l zg4u6hPIuE@o1V0gdnMl)(U#F%P=3=Cpi^a0n@j9jW;nA1S@sH?w`HqS5nkMa(_@y( zGEKz;-@pp(mk1Y?5}Tz#4mUSp@f1@cu42lUk)7CB$xg2Q;l+SSK3u+Ts%e z)AQs2L07W+$IQ@xDVM#(1>L$ExXmRGy_d?t248kEO9y(1Hvvrbb1H=vo4@v%H4nLn z1&GHuWVLwC_X%TgK9(MBOGJUlxiQTf!KW{mD-_t{GsF7-GoHZ?KD8NwEtwEP5t|ty6Yyq1KS{F`v0W2JR)twRzrZnS? z>M1tbjJO-@rv6ua>~vE1nWOLU`q{TL7oGIlL)Be#Nyf6i*$!GRe6EF>S%54>)Ev>h zMi~BE&;ReA()}(P{w*@5{~jeDxx>VCLiZd+k6$@N#-G+yyo`I})`cJ>5{Vqx92>*2 zu}w8sdWXT>75Y7CVYt)2w_J!KR^&32w`S9*9Ah>r^FXv)AlTJ3I|H$b1=%z}R{5&x z&K%h~c>+c&$=KG8(Kl)LEXpk?%zut-|HyJKGLDq$4xaK+6DeU(gW-IOB=3ieNw>cu zQ|Wh_8>O@v%TVjPiZG12Ne=Xd=+J_oKPC zuDB8h(5O++D*CMAl?_-P^3n|UQuJ=uq&=+!zSt)j$$tLrO*vg!NypwrD@;%{9_ace z&p=a=s5f2J>G3$a;x=y&Nm%p64CCD9-;Swxm6RGBUA8#&F!rzL@e$*||MmL!r;&c7 z|5!FE)3c;l*I$a2n{1mOuHW=|ED3IuQx@CgS=H`jON;CnNk;|5kE*Fw$)-*xoxA=3 zXPjN?r)Sgq+zJOljTA&GqQIra-Oa-kTK7;EV4WX=UdC`T{h%0aE!PQ@WBnlRubjGu zg5)a|k6z?zF6l< z7P9UP9N^x^pMr^2enU~}RiDxei}2WUi;QuFD?C%zZB=Io2)W_UQuo5Gx73r_$n??B z`$_MPem;*7v=$E$1CoJK;9juCz}*y12E?Byl`*)21HQek-udlp;dRoCw|^Cqk^OVK z^LX2L064R{f1PT7E*;?} zBS5XvVi|51KFwt%iA~v!7JZBq%4(RiNzIAN+L5LzleW3swjnE%gEA{mB2zoBkX&xd zJRXV3Jo{lDO_wL=hILR)i!H?k)4$nbF~?=SVS{VPUhDg^fE1sG8IzhCT?IxBc{HS&CN4||#nON_P8A${*+hKrB=g(d-{qu0=-xchncpb~4iumO1 zjqv-N@HMSXAx_*1A7JHhc9r=AZr9mEBz>83pf$G5C9Rt=a)1Vo4}AY!A&<`=Sx6=( z!M);~ZQaeDSd+4*DMNd*C66eFpcT&6nC9#^?NeKWn~OVH6gis7wiTq{r|COrh`m^2 zM4(D-4MtzlwNJiQ!V^enoyS_64y}>gBl!aTK^>zZnX`Q0$UTG5Q!K}MrGja`gs?m;0kIg$Hr`$Ue9sAOWB zW&9oQ-9&*Dp_Uhr0lA@6x2^4%KNGABaH`~RsBP|mhescSLtJlL`K3;`WMs7H8}Ij& z9|sYc;*7)U10G;MPR>c6)p?LaI5gJE{HVkQF~Qt&#tfmX-xw23Oki7GB;^;*z#{WBeyCce641gzqoq{>_cgV5>vNP@5`{{~t;-kG;$eJii1WtrR!={D-= z0qbCSUWzjcj)%BPPKt?qY!!XAR5PB%G+SDCfA@SRB+PiCMnPvb*VztSf{Clv=Xi#Cryf4?yP`wS@l)WLi+pxAmAJQcPW8U35Y@>@^-!v++w zy_=+>br0=9pW_ezT4rJl1b%f>(8u8Fi-*oODgByJ`eQ?SOt%!ui~9aSqL%X*YF(_X zQ!AVPK$$g~rv;DVXhB~U-D?+m=(MweAX+ZHQro_^w7myV#}0H4^$=FIkJ)R02r(Wv z?%59_o5s>Q zH@yw`x~%+`gzxqeGkUeHJ8XlR;&@rd~^@{2bk|2UUg;+8xs&)RkTPL?OMG zxm?=AzP?LgPiF5hLNBrH0P1K`e~`qVp_lFdk3z4AzlB~re}!Hb0M<9jr(Jj*3-tn1 z+E1866K-2_IIL6juqJ})N)nIm+S?6kmZrCgZo_!UhUb&3cwot>8dFT|C8Fg-pO&^F7f)+Kksc13BhAJ{tf2cI-F~%qW9~IR9@KePd zN-PFj(gzuwh_5n&kd1L(6fIm#WW>lk!6+NFn7prEIvEJex0iVC!Vm!!x`v8!@PNG4s%ZGMy2H0MT! zYEm@sJL*Zx-nZ6zyKV>(BrqJdy>`yWrLVbS@X6LC`ESHbcg1BDeJ~c0Ga->9SKZLGQ*DtJlAbr!*n18?e zfB*YmDyIB?M@&qif>9#l!E6n!{76i5@sc_tOJPa+@Un_bed#C6Nz>6|mza(Zqs^UC_wXB5P}`iyxuy-z{UM$#j@1KtA$ydN7P zHoOagApB^ny_0_LdIH0Xj9hKCvN1QH9zwq9Y0)exiuS?)QhAC=qp-O0oAL02pkSx^ zc=NK*8dkDG(Q|M%5p=hYf(gB>vGU>_6*I06?a|F)}K$veH<;>;`N{pZCdC;8< z=>!6SN~r$hJ-PE(&(a(2E}S)C3VE@<#esgoqHJ5Ufl0SHunOk)C@=1oTMW*pLb@Km zNn#e9k9EM74fCthA=fecbHwbFTIT*4DcU&`ksRVj@1hogZk%0ie(}5`LoHra%u!~3 zRx}vO9F(-)KdC>;InzLQs$zslf1ZvlC_gZT+*;e|;*Vri zu$qEs5YWV`Hip4`!E=88mc>iYqDaDRlI)3C{c=t)gbHaxHnKQRc&F8z{>p@J(Oex8 z-;Q9(prFkgQ}TKro4H&du{Fc8O~F=}_%F0UBE$7i7NJkgNN?>Jai2fPY@wdCbflaU zSWI9>C)XyF%thFw%~!7O%fJ@md2>eZlL{sL1Yzl1vfBIzOEC{*C(J=3OAuy2V6c~+ znirxCrBg&H+}6X>T2^SFOzjtwhDq(8k+RkU{1%ftXF)f+c0zlVwpJJLS$$BuHZ!to zfRM7eVEHTqWTM~?x#*O?E~r_W8~o$7&ylTn#!HX6+>V|$3(vLWn{$v?J6~oUVA9Zt z&9VQC@;x-}y^-I9olw=r@4az&6cW6G&3rE%CdrE{w?PkQ>}BPunS@zgdu{RPa^ASz znOD0>w>DEa_q*Glw?rQVf2mtl1qSXGKsxCDP-Y91cc$AmQQNibmrUF`g@KeX;oJ-U z#o}C%V4GAmg9wkDVx-GcC>kSyQG+%wBaDTMsv+;bDTsdzc~_Vz zKN@87mSH{oW6d8c->h|hs&?rcy(VL$YPqXSe{TYLIq1x5=~St!4(`uE!)&PS7qa8w zkkNd{?T&M0&b5{wRE}HX6hazKiFM4&rnL@hE;uk~7c zHu^}cdN#f(vt8|oqc8(p3IoobELgBK*c>P&6wCluF&o~%_7aRo`CKLF2!C58s$uc6 z_(%4)cav+K&F#E2{X(UjEc)Gg7#f%1V*E8TML6na5ks5x$J zJ#;zenGo{Y8$)o`wrRsGRi38{N}vyPdgTpC#%4drJ?i}&7NeZ$*fZguCyAmC*=r-K zq%A0k=9w4PYLMWnRvKq2Tpb3%6p-XWa@XFmgue}nb7E-!By62t_9eTSB1-0b@3Qs! z*1mt>a+sU%&m(?*n}eaa%c2&QXt=!yMKrX)pPvku87Q9cZK~K56ua;G=h0@xUpQ!< z+OtmFItXBR842#kwr`Ncm-bc|P8>>|%`cGMcT?_$VguDZO6F^C*xDLx4kTk>`f}HKs4V0T3FEJ_z5uyj)C@WS7$V0YJHMv2MF0Z2Bbo1#puxECI(NV*1(+1lO zeM_uATLiYrcLxYK&7yl!MXjPDCK;Mt2!kO4VtfkJ3h+gOg{ zT#Rd4iuc9jE%{DaUZ&EZ^#p#-o*04aqCAiQz0#hE0e7JBoErrNYAAwB_jW`A#bWFv z4d^hGFN5@^6YiMvYTEo^&m84=(`Mw5+ep4HyuZ;KN0AXZ>c)LHh|P(>Dj&dLpD1v! z_;S{dh`ey3cK+H*IHvk>*;lA7F`#6q*JYxdXu77g!xwXNLbIy&OpOJ$qK?S-h^hO& z|MTjFiXWk!aztl@GF5Cu*T-ebl<~}Xa-R=@{`G~A(rUIdRlvp+1FyP5D$*EqU$a2u z1Tr^FEvwYpZJz6L<&2)VCe>y>H(M}oI-m&PX_@v;i{{I5w>|C0NE<_a_@7mC8{L4; z>laLPWi-)twaS)A)`qO*@L zEY*R1AeYygqh~3&M%<^-9Ar>NH=9Ian!JjG*)_bfHn7l~b0<&kAfB8bOVvlaUfe;) zO}rR#r^LccG8pk3$lWL4677BRcxsxg$mW1#wIARnDFiWo$Tv&TW? z@yRQ__5H8rIB+mX8jCq^%r;_>Hk~u)#xu8W1$SEpi(*?W zp%IHRBY`~c)IiC_c#e0Pb?w|oe&m1gp4jl-Jb``U>$a(_A+;01{Naix^Q#eV2MY3& ziS|yBap~07kzc zJmuOrXIsTt5-eq5&8&6@TdOyR2A{$%BN%!cgtSYDK}hnSm2zL=e2DZ9fU z0W^Ez4M)+UMziJkKH*m1%1#RfTvb(Yrm%RJMx|NSNKp3Jv?z|n-D+JT)$2tz?-(2} z!a-Lx;`gvsKLDKe1Rv>q5t!w{4!=bn*N!6v?n_l^%2&UDaUvVx(Ys^vl*y)NgoCW^DA1AkwY`a?PniDfO%L84!1!LvEiTCLkrl(ipR>VD@!;cAaPP0}@o%5N+%E@CD zM$(>$AM8f_$q-8&_9-X79}PiTN=%0b)Vn1{Q-sVh}VQ7G8}H+ zYe&<&H_K03tL_)(j(+ygX`tzr8ASZu8pgOyy&L#`_dz=nbi-tOwEn!9mx8QA)>2tCi~uWWgZFQWYIC+^|C{)b)E?KInN6W>S^<>(lj8v^m(W$rCG%gt!lvU zA0Sq!*j#zo|0bXrZq~9#2q}TFd|@%+6_BleekZ+AIo_Z{xOm7e2fVeZ|NXOuPr3L1 z6V9wp&;2&tXG)-N@)rf1bno|&)bZy#C<(_iFP!Nbl!Y3y%lS$xp2X@vKv9AocN0v% zcLzkiWT+77Ayxe(fqtLo7CfR}}LTAQ7Va#R~|Sy!dw|HxOWu{Sz4NxF-4#NMUe_(X>ppmS&cNzsBn}~d+MfL1<0}3CqlWaGtfn} zP8eLGiufiC_UPt)FWHDasz#L}?76Zl0=U-*RGiuB;fW1*-PIeVk&-(fxtX|Co?QK| zT^RRr5?^P16e(X%fZ=g#KTc3jy(SzpDZ-e1?_eImB8y^>?7AWP=mfmh?1uI3>|jz* zfs}uMNPI`{mHyH@E(?TR$64mUB?(409&N5$mtZ(pzwqus+Wi@koZvb8i8L$Lhy=B( zCNQZrUN4LUhQkDIk}zKhZWC3h&_#grl{^@qm<2!7|Xsp+_G8E}n>LUWervPUNxR5hi!* zN+ceTye?Q~{Oes&p3m$1-!ArfwY*8DmQGNDI$i8gN!rK)tRmDrz{jgf zZdkFV;w|Y`?fm(GcHie5=Qd;b1tgtLj+@52yw0l2l)8pOm$ZY{4R8y5@gv*oS;Ehs zN1Q3YZgxKbzo+-<&}5uzPKdUuuig|j=UE(OUA{Kf1@JDFX!8zJtmOTQxR#;4uOjS# zk+gCHg<;d!-E*cKp(QP1(Wh`6CVd#+J=IV2T$gM8uD(qASn2H76%gNWcT%f7L?t%P ziLYzGr7)$N6rx7U*RUyevoEYUNd;n!Jk6EAy7*R>)3WCZ&(`qIMI{AV^_%tR>q^CC ztCn-XC_PaFg;c+TG)taC_LyPYuuhrF6g(pxhQSm2_QWs*lzrhXWRS#AGt~y)4X;4@ z`5ki_dWm}5dJ7r#j)obKxqE(4L8FFrx+8MJVmi{$S|A z`T$H#-piR?yQ-*HJzZ5yw5Fyd=g7r2LH8$c*|`~#4*G&O>Zcs2Df{>Hb7;5(-pt?P zKGaR4`e<^H+KZ|KPc+DeD_>j!KkwEt$qUzbHFMBO$J80lS*qz@YUA!qFY6^3oO-(! zq#)cqkW1UQsFwt+AwjK$m+oZP_2Q|S{tBTqcbl6tu}qm4Px}QEaVs zAgycswCc)RslvY$KwEZc^G0_b@o|@?Gb`S7U`9V#`VPboxKX(DBU(~WFMbH)d=2(( zqHa<)v=(L%kFHfwvz9n~n<_6oP%Eh`yCc-Xk0#Yb4)!nAJsNc;8HFAxI%9yga8^QN zb-WOKPR#JP~2$L4?^(2`(lve?Sqq)(43E2{^mu{fVD?`uC7Q2`}BN=CAHu9 z69WKkNaw~8m#(7h=>QTo_W`!FR~s1{V=L-i@rat=&WR}wLk9HGn3;m5unBAaP7KYB z@)IZGt}5fe+50guRL=PuXM6MwjX!p=o;4Mx^}@qnWk{CN-tzpaP!Y;8^#ed!Fla9D zP+$HrT^&EZcGK*b<0fpIo7H1acH@GAIvL1x1YgJH06jyI0iv&H@@Wp-3kUOMy@BtO zzJ5-3Y$3?p%OPL39N1|%{ncq^%O`B@FP{DfVJi%%GiSxR$jr2@o`MAE7Jb2$%e8fp z`G1bTA$Hg>^0H988xJD-)jS&SLy&{fkzlXyU4^khfP}#y%`wj@Rw{hgQ`h0TmR!mi zG?*o$NsTyPe=BlM7}pmPuAX#cojS=GO~#1(ca zDlIMaHO{@Q;pcg_?Kt%s`svp*b4eB7zP+N=T2+f_{aP{Qy4Y(s%ZPZ!_$VRT%C5Me zO9?wN9Jz1}gZ8NP1YrUQx9{_o|j#fK&MJtFueVfT0 zbNg-~&6y$$(OFeN6$Xxeso_8I8~v9B|8q(AkqIvHd*jK+(oSW z{72nwds7JGPH2Osk{PO9+)DzP=_(bHQcxk?(2{1h^saWVp1(_sLjf*Du)7cF!Ops1 z)w@(HC?7MDB|-@eUoy)*)Mi|1Nu$$iE?6s{^GDNJF^nFF4k(r8w!njY6dCSPx%o$Ml!5I7qOCw@q_5)xddUeyxzfZ;u=t1fe z(_NUyobB{Sk<~;Sne*~-exMthHcYmjuK@!eH{uhWWg$==s1u6b4}~6NVGYZwL(K^6 znh}`4I-D8el~VTsP!hdvPe~B?inA9RnpMb5ZcjO?FRgaBc+%JIc42JoyZO4Mu&eW6 zIn_NvpU4lwr%7JkL3-d_rkniX4-7#ri-%W`warin6vZb9TW*AhROyE zWX8u^N@^lEA@Ir+O~VbRxmcO5vxdN=aYl$vNQI|AD)T*8?-JExgM%ZiQhBdDG;yQv z1AxiHleun{xVZ*mXEd^!92c@C%2j}8U1jG6iV1%D%sL5hW8u9$_&4`D{r6uI5`*l9 zTcQnG`lQY%w5+(-ZLhl?wPApr2jdE&b7?`2{2lf9rZBF;!if>q5(o7hSJ2|luVhke zOjJBXoPyQr=vTakt**$;oo&5MKkXhpt?X~A7K-kKbou&d%~GrR``e%^n|z4gFjk!e z*-;&u)IB`ge{q23uUnN;T^yFe4{oH7MW0aS?=)Dc+%Mb>MbN>Lw%Oz9DJ{&xO74rv z%Pr0@ooD0LQuu7$iRkP@T~Cr+1#^l_QxGw#YUw9-CpohvwMmQ(k;~NY`a*`9f;$Zq z=!Y|<2XUj#Hp`n~*(aoKw4-|ZCom5@3Q}8U3l_EA@O+X%17f{z`bN zct|8SHzyD9(`R!}M4DzgtuBM)C@y*56j-3i8mwr;1Hg*~NlJ9o?CV%!Or3Y(2f*d^ zk}$E_Or)s-KE2*rPhPWG*kH}3Mg3#QPWY#g9gC6>(Vo>5ew#u9USi-*LBF=vomL{| z)Q(fhdF}}tmw$~03R%r1ICu>r&%L_-2C+ltXv+*GZ(JA`ubb?!_#B;=m|ye&k8&VX z^fXe9jll<&8l*WWLWmf1C_c98{>QrS+P)rM>adI)j1&JK+(yrb*Y=l$Dtqdp;irfc zsyi=^K)|6TV!6^YE9ZQc#Kn(smspWBLG~`CgUfwjk4~EVo1#W<%(GrKuXiPZ3De%F z5DPwt{`rd&GxQ{#k(9!{|K=>8ee<)Hf4D zGXUqyI)0Y6EB^;M>pvIshpu+aTa@)q231-x4rg0Fc4;T>mx1)0$z8)tn$#&4x(N@b zJ(*n4SbPxh!mBoJ>8a14o0Z4G$9`oHTX~9FjY$w0YCyzOpup>HmC*eQm6|6fO$L_V zaD1U80a*+fW{@NZgkR`tS(O#)$(4-76hIJQJ6y$-KewZ8=c-+;)OA14e&$Mt1>MW} z0X7@eeNg|!SL1W0Nc>WT2W3cGV$8MlrS+*1Lys88n0GR}Cqi>fj7R$xwVq!4R18zL=Ktb5*<|mzS`$F(q>J*-K4CNT22KB`geTl%QXE zcv9bMoDQX}B~DI@l_O}vcK3aUcsAPFLdtDxX{&V>LG91ENAw%X37FbcBlSnryfav7 zT2;Z!T36oc022a3>g3)>S?^J^Ls@b-bQYFy&v%TxWJ9{ZJwL4>3$0}=PP99A`9Y+F zHtI~u<{q1;Ik8g8%FHTDnm}Js^UA?B0SVcMmMxp5he~bg;XRh4Ltx9S>7e1ej{ZWX zU@N-oF<*y-v*~0IRHM&jG+OCxUdmiQ(ZLV@-WC`A`1Hj)^3A7M5teis_kaMkcTS@{ z>3rh1Oa1nfpq)m26^j!K3Yt6pJc~=EHAOuXyQFIvE5>+C4l*`j+%&;1P((lP$MLr{ zDwZ{27QHof(~>ijplw-P_}N3BJ=1?Sr~MlST^Dm7CpQyqq;~91UQy8xf7lGXV3^#g z*dUsK*a@VZ*l5fNL!@uKs^x9%7xFi~b&XaOiv5o1`~Wh-S|jCg`~~cv(Xe+s$(!{L zHr|9vcxIbawXPpXTp_mrP+P>3H>j#$m09^WM(2iGPj2Vdy>&(OJEv~8HgR0}(bP6a zoYV5fGovL$lU`zM?7-{;N8gOWR}-6i(lVN>0u5E|VfhpP&;xd*lwG*3b+k)Ez&vJJ zEz+j=<#e#!d_hulJ_Fqvj1Ti1LgU@1DvM3;9mrw)xWkP@$Co$*Jkno&Z#40WAkVqH z!iX!q5s@4`QPLJsE8XtwW>U^ZruF7-4xVVuXOV09^0o0bqg%@!S63yIw+U|%CAPf# zBXsE&@vowO)#sn6z}oMw&$y5K|ayt<>yKJjYUNk3O^kmJpZ zMe*+~+-g|c&kLwX1}8{!hXsnpZA=G?(xN%Zb?^*7WbpJ)WJ9t&_5o>eRd!Ai%MSz7hWGrjI~* z!P;*Q|L-&DFX;<*x5$i`m$n%Ml|(@O{>`dj{{qUw!N=h%hKyeM}oY!65-%e@$xc+r5-1H5A3^5PxO* zf2Rl@?YoWxPyPBt3UEjmw_~I0m4;JAeBH}~H~VzM&@f~5ns$FpQEDDdTmsG}bamdg z{dQ}2In;)%FRolhO{dGoTry78j~|j%a?@Z-%(3{$x<@q7f>#!`RN-k(r&ZiZu~gmc z=&m=RPn=o&N|24NS|!x<1X4A;?t~nT`FijXXDa#JEiP@brM)M&D{tvbyju+L;zuURu<_wj#dX<{M2ja=W6WU(u(ly`~ zP0F5$HVP<@*hiT@Lb3918xt zLFccMerKG2S{VI;lrcq`nX=4sO;C*Mf5^zpy0~aInONC&rrXTev@y?3pNFQ#gxWjP zSK>yuOXGv4h@EbF@_2REDC;v;#E(?Wmc~AHvZSj}NC)R=oQchXwQM$iQ&dIX9FPugaLbJpx`cJK9ZEgh(^_r1`CVwk zFJYux2Zuj)ErmfBq=~e(-!b!B9s^xz_u%YVSYYLF6YqkNKyz4mjeJyVX98%Cs{54R zTwITwTYZw;&{zrd1AsMNcTF0w`y9#sc-^;;NXrW&k9h5?4X@wyMlk>BE&C%_A-(fy z`gog` Date: Mon, 13 Nov 2023 13:51:15 +0000 Subject: [PATCH 019/309] fix: linter errors in README --- README.md | 55 +++++++++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c1a9b10c..ef16a9e0 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ # Torrust Index -[![container_wf_b]][container_wf] [![coverage_wf_b]][coverage_wf] [![deployment_wf_b]][deployment_wf] [![testing_wf_b]][testing_wf] +[![container_wf_b]][container_wf] [![coverage_wf_b]][coverage_wf] [![deployment_wf_b]][deployment_wf] [![testing_wf_b]][testing_wf] [![labels_wf_b]][labels_wf] -__Torrust Index__, is a library for [BitTorrent][bittorrent] Files. Written in [Rust Language][rust] with the [axum] web framework. ___This index aims to be respectful to established standards, (both [formal][BEP 00] and [otherwise][torrent_source_felid]).___ +__Torrust Index__ is a library for [BitTorrent][bittorrent] Files. Written in [Rust Language][rust] with the [Axum] web framework. ___This index aims to be respectful to established standards, (both [formal][BEP 00] and [otherwise][torrent_source_felid]).___ > This is a [Torrust][torrust] project and is in active development. It is community supported as well as sponsored by [Nautilus Cyberneering][nautilus]. ## About -The core purpose of a [BitTorrent][bittorrent] Index is to maintain a database that connects torrent files with useful metadata. Allowing a community of users to keep track their torrents in a well organized and informative manner. -The __Torrust Index__ serves a [high-level api][api] for our [Torrust Index GUI][gui] client. It also connects to the [management api][api_tracker] of our [Torrust Tracker][tracker], to provide statistic and whitelisting functionally. +The core purpose of a [BitTorrent][bittorrent] Index is to maintain a database that connects torrent files with useful metadata. Allowing a community of users to keep track of their torrents in a well-organized and informative manner. + +The __Torrust Index__ serves as a [high-level API][API] for our [Torrust Index GUI][gui] client. It also connects to the [management api][api_tracker] of our [Torrust Tracker][tracker], to provide statistics and whitelisting functionally. ![Torrust Index Architecture](./docs/images/torrust-index-architecture.jpg) ## Key Features -** + - [x] High Quality and Modern Rust Codebase. - [x] [Documentation][docs] Generated from Code Comments. - [x] [Comprehensive Suit][coverage] of Unit and Functional Tests. @@ -25,30 +26,33 @@ The __Torrust Index__ serves a [high-level api][api] for our [Torrust Index GUI] ## Getting Started ### Upgrading + If you are using `Version 1` of `torrust-tracker-backend`, please view our [upgrading guide][upgrade.md]. ### Container Version The Torrust Index is [deployed to DockerHub][dockerhub], you can run a demo immediately with the following commands: -#### Docker: +#### Docker ```sh docker run -it torrust/index:develop ``` + > Please read our [container guide][containers.md] for more information. -#### Podman: +#### Podman ```sh podman run -it torrust/index:develop ``` + > Please read our [container guide][containers.md] for more information. ### Development Version -- Please assure you have the ___[latest stable (or nightly) version of rust][rust]___. -- Please assure that you computer has enough ram. ___Recommended 16GB.___ +- Please assure you have the ___[latest stable (or nightly) version of Rust][Rust]___. +- Please assure that your computer has enough RAM. ___Recommended 16GB.___ #### Checkout, Test and Run: @@ -67,7 +71,8 @@ cargo test --tests --benches --examples --workspace --all-targets --all-features # Run the index: cargo run ``` -#### Customization: + +#### Customization ```sh # Copy the default configuration into the standard location: @@ -88,7 +93,7 @@ _Optionally, you may choose to supply the entire configuration as an environment TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") cargo run ``` -_For deployment you __should__ override the `tracker_api_token` by using an environmental variable:_ +_For deployment, you __should__ override the `tracker_api_token` by using an environmental variable:_ ```sh # Please use the secret that you generated for the torrust-tracker configuration. @@ -101,6 +106,7 @@ TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") \ > Please view our [crate documentation][docs] for more detailed instructions. ### Services + The following services are provided by the default configuration: - API @@ -111,14 +117,15 @@ The following services are provided by the default configuration: - [API (Version 1)][api] ## Contributing + We are happy to support and welcome new people to our project. Please consider our [contributor guide][guide.md].
-This is an open-source community supported project. We welcome contributions from the community! +This is an open-source community-supported project. We welcome contributions from the community! __How can you contribute?__ - Bug reports and feature requests. - Code contributions. You can start by looking at the issues labeled "[good first issues]". -- Documentation improvements. Check the [documentation][docs] and [API documentation][api] for typos, errors, or missing information. +- Documentation improvements. Check the [documentation][docs] and [API documentation][API] for typos, errors, or missing information. - Participation in the community. You can help by answering questions in the [discussions]. ## License @@ -138,11 +145,13 @@ Some files include explicit copyright notices and/or license notices. For prosperity, versions of Torrust Tracker that are older than five years are automatically granted the [MIT-0][MIT_0] license in addition to the existing [AGPL-3.0-only][AGPL_3_0] license. ## Contributor Agreement + The copyright of the Torrust Tracker is retained by the respective authors. -**Contributors agree:** -- That all their contributions be granted a license(s) **compatible** with the [Torrust Trackers License](#License). -- That all contributors signal **clearly** and **explicitly** any other compilable licenses if they are not: *[AGPL-3.0-only with the legacy MIT-0 exception](#License)*. +**Contributors agree that:** + +- All their contributions be granted a license(s) __compatible__ with the [Torrust Trackers License](#license). +- All contributors signal __clearly__ and __explicitly__ any other compilable licenses if they are not: __[AGPL-3.0-only with the legacy MIT-0 exception](#license)__. **The Torrust-Tracker project has no copyright assignment agreement.** @@ -152,8 +161,6 @@ _We kindly ask you to take time and consider The Torrust Project [Contributor Ag This project was a joint effort by [Nautilus Cyberneering GmbH][nautilus] and [Dutch Bits]. - - [container_wf]: ../../actions/workflows/container.yaml [container_wf_b]: ../../actions/workflows/container.yaml/badge.svg [coverage_wf]: ../../actions/workflows/coverage.yaml @@ -162,11 +169,12 @@ This project was a joint effort by [Nautilus Cyberneering GmbH][nautilus] and [D [deployment_wf_b]: ../../actions/workflows/deployment.yaml/badge.svg [testing_wf]: ../../actions/workflows/testing.yaml [testing_wf_b]: ../../actions/workflows/testing.yaml/badge.svg +[labels_wf]: ../../actions/workflows/labels.yaml +[labels_wf_b]: ../../actions/workflows/labels.yaml/badge.svg [bittorrent]: http://bittorrent.org/ [rust]: https://www.rust-lang.org/ [axum]: https://github.com/tokio-rs/axum -[newtrackon]: https://newtrackon.com/ [coverage]: https://app.codecov.io/gh/torrust/torrust-index [torrust]: https://torrust.com/ @@ -178,12 +186,6 @@ This project was a joint effort by [Nautilus Cyberneering GmbH][nautilus] and [D [torrent_source_felid]: https://github.com/qbittorrent/qBittorrent/discussions/19406 [BEP 00]: https://www.bittorrent.org/beps/bep_0000.html -[BEP 03]: https://www.bittorrent.org/beps/bep_0003.html -[BEP 07]: https://www.bittorrent.org/beps/bep_0007.html -[BEP 15]: https://www.bittorrent.org/beps/bep_0015.html -[BEP 23]: https://www.bittorrent.org/beps/bep_0023.html -[BEP 27]: https://www.bittorrent.org/beps/bep_0027.html -[BEP 48]: https://www.bittorrent.org/beps/bep_0048.html [containers.md]: ./docs/containers.md [upgrade.md]: ./upgrades/from_v1_0_0_to_v2_0_0/README.md @@ -204,6 +206,3 @@ This project was a joint effort by [Nautilus Cyberneering GmbH][nautilus] and [D [nautilus]: https://github.com/orgs/Nautilus-Cyberneering/ [Dutch Bits]: https://dutchbits.nl -[Naim A.]: https://github.com/naim94a/udpt -[greatest-ape]: https://github.com/greatest-ape/aquatic -[Power2All]: https://github.com/power2all From b93564c3e52c21a909511ad9682b863c8a142592 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 13 Nov 2023 16:25:54 +0000 Subject: [PATCH 020/309] fix: [#362] rename docker image From: `torrust/index-backend:develop` To: `torrust/index:develop` --- .github/workflows/container.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/container.yaml b/.github/workflows/container.yaml index 84ac4200..8a6e4a82 100644 --- a/.github/workflows/container.yaml +++ b/.github/workflows/container.yaml @@ -111,7 +111,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - "${{ secrets.DOCKER_HUB_USERNAME }}/${{secrets.DOCKER_HUB_REPOSITORY_NAME }}" + "${{ vars.DOCKER_HUB_USERNAME }}/${{vars.DOCKER_HUB_REPOSITORY_NAME }}" tags: | type=ref,event=branch @@ -119,7 +119,7 @@ jobs: name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} + username: ${{ vars.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - id: setup @@ -149,7 +149,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - "${{ secrets.DOCKER_HUB_USERNAME }}/${{secrets.DOCKER_HUB_REPOSITORY_NAME }}" + "${{ vars.DOCKER_HUB_USERNAME }}/${{vars.DOCKER_HUB_REPOSITORY_NAME }}" tags: | type=semver,value=${{ needs.context.outputs.version }},pattern={{raw}} type=semver,value=${{ needs.context.outputs.version }},pattern={{version}} @@ -160,7 +160,7 @@ jobs: name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} + username: ${{ vars.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - id: setup From 03943efcd353e7653d0740337c939340e9ebcfe8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 12:52:59 +0000 Subject: [PATCH 021/309] fix: [#342] fix en vars to run E2E tests --- contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 2 -- contrib/dev-tools/container/e2e/run-e2e-tests.sh | 4 ++-- contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh | 2 -- tests/e2e/mod.rs | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index 5124e5dc..c0114ebf 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -4,12 +4,10 @@ TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - # Index TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_IDX_BACK_MYSQL_DATABASE="torrust_index_e2e_testing" \ - # Tracker TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.mysql.toml) \ TORRUST_TRACKER_DATABASE_DRIVER="mysql" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ diff --git a/contrib/dev-tools/container/e2e/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/run-e2e-tests.sh index 04c3b679..891fba0a 100755 --- a/contrib/dev-tools/container/e2e/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/run-e2e-tests.sh @@ -60,7 +60,7 @@ sleep 20s docker ps # Run E2E tests with shared app instance -TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 @@ -91,7 +91,7 @@ echo "Creating MySQL database $MYSQL_DATABASE for for E2E testing ..." mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" # Run E2E tests with shared app instance -TORRUST_IDX_BACK_E2E_SHARED=true TORRUST_IDX_BACK_E2E_CONFIG_PATH="./share/default/config/index.container.mysql.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.mysql.toml" cargo test || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index e5c67632..7eb7f16f 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -4,11 +4,9 @@ TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) docker compose build USER_ID=${USER_ID:-1000} \ - # Index TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ - # Tracker TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index 07c53151..ff350454 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -4,14 +4,14 @@ //! against an in-process server (isolated). //! //! If you want to run the tests against an out-of-process server, you need to -//! set the environment variable `TORRUST_IDX_BACK_E2E_SHARED` to `true`. +//! set the environment variable `TORRUST_INDEX_E2E_SHARED` to `true`. //! //! > **NOTICE**: The server must be running before running the tests. The //! server url is hardcoded to `http://localhost:3001` for now. We are planning //! to make it configurable in the future via a environment variable. //! //! ```text -//! TORRUST_IDX_BACK_E2E_SHARED=true cargo test +//! TORRUST_INDEX_E2E_SHARED=true cargo test //! ``` //! //! If you want to run the tests against an isolated server, you need to execute From 1cce823d12ada3e5995f87bcca4f4e9d5f5854de Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 13:46:45 +0000 Subject: [PATCH 022/309] fix: [#342] index container not running for E2E tests --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index a7f9e881..3ad56e74 100644 --- a/compose.yaml +++ b/compose.yaml @@ -5,7 +5,7 @@ services: build: context: . dockerfile: ./Containerfile - target: debug + target: release tty: true environment: - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} From e78607dbb6deb3d12c626636f419be52f93e0ff2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 14:51:31 +0000 Subject: [PATCH 023/309] fix: [#342] SQLite data file path inside the container for E2E tests The SQLite file path inside the container is not the same as on hte host: In the container: `sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc` It's an absolute path. From the host: `sqlite://./storage/index/lib/database/sqlite3.db?mode=rwc` It's a relative path to where the test are being executed (root project path). TODO: inject as an env var when running the E2E tests isntead of parsing the config file. ``` TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.sqlite3.toml" cargo test ``` --- tests/e2e/environment.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 73652725..1c20ca26 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -114,7 +114,7 @@ impl TestEnv { return match maybe_db_driver { Ok(db_driver) => match db_driver { - database::Driver::Sqlite3 => Some(db_path), + database::Driver::Sqlite3 => Some(Self::overwrite_sqlite_path(&db_path, "./storage/index/lib")), database::Driver::Mysql => Some(Self::overwrite_mysql_host(&db_path, "localhost")), }, Err(_) => None, @@ -127,7 +127,26 @@ impl TestEnv { } } - /// It overrides the "Host" in a `SQLx` database connection URL. For example: + /// It overrides the `SQLite` file path in a `SQLx` database connection URL. + /// For example: + /// + /// For: + /// + /// `sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc`. + /// + /// It changes the `mysql` host name to `localhost`: + /// + /// `sqlite://./storage/index/lib/database/sqlite3.db?mode=rwc`. + /// + /// For E2E tests, we use docker compose. Inside the container, the + /// `SQLite` file path is not the same as the host path. + fn overwrite_sqlite_path(db_path: &str, host_path: &str) -> String { + // todo: inject value with env var + db_path.replace("/var/lib/torrust/index", host_path) + } + + /// It overrides the "Host" in a `SQLx` database connection URL. + /// For example: /// /// For: /// @@ -138,10 +157,11 @@ impl TestEnv { /// `mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing`. /// /// For E2E tests, we use docker compose, internally the index connects to - /// the database using the "mysql" host, which is the docker compose service - /// name, but tests connects directly to the localhost since the `MySQL` - /// is exposed to the host. + /// the `MySQL` database using the "mysql" host, which is the docker compose + /// service name, but tests connects directly to the localhost since the + /// `MySQL` is exposed to the host. fn overwrite_mysql_host(db_path: &str, new_host: &str) -> String { + // todo: inject value with env var db_path.replace("@mysql:", &format!("@{new_host}:")) } From f8aa238ea1b4ca772ee9392e0fd791d6d4e32cac Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 15:41:39 +0000 Subject: [PATCH 024/309] fix: [#342] broken E2E tests after renaming Category::category_id field Some E2E tests were failing becuase a category field in a response was renamed. I have reestablished the old name `category_id`. --- .github/workflows/testing.yaml | 10 ++- .../e2e/{ => mysql}/run-e2e-tests.sh | 23 +------ .../dev-tools/container/e2e/sqlite/install.sh | 13 ++++ .../container/e2e/sqlite/run-e2e-tests.sh | 67 +++++++++++++++++++ contrib/dev-tools/container/install.sh | 4 -- contrib/dev-tools/init/install-local.sh | 1 + tests/common/contexts/torrent/asserts.rs | 5 +- tests/common/contexts/torrent/responses.rs | 2 +- .../web/api/v1/contexts/torrent/contract.rs | 2 +- 9 files changed, 95 insertions(+), 32 deletions(-) rename contrib/dev-tools/container/e2e/{ => mysql}/run-e2e-tests.sh (76%) create mode 100755 contrib/dev-tools/container/e2e/sqlite/install.sh create mode 100755 contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh delete mode 100755 contrib/dev-tools/container/install.sh diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index d64e9110..523370a3 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -163,6 +163,10 @@ jobs: name: Make Build Clean run: cargo clean - - id: test - name: Run Integration Tests - run: ./contrib/dev-tools/container/e2e/run-e2e-tests.sh + - id: test-sqlite + name: Run Integration Tests (SQLite) + run: ./contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh + + - id: test-mysql + name: Run Integration Tests (MySQL) + run: ./contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh \ No newline at end of file diff --git a/contrib/dev-tools/container/e2e/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh similarity index 76% rename from contrib/dev-tools/container/e2e/run-e2e-tests.sh rename to contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 891fba0a..c33d3f17 100755 --- a/contrib/dev-tools/container/e2e/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -10,6 +10,7 @@ TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID export TORRUST_IDX_BACK_USER_UID export TORRUST_TRACKER_USER_UID +# todo: remove duplicate funtion wait_for_container_to_be_healthy() { local container_name="$1" local max_retries="$2" @@ -42,28 +43,6 @@ cargo install imdl || exit 1 # Install app (no docker) that will run the test suite against the E2E testing # environment (in docker). cp .env.local .env || exit 1 -./contrib/dev-tools/init/install-local.sh || exit 1 - -# TEST USING SQLITE -echo "Running E2E tests using SQLite ..." - -# Start E2E testing environment -./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 - -wait_for_container_to_be_healthy torrust-mysql-1 10 3 -# todo: implement healthchecks for tracker and index and wait until they are healthy -#wait_for_container torrust-tracker-1 10 3 -#wait_for_container torrust-idx-back-1 10 3 -sleep 20s - -# Just to make sure that everything is up and running -docker ps - -# Run E2E tests with shared app instance -TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 - -# Stop E2E testing environment -./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 # TEST USING MYSQL echo "Running E2E tests using MySQL ..." diff --git a/contrib/dev-tools/container/e2e/sqlite/install.sh b/contrib/dev-tools/container/e2e/sqlite/install.sh new file mode 100755 index 00000000..7c37a06f --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# This script is only intended to be used for local development or testing environments. + +# Generate storage directory if it does not exist +mkdir -p ./storage/index/lib/database + +# Generate the sqlite database if it does not exist +if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then + # todo: it should get the path from tracker.toml and only do it when we use sqlite + sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" +fi + diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh new file mode 100755 index 00000000..9506bc91 --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +CURRENT_USER_NAME=$(whoami) +CURRENT_USER_ID=$(id -u) +echo "User name: $CURRENT_USER_NAME" +echo "User id: $CURRENT_USER_ID" + +TORRUST_IDX_BACK_USER_UID=$CURRENT_USER_ID +TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID +export TORRUST_IDX_BACK_USER_UID +export TORRUST_TRACKER_USER_UID + +# todo: remove duplicate funtion +wait_for_container_to_be_healthy() { + local container_name="$1" + local max_retries="$2" + local retry_interval="$3" + local retry_count=0 + + while [ $retry_count -lt "$max_retries" ]; do + container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")" + if [ "$container_health" != "{}" ]; then + container_status="$(echo "$container_health" | jq -r '.Status')" + if [ "$container_status" == "healthy" ]; then + echo "Container $container_name is healthy" + return 0 + fi + fi + + retry_count=$((retry_count + 1)) + echo "Waiting for container $container_name to become healthy (attempt $retry_count of $max_retries)..." + sleep "$retry_interval" + done + + echo "Timeout reached, container $container_name is not healthy" + return 1 +} + +# Install tool to create torrent files. +# It's needed by some tests to generate and parse test torrent files. +cargo install imdl || exit 1 + +# Install app (no docker) that will run the test suite against the E2E testing +# environment (in docker). +cp .env.local .env || exit 1 +./contrib/dev-tools/container/e2e/sqlite/install.sh || exit 1 + +# TEST USING SQLITE +echo "Running E2E tests using SQLite ..." + +# Start E2E testing environment +./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 + +wait_for_container_to_be_healthy torrust-mysql-1 10 3 +# todo: implement healthchecks for tracker and index and wait until they are healthy +#wait_for_container torrust-tracker-1 10 3 +#wait_for_container torrust-idx-back-1 10 3 +sleep 20s + +# Just to make sure that everything is up and running +docker ps + +# Run E2E tests with shared app instance +TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 + +# Stop E2E testing environment +./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/container/install.sh b/contrib/dev-tools/container/install.sh deleted file mode 100755 index 5493b7c0..00000000 --- a/contrib/dev-tools/container/install.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -./contrib/dev-tools/container/e2e/bin/build.sh -./contrib/dev-tools/init/install-local.sh diff --git a/contrib/dev-tools/init/install-local.sh b/contrib/dev-tools/init/install-local.sh index 2368de3b..7c37a06f 100755 --- a/contrib/dev-tools/init/install-local.sh +++ b/contrib/dev-tools/init/install-local.sh @@ -10,3 +10,4 @@ if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then # todo: it should get the path from tracker.toml and only do it when we use sqlite sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" fi + diff --git a/tests/common/contexts/torrent/asserts.rs b/tests/common/contexts/torrent/asserts.rs index 261df54e..d0f1a8cf 100644 --- a/tests/common/contexts/torrent/asserts.rs +++ b/tests/common/contexts/torrent/asserts.rs @@ -15,7 +15,10 @@ pub fn assert_expected_torrent_details(torrent: &TorrentDetails, expected_torren ("info_hash", torrent.info_hash == expected_torrent.info_hash), ("title", torrent.title == expected_torrent.title), ("description", torrent.description == expected_torrent.description), - ("category.category_id", torrent.category.id == expected_torrent.category.id), + ( + "category.category_id", + torrent.category.category_id == expected_torrent.category.category_id, + ), ("category.name", torrent.category.name == expected_torrent.category.name), ("file_size", torrent.file_size == expected_torrent.file_size), ("seeders", torrent.seeders == expected_torrent.seeders), diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index b1ef0882..929d31f9 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -76,7 +76,7 @@ pub struct TorrentDetails { #[derive(Deserialize, PartialEq, Debug)] pub struct Category { - pub id: CategoryId, + pub category_id: CategoryId, pub name: String, pub num_torrents: u64, } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 59f487b3..4b590226 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -182,7 +182,7 @@ mod for_guests { title: test_torrent.index_info.title.clone(), description: test_torrent.index_info.description, category: Category { - id: software_predefined_category_id(), + category_id: software_predefined_category_id(), name: test_torrent.index_info.category, num_torrents: 19, // Ignored in assertion }, From 31351fa20ff328df665d469f260a3f1b1adac591 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 15:55:58 +0000 Subject: [PATCH 025/309] fix: [#342] disable clippy warning until we reestablish the E2E tests. --- Cargo.lock | 1 + Cargo.toml | 1 + project-words.txt | 1 + tests/common/contexts/torrent/responses.rs | 12 +++++++++++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8c94b306..90e50a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3008,6 +3008,7 @@ dependencies = [ "rand_core", "regex", "reqwest", + "rustversion", "serde", "serde_bencode", "serde_bytes", diff --git a/Cargo.toml b/Cargo.toml index 802cadaa..cc884b39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ pbkdf2 = { version = "0", features = ["simple"] } rand_core = { version = "0", features = ["std"] } regex = "1" reqwest = { version = "0", features = ["json", "multipart"] } +rustversion = "1.0.14" serde = { version = "1", features = ["rc"] } serde_bencode = "0" serde_bytes = "0" diff --git a/project-words.txt b/project-words.txt index 74669479..b8755b5a 100644 --- a/project-words.txt +++ b/project-words.txt @@ -68,6 +68,7 @@ rowid RUSTDOCFLAGS RUSTFLAGS rustfmt +rustversion serde sgxj singlepart diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index 929d31f9..a6fd4946 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -74,9 +74,19 @@ pub struct TorrentDetails { pub encoding: Option, } +#[rustversion::stable] #[derive(Deserialize, PartialEq, Debug)] pub struct Category { - pub category_id: CategoryId, + pub category_id: CategoryId, // todo: rename to `id` + pub name: String, + pub num_torrents: u64, +} + +#[rustversion::nightly] +#[derive(Deserialize, PartialEq, Debug)] +#[allow(clippy::struct_field_names)] +pub struct Category { + pub category_id: CategoryId, // todo: rename to `id` pub name: String, pub num_torrents: u64, } From d92443d3fc9e30e12a3e90d430699f09cac91d20 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 Nov 2023 17:16:59 +0000 Subject: [PATCH 026/309] fix: [#342] E2E test execution with MySQL The Tracker configuration was using MySQL instead of SQLite but the database was not created. We can use SQLite for the Tracker even if in this case we are testing with MySQL becuase E2E test are for the Index not the tracker. --- .github/workflows/testing.yaml | 2 +- .../dev-tools/container/e2e/mysql/e2e-env-up.sh | 5 +++-- contrib/dev-tools/container/e2e/mysql/install.sh | 15 +++++++++++++++ .../container/e2e/mysql/run-e2e-tests.sh | 11 ++--------- contrib/dev-tools/container/e2e/sqlite/install.sh | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) create mode 100755 contrib/dev-tools/container/e2e/mysql/install.sh diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 523370a3..21a11b23 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -169,4 +169,4 @@ jobs: - id: test-mysql name: Run Integration Tests (MySQL) - run: ./contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh \ No newline at end of file + run: ./contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index c0114ebf..73cd2fc6 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -8,7 +8,8 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_IDX_BACK_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.mysql.toml) \ - TORRUST_TRACKER_DATABASE_DRIVER="mysql" \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ + TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ docker compose up -d + diff --git a/contrib/dev-tools/container/e2e/mysql/install.sh b/contrib/dev-tools/container/e2e/mysql/install.sh new file mode 100755 index 00000000..de8e1cf3 --- /dev/null +++ b/contrib/dev-tools/container/e2e/mysql/install.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# This script is only intended to be used for E2E testing environment. + +# Database credentials +MYSQL_USER="root" +MYSQL_PASSWORD="root_secret_password" +MYSQL_HOST="127.0.0.1" +MYSQL_DATABASE="torrust_index_e2e_testing" + +# Create the MySQL database for the index. Assumes MySQL client is installed. +# The docker compose configuration already creates the database the first time +# the container is created. +echo "Creating MySQL database $MYSQL_DATABASE for for E2E testing ..." +MYSQL_PWD=$MYSQL_PASSWORD mysql -h $MYSQL_HOST -u $MYSQL_USER -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index c33d3f17..a9e275d2 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -59,15 +59,8 @@ sleep 20s # Just to make sure that everything is up and running docker ps -# Database credentials -MYSQL_USER="root" -MYSQL_PASSWORD="root_secret_password" -MYSQL_HOST="localhost" -MYSQL_DATABASE="torrust_index_e2e_testing" - -# Create the MySQL database for the index. Assumes MySQL client is installed. -echo "Creating MySQL database $MYSQL_DATABASE for for E2E testing ..." -mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" +# Install MySQL database for the index +./contrib/dev-tools/container/e2e/mysql/install.sh || exit 1 # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.mysql.toml" cargo test || exit 1 diff --git a/contrib/dev-tools/container/e2e/sqlite/install.sh b/contrib/dev-tools/container/e2e/sqlite/install.sh index 7c37a06f..a1b800e6 100755 --- a/contrib/dev-tools/container/e2e/sqlite/install.sh +++ b/contrib/dev-tools/container/e2e/sqlite/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -# This script is only intended to be used for local development or testing environments. +# This script is only intended to be used for E2E testing environment. # Generate storage directory if it does not exist mkdir -p ./storage/index/lib/database From 5b1a3c18ebdfd5b1c1e5c6f0e97c3088084b387b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 Nov 2023 10:03:10 +0000 Subject: [PATCH 027/309] refactor: [#361] rename env vars to follow repo renaming - Repo `torrust-index-backend` was renamed to `torrust-index`. - We are not using the `IDX` abbreviation for `INDEX`. - `USER_ID` was renamed when @da2ce7 did the container overhaul. ``` TORRUST_INDEX_BACK_CORS_PERMISSIVE -> TORRUST_INDEX_API_CORS_PERMISSIVE TORRUST_IDX_BACK_CONFIG -> TORRUST_INDEX_CONFIG TORRUST_IDX_BACK_USER_UID -> USER_ID TORRUST_IDX_BACK_RUN_AS_USER -> TORRUST_INDEX_RUN_AS_USER TORRUST_IDX_BACK_MYSQL_DATABASE -> TORRUST_INDEX_MYSQL_DATABASE ``` --- .env.local | 5 +++-- compose.yaml | 2 +- contrib/dev-tools/container/build.sh | 12 ++++++------ contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/mysql/run-e2e-tests.sh | 4 ++-- .../dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 4 ++-- contrib/dev-tools/container/run.sh | 8 ++++---- src/bootstrap/config.rs | 2 +- src/lib.rs | 12 ++++++------ 9 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.env.local b/.env.local index 8d5f8e89..faf5afb2 100644 --- a/.env.local +++ b/.env.local @@ -1,6 +1,7 @@ DATABASE_URL=sqlite://storage/database/data.db?mode=rwc -TORRUST_IDX_BACK_CONFIG= -TORRUST_IDX_BACK_USER_UID=1000 +TORRUST_INDEX_CONFIG= +USER_ID=1000 TORRUST_TRACKER_CONFIG= TORRUST_TRACKER_DATABASE_DRIVER=sqlite3 TORRUST_TRACKER_API_ADMIN_TOKEN=MyAccessToken + diff --git a/compose.yaml b/compose.yaml index 3ad56e74..fa968c41 100644 --- a/compose.yaml +++ b/compose.yaml @@ -67,7 +67,7 @@ services: environment: - MYSQL_ROOT_HOST=% - MYSQL_ROOT_PASSWORD=root_secret_password - - MYSQL_DATABASE=${TORRUST_IDX_BACK_MYSQL_DATABASE:-torrust_index_e2e_testing} + - MYSQL_DATABASE=${TORRUST_INDEX_MYSQL_DATABASE:-torrust_index_e2e_testing} - MYSQL_USER=db_user - MYSQL_PASSWORD=db_user_secret_password networks: diff --git a/contrib/dev-tools/container/build.sh b/contrib/dev-tools/container/build.sh index 21be00a3..e4dafebd 100755 --- a/contrib/dev-tools/container/build.sh +++ b/contrib/dev-tools/container/build.sh @@ -1,13 +1,13 @@ #!/bin/bash -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} -TORRUST_IDX_BACK_RUN_AS_USER=${TORRUST_IDX_BACK_RUN_AS_USER:-appuser} +USER_ID=${USER_ID:-1000} +TORRUST_INDEX_RUN_AS_USER=${TORRUST_INDEX_RUN_AS_USER:-appuser} echo "Building docker image ..." -echo "TORRUST_IDX_BACK_USER_UID: $TORRUST_IDX_BACK_USER_UID" -echo "TORRUST_IDX_BACK_RUN_AS_USER: $TORRUST_IDX_BACK_RUN_AS_USER" +echo "USER_ID: $USER_ID" +echo "TORRUST_INDEX_RUN_AS_USER: $TORRUST_INDEX_RUN_AS_USER" docker build \ - --build-arg UID="$TORRUST_IDX_BACK_USER_UID" \ - --build-arg RUN_AS_USER="$TORRUST_IDX_BACK_RUN_AS_USER" \ + --build-arg UID="$USER_ID" \ + --build-arg RUN_AS_USER="$TORRUST_INDEX_RUN_AS_USER" \ -t torrust-index . diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index 73cd2fc6..e2af81f8 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -7,7 +7,7 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ - TORRUST_IDX_BACK_MYSQL_DATABASE="torrust_index_e2e_testing" \ + TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index a9e275d2..bee61a75 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -5,9 +5,9 @@ CURRENT_USER_ID=$(id -u) echo "User name: $CURRENT_USER_NAME" echo "User id: $CURRENT_USER_ID" -TORRUST_IDX_BACK_USER_UID=$CURRENT_USER_ID +USER_ID=$CURRENT_USER_ID TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID -export TORRUST_IDX_BACK_USER_UID +export USER_ID export TORRUST_TRACKER_USER_UID # todo: remove duplicate funtion diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 9506bc91..bfd6bbf1 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -5,9 +5,9 @@ CURRENT_USER_ID=$(id -u) echo "User name: $CURRENT_USER_NAME" echo "User id: $CURRENT_USER_ID" -TORRUST_IDX_BACK_USER_UID=$CURRENT_USER_ID +USER_ID=$CURRENT_USER_ID TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID -export TORRUST_IDX_BACK_USER_UID +export USER_ID export TORRUST_TRACKER_USER_UID # todo: remove duplicate funtion diff --git a/contrib/dev-tools/container/run.sh b/contrib/dev-tools/container/run.sh index 19df5d3a..c967f788 100755 --- a/contrib/dev-tools/container/run.sh +++ b/contrib/dev-tools/container/run.sh @@ -1,11 +1,11 @@ #!/bin/bash -TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} -TORRUST_IDX_BACK_CONFIG=$(cat config.toml) +USER_ID=${USER_ID:-1000} +TORRUST_INDEX_CONFIG=$(cat config.toml) docker run -it \ - --user="$TORRUST_IDX_BACK_USER_UID" \ + --user="$USER_ID" \ --publish 3001:3001/tcp \ - --env TORRUST_IDX_BACK_CONFIG="$TORRUST_IDX_BACK_CONFIG" \ + --env TORRUST_INDEX_CONFIG="$TORRUST_INDEX_CONFIG" \ --volume "$(pwd)/storage":"/app/storage" \ torrust-index diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 49f661ac..e6910a33 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -20,7 +20,7 @@ pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/index.development.sqlite3.toml"; /// If present, CORS will be permissive. -pub const ENV_VAR_CORS_PERMISSIVE: &str = "TORRUST_INDEX_BACK_CORS_PERMISSIVE"; +pub const ENV_VAR_CORS_PERMISSIVE: &str = "TORRUST_INDEX_API_CORS_PERMISSIVE"; /// It loads the application configuration from the environment. /// diff --git a/src/lib.rs b/src/lib.rs index 397dc04f..039bdfeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,9 +110,9 @@ //! //! ```text //! mkdir -p ./storage/database \ -//! && export TORRUST_IDX_BACK_USER_UID=1000 \ +//! && export USER_ID=1000 \ //! && docker run -it \ -//! --user="$TORRUST_IDX_BACK_USER_UID" \ +//! --user="$USER_ID" \ //! --publish 3001:3001/tcp \ //! --volume "$(pwd)/storage":"/app/storage" \ //! torrust/index @@ -204,10 +204,10 @@ //! //! For more information about configuration you can visit the documentation for the [`config`]) module. //! -//! Alternatively to the `config.toml` file you can use one environment variable `TORRUST_IDX_BACK_CONFIG` to pass the configuration to the tracker: +//! Alternatively to the `config.toml` file you can use one environment variable `TORRUST_INDEX_CONFIG` to pass the configuration to the tracker: //! //! ```text -//! TORRUST_IDX_BACK_CONFIG=$(cat config.toml) +//! TORRUST_INDEX_CONFIG=$(cat config.toml) //! cargo run //! ``` //! @@ -215,9 +215,9 @@ //! //! The env var contains the same data as the `config.toml`. It's particularly useful in you are [running the index with docker](https://github.com/torrust/torrust-index/tree/develop/docker). //! -//! > **NOTICE**: The `TORRUST_IDX_BACK_CONFIG` env var has priority over the `config.toml` file. +//! > **NOTICE**: The `TORRUST_INDEX_CONFIG` env var has priority over the `config.toml` file. //! -//! > **NOTICE**: You can also change the location for the configuration file with the `TORRUST_IDX_BACK_CONFIG_PATH` env var. +//! > **NOTICE**: You can also change the location for the configuration file with the `TORRUST_INDEX_CONFIG_PATH` env var. //! //! # Usage //! From fe44afa59b0c6a6063c6474dafde1ba6e8733389 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 Nov 2023 12:43:06 +0000 Subject: [PATCH 028/309] fix: add missing env var in compose.yaml If we do not add the env var it will not be injected in the running container. --- compose.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose.yaml b/compose.yaml index fa968c41..db66954f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,6 +8,7 @@ services: target: release tty: true environment: + - USER_ID=${USER_ID} - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - TORRUST_INDEX_TRACKER_API_TOKEN=${TORRUST_INDEX_TRACKER_API_TOKEN:-MyAccessToken} @@ -28,6 +29,7 @@ services: image: torrust/tracker:develop tty: true environment: + - USER_ID=${USER_ID} - TORRUST_TRACKER_CONFIG=${TORRUST_TRACKER_CONFIG} - TORRUST_TRACKER_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-sqlite3} - TORRUST_TRACKER_API_ADMIN_TOKEN=${TORRUST_TRACKER_API_ADMIN_TOKEN:-MyAccessToken} From 5b73255c7e86b82d417a99ad68f4118887e3536c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 Nov 2023 12:45:47 +0000 Subject: [PATCH 029/309] chore: disable shellcheck SC2016 in entryscript We do not want to replace variables. --- share/container/entry_script_sh | 1 + 1 file changed, 1 insertion(+) diff --git a/share/container/entry_script_sh b/share/container/entry_script_sh index 5f8d9d21..1afd9e6b 100644 --- a/share/container/entry_script_sh +++ b/share/container/entry_script_sh @@ -73,6 +73,7 @@ if [ -e "/usr/share/torrust/container/message" ]; then fi # Load message of the day from Profile +# shellcheck disable=SC2016 echo '[ ! -z "$TERM" -a -r /etc/motd ] && cat /etc/motd' >> /etc/profile cd /home/torrust || exit 1 From 022b74efe5abd8359909f2458c0d8f2245beb011 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 Nov 2023 12:46:42 +0000 Subject: [PATCH 030/309] chore: [#386] remove deprecated container varaibles --- Containerfile | 8 +------- contrib/dev-tools/container/build.sh | 3 --- contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh | 1 + 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Containerfile b/Containerfile index 378a9b10..4048d27d 100644 --- a/Containerfile +++ b/Containerfile @@ -98,20 +98,14 @@ COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec ARG TORRUST_INDEX_PATH_CONFIG="/etc/torrust/index/index.toml" ARG TORRUST_INDEX_DATABASE_DRIVER="sqlite3" ARG USER_ID=1000 -ARG UDP_PORT=6969 -ARG HTTP_PORT=7070 -ARG API_PORT=1212 +ARG API_PORT=3001 ENV TORRUST_INDEX_PATH_CONFIG=${TORRUST_INDEX_PATH_CONFIG} ENV TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER} ENV USER_ID=${USER_ID} -ENV UDP_PORT=${UDP_PORT} -ENV HTTP_PORT=${HTTP_PORT} ENV API_PORT=${API_PORT} ENV TZ=Etc/UTC -EXPOSE ${UDP_PORT}/udp -EXPOSE ${HTTP_PORT}/tcp EXPOSE ${API_PORT}/tcp RUN mkdir -p /var/lib/torrust/index /var/log/torrust/index /etc/torrust/index diff --git a/contrib/dev-tools/container/build.sh b/contrib/dev-tools/container/build.sh index e4dafebd..c6c28635 100755 --- a/contrib/dev-tools/container/build.sh +++ b/contrib/dev-tools/container/build.sh @@ -1,13 +1,10 @@ #!/bin/bash USER_ID=${USER_ID:-1000} -TORRUST_INDEX_RUN_AS_USER=${TORRUST_INDEX_RUN_AS_USER:-appuser} echo "Building docker image ..." echo "USER_ID: $USER_ID" -echo "TORRUST_INDEX_RUN_AS_USER: $TORRUST_INDEX_RUN_AS_USER" docker build \ --build-arg UID="$USER_ID" \ - --build-arg RUN_AS_USER="$TORRUST_INDEX_RUN_AS_USER" \ -t torrust-index . diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index 7eb7f16f..7d2d224f 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -11,3 +11,4 @@ USER_ID=${USER_ID:-1000} \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ docker compose up -d + From 1e0e416b28ece0b85e6ee25b3d023f23939e919f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 Nov 2023 17:06:46 +0000 Subject: [PATCH 031/309] refactor: [#389] extract duplicate function wait_for_container_to_be_healthy --- .../container/e2e/mysql/run-e2e-tests.sh | 29 ++----------------- .../container/e2e/sqlite/run-e2e-tests.sh | 29 ++----------------- .../wait_for_container_to_be_healthy.sh | 26 +++++++++++++++++ 3 files changed, 30 insertions(+), 54 deletions(-) create mode 100755 contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index bee61a75..4e9ab8c7 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -10,32 +10,6 @@ TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID export USER_ID export TORRUST_TRACKER_USER_UID -# todo: remove duplicate funtion -wait_for_container_to_be_healthy() { - local container_name="$1" - local max_retries="$2" - local retry_interval="$3" - local retry_count=0 - - while [ $retry_count -lt "$max_retries" ]; do - container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")" - if [ "$container_health" != "{}" ]; then - container_status="$(echo "$container_health" | jq -r '.Status')" - if [ "$container_status" == "healthy" ]; then - echo "Container $container_name is healthy" - return 0 - fi - fi - - retry_count=$((retry_count + 1)) - echo "Waiting for container $container_name to become healthy (attempt $retry_count of $max_retries)..." - sleep "$retry_interval" - done - - echo "Timeout reached, container $container_name is not healthy" - return 1 -} - # Install tool to create torrent files. # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 @@ -50,7 +24,8 @@ echo "Running E2E tests using MySQL ..." # Start E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh || exit 1 -wait_for_container_to_be_healthy torrust-mysql-1 10 3 +# Wait for conatiners to be healthy +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 # todo: implement healthchecks for tracker and index and wait until they are healthy #wait_for_container torrust-tracker-1 10 3 #wait_for_container torrust-idx-back-1 10 3 diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index bfd6bbf1..60a21c22 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -10,32 +10,6 @@ TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID export USER_ID export TORRUST_TRACKER_USER_UID -# todo: remove duplicate funtion -wait_for_container_to_be_healthy() { - local container_name="$1" - local max_retries="$2" - local retry_interval="$3" - local retry_count=0 - - while [ $retry_count -lt "$max_retries" ]; do - container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")" - if [ "$container_health" != "{}" ]; then - container_status="$(echo "$container_health" | jq -r '.Status')" - if [ "$container_status" == "healthy" ]; then - echo "Container $container_name is healthy" - return 0 - fi - fi - - retry_count=$((retry_count + 1)) - echo "Waiting for container $container_name to become healthy (attempt $retry_count of $max_retries)..." - sleep "$retry_interval" - done - - echo "Timeout reached, container $container_name is not healthy" - return 1 -} - # Install tool to create torrent files. # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 @@ -51,7 +25,8 @@ echo "Running E2E tests using SQLite ..." # Start E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 -wait_for_container_to_be_healthy torrust-mysql-1 10 3 +# Wait for conatiners to be healthy +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 # todo: implement healthchecks for tracker and index and wait until they are healthy #wait_for_container torrust-tracker-1 10 3 #wait_for_container torrust-idx-back-1 10 3 diff --git a/contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh b/contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh new file mode 100755 index 00000000..9e67a434 --- /dev/null +++ b/contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +wait_for_container_to_be_healthy() { + local container_name="$1" + local max_retries="$2" + local retry_interval="$3" + local retry_count=0 + + while [ $retry_count -lt "$max_retries" ]; do + container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")" + if [ "$container_health" != "{}" ]; then + container_status="$(echo "$container_health" | jq -r '.Status')" + if [ "$container_status" == "healthy" ]; then + echo "Container $container_name is healthy" + return 0 + fi + fi + + retry_count=$((retry_count + 1)) + echo "Waiting for container $container_name to become healthy (attempt $retry_count of $max_retries)..." + sleep "$retry_interval" + done + + echo "Timeout reached, container $container_name is not healthy" + return 1 +} \ No newline at end of file From 0e8b32b971f79e89fcf568deacda113f0013f4f6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 16 Nov 2023 17:06:05 +0000 Subject: [PATCH 032/309] ci: [#254] add container healthcheck for index service For both services: - The Index API - The Tracker Stattistics Importer (console cronjob) An API was added for the Importer. The Importer API has two endpoints: - GET /health_check -> to get the crobjob status. It only checks that is running periodically. - POST /heartbeat -> used by the cronjob to inform it's alive. And a new endpoint was added the the Index API: - GET /health_check -> to check API responsiveness. --- Containerfile | 9 +- .../container/e2e/mysql/run-e2e-tests.sh | 8 +- .../container/e2e/sqlite/run-e2e-tests.sh | 8 +- .../default/config/index.container.mysql.toml | 1 + .../config/index.container.sqlite3.toml | 1 + .../config/index.development.sqlite3.toml | 1 + src/app.rs | 37 ++--- src/bin/health_check.rs | 37 +++++ src/config.rs | 3 + src/console/mod.rs | 1 + src/console/tracker_statistics_importer.rs | 130 ++++++++++++++++++ src/lib.rs | 1 + src/web/api/v1/contexts/settings/mod.rs | 1 + src/web/api/v1/routes.rs | 11 +- tests/common/contexts/settings/mod.rs | 2 + tests/environments/isolated.rs | 3 + 16 files changed, 220 insertions(+), 34 deletions(-) create mode 100644 src/bin/health_check.rs create mode 100644 src/console/tracker_statistics_importer.rs diff --git a/Containerfile b/Containerfile index 4048d27d..fe7961da 100644 --- a/Containerfile +++ b/Containerfile @@ -85,7 +85,9 @@ COPY --from=build \ RUN cargo nextest run --workspace-remap /test/src/ --extract-to /test/src/ --no-run --archive-file /test/torrust-index.tar.zst RUN cargo nextest run --workspace-remap /test/src/ --target-dir-remap /test/src/target/ --cargo-metadata /test/src/target/nextest/cargo-metadata.json --binaries-metadata /test/src/target/nextest/binaries-metadata.json -RUN mkdir -p /app/bin/; cp -l /test/src/target/release/torrust-index /app/bin/torrust-index +RUN mkdir -p /app/bin/; \ + cp -l /test/src/target/release/torrust-index /app/bin/torrust-index; \ + cp -l /test/src/target/release/health_check /app/bin/health_check; # RUN mkdir -p /app/lib/; cp -l $(realpath $(ldd /app/bin/torrust-index | grep "libz\.so\.1" | awk '{print $3}')) /app/lib/libz.so.1 RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; chmod -R a+x /app/bin @@ -99,11 +101,13 @@ ARG TORRUST_INDEX_PATH_CONFIG="/etc/torrust/index/index.toml" ARG TORRUST_INDEX_DATABASE_DRIVER="sqlite3" ARG USER_ID=1000 ARG API_PORT=3001 +ARG IMPORTER_API_PORT=3002 ENV TORRUST_INDEX_PATH_CONFIG=${TORRUST_INDEX_PATH_CONFIG} ENV TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER} ENV USER_ID=${USER_ID} ENV API_PORT=${API_PORT} +ENV IMPORTER_API_PORT=${IMPORTER_API_PORT} ENV TZ=Etc/UTC EXPOSE ${API_PORT}/tcp @@ -130,5 +134,6 @@ CMD ["sh"] FROM runtime as release ENV RUNTIME="release" COPY --from=test /app/ /usr/ -# HEALTHCHECK CMD ["/usr/bin/wget", "--no-verbose", "--tries=1", "--spider", "localhost:${API_PORT}/version"] +HEALTHCHECK --interval=5s --timeout=5s --start-period=3s --retries=3 \ + CMD /usr/bin/health_check http://localhost:${API_PORT}/health_check && /usr/bin/health_check http://localhost:${IMPORTER_API_PORT}/health_check || exit 1 CMD ["/usr/bin/torrust-index"] diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 4e9ab8c7..03f23079 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -25,10 +25,10 @@ echo "Running E2E tests using MySQL ..." ./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh || exit 1 # Wait for conatiners to be healthy -./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 -# todo: implement healthchecks for tracker and index and wait until they are healthy -#wait_for_container torrust-tracker-1 10 3 -#wait_for_container torrust-idx-back-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 +# todo: implement healthchecks for the tracker and wait until it's healthy +#./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 sleep 20s # Just to make sure that everything is up and running diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 60a21c22..afedc5f6 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -26,10 +26,10 @@ echo "Running E2E tests using SQLite ..." ./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 # Wait for conatiners to be healthy -./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 -# todo: implement healthchecks for tracker and index and wait until they are healthy -#wait_for_container torrust-tracker-1 10 3 -#wait_for_container torrust-idx-back-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 +# todo: implement healthchecks for the tracker and wait until it's healthy +#./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 sleep 20s # Just to make sure that everything is up and running diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 1999c4a1..c7b4c33c 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -48,3 +48,4 @@ max_torrent_page_size = 30 [tracker_statistics_importer] torrent_info_update_interval = 3600 +port = 3002 \ No newline at end of file diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index c0cb6002..ac1dd71d 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -48,3 +48,4 @@ max_torrent_page_size = 30 [tracker_statistics_importer] torrent_info_update_interval = 3600 +port = 3002 \ No newline at end of file diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 06f89a3c..669979af 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -44,3 +44,4 @@ max_torrent_page_size = 30 [tracker_statistics_importer] torrent_info_update_interval = 3600 +port = 3002 \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 353ce274..22ffd4f7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,8 +19,8 @@ use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbU use crate::services::{proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::web::api::v1::auth::Authentication; -use crate::web::api::{start, Version}; -use crate::{mailer, tracker}; +use crate::web::api::Version; +use crate::{console, mailer, tracker, web}; pub struct Running { pub api_socket_addr: SocketAddr, @@ -46,8 +46,12 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let settings = configuration.settings.read().await; + // From [database] config let database_connect_url = settings.database.connect_url.clone(); - let torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; + // From [importer] config + let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; + let importer_port = settings.tracker_statistics_importer.port; + // From [net] config let net_ip = "0.0.0.0".to_string(); let net_port = settings.net.port; @@ -153,29 +157,18 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running ban_service, )); - // Start repeating task to import tracker torrent data and updating + // Start cronjob to import tracker torrent data and updating // seeders and leechers info. - - let weak_tracker_statistics_importer = Arc::downgrade(&tracker_statistics_importer); - - let tracker_statistics_importer_handle = tokio::spawn(async move { - let interval = std::time::Duration::from_secs(torrent_info_update_interval); - let mut interval = tokio::time::interval(interval); - interval.tick().await; // first tick is immediate... - loop { - interval.tick().await; - if let Some(tracker) = weak_tracker_statistics_importer.upgrade() { - drop(tracker.import_all_torrents_statistics().await); - } else { - break; - } - } - }); + let tracker_statistics_importer_handle = console::tracker_statistics_importer::start( + importer_port, + importer_torrent_info_update_interval, + &tracker_statistics_importer, + ); // Start API server + let running_api = web::api::start(app_data, &net_ip, net_port, api_version).await; - let running_api = start(app_data, &net_ip, net_port, api_version).await; - + // Full running application Running { api_socket_addr: running_api.socket_addr, api_server: running_api.api_server, diff --git a/src/bin/health_check.rs b/src/bin/health_check.rs new file mode 100644 index 00000000..16e2d874 --- /dev/null +++ b/src/bin/health_check.rs @@ -0,0 +1,37 @@ +//! Minimal `curl` or `wget` to be used for container health checks. +//! +//! It's convenient to avoid using third-party libraries because: +//! +//! - They are harder to maintain. +//! - They introduce new attack vectors. +use std::{env, process}; + +#[tokio::main] +async fn main() { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: cargo run --bin health_check "); + eprintln!("Example: cargo run --bin health_check http://localhost:3002/health_check"); + std::process::exit(1); + } + + println!("Health check ..."); + + let url = &args[1].clone(); + + match reqwest::get(url).await { + Ok(response) => { + if response.status().is_success() { + println!("STATUS: {}", response.status()); + process::exit(0); + } else { + println!("Non-success status received."); + process::exit(1); + } + } + Err(err) => { + println!("ERROR: {err}"); + process::exit(1); + } + } +} diff --git a/src/config.rs b/src/config.rs index 941c3921..4f90aa47 100644 --- a/src/config.rs +++ b/src/config.rs @@ -334,12 +334,15 @@ impl Default for Api { pub struct TrackerStatisticsImporter { /// The interval in seconds to get statistics from the tracker. pub torrent_info_update_interval: u64, + /// The port the Importer API is listening on. Default to `3002`. + pub port: u16, } impl Default for TrackerStatisticsImporter { fn default() -> Self { Self { torrent_info_update_interval: 3600, + port: 3002, } } } diff --git a/src/console/mod.rs b/src/console/mod.rs index 82b6da3c..889c06dc 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -1 +1,2 @@ pub mod commands; +pub(crate) mod tracker_statistics_importer; diff --git a/src/console/tracker_statistics_importer.rs b/src/console/tracker_statistics_importer.rs new file mode 100644 index 00000000..c0787ad1 --- /dev/null +++ b/src/console/tracker_statistics_importer.rs @@ -0,0 +1,130 @@ +//! Cronjob to import tracker torrent data and updating seeders and leechers +//! info. +//! +//! It has two services: +//! +//! - The importer which is the cronjob executed at regular intervals. +//! - The importer API. +//! +//! The cronjob sends a heartbeat signal to the API each time it is executed. +//! The last heartbeat signal time is used to determine whether the cronjob was +//! executed successfully or not. The API has a `health_check` endpoint which is +//! used when the application is running in containers. +use std::sync::{Arc, Mutex}; + +use axum::extract::State; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use chrono::{DateTime, Utc}; +use log::{error, info}; +use serde_json::{json, Value}; +use tokio::task::JoinHandle; + +use crate::tracker::statistics_importer::StatisticsImporter; + +const IMPORTER_API_IP: &str = "127.0.0.1"; + +#[derive(Clone)] +struct ImporterState { + /// Shared variable to store the timestamp of the last heartbeat sent + /// by the cronjob. + pub last_heartbeat: Arc>>, + /// Interval between importation executions + pub torrent_info_update_interval: u64, +} + +pub fn start( + importer_port: u16, + torrent_info_update_interval: u64, + tracker_statistics_importer: &Arc, +) -> JoinHandle<()> { + let weak_tracker_statistics_importer = Arc::downgrade(tracker_statistics_importer); + + tokio::spawn(async move { + info!("Tracker statistics importer launcher started"); + + // Start the Importer API + + let _importer_api_handle = tokio::spawn(async move { + let import_state = Arc::new(ImporterState { + last_heartbeat: Arc::new(Mutex::new(Utc::now())), + torrent_info_update_interval, + }); + + let app = Router::new() + .route("/", get(|| async { Json(json!({})) })) + .route("/health_check", get(health_check_handler)) + .with_state(import_state.clone()) + .route("/heartbeat", post(heartbeat_handler)) + .with_state(import_state); + + let addr = format!("{IMPORTER_API_IP}:{importer_port}"); + + info!("Tracker statistics importer API server listening on http://{}", addr); + + axum::Server::bind(&addr.parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); + }); + + // Start the Importer cronjob + + info!("Tracker statistics importer cronjob starting ..."); + + let interval = std::time::Duration::from_secs(torrent_info_update_interval); + let mut interval = tokio::time::interval(interval); + + interval.tick().await; // first tick is immediate... + + loop { + interval.tick().await; + + info!("Running tracker statistics importer ..."); + + if let Err(e) = send_heartbeat(importer_port).await { + error!("Failed to send heartbeat from importer cronjob: {}", e); + } + + if let Some(tracker) = weak_tracker_statistics_importer.upgrade() { + drop(tracker.import_all_torrents_statistics().await); + } else { + break; + } + } + }) +} + +/// Endpoint for container health check. +async fn health_check_handler(State(state): State>) -> Json { + let margin_in_seconds = 10; + let now = Utc::now(); + let last_heartbeat = state.last_heartbeat.lock().unwrap(); + + if now.signed_duration_since(*last_heartbeat).num_seconds() + <= (state.torrent_info_update_interval + margin_in_seconds).try_into().unwrap() + { + Json(json!({ "status": "Ok" })) + } else { + Json(json!({ "status": "Error" })) + } +} + +/// The tracker statistics importer cronjob sends a heartbeat on each execution +/// to inform that it's alive. This endpoint handles receiving that signal. +async fn heartbeat_handler(State(state): State>) -> Json { + let now = Utc::now(); + let mut last_heartbeat = state.last_heartbeat.lock().unwrap(); + *last_heartbeat = now; + Json(json!({ "status": "Heartbeat received" })) +} + +/// Send a heartbeat from the importer cronjob to the importer API. +async fn send_heartbeat(importer_port: u16) -> Result<(), reqwest::Error> { + let client = reqwest::Client::new(); + let url = format!("http://{IMPORTER_API_IP}:{importer_port}/heartbeat"); + + client.post(url).send().await?; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 039bdfeb..ba89003c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,6 +200,7 @@ //! //! [tracker_statistics_importer] //! torrent_info_update_interval = 3600 +//! port = 3002 //! ``` //! //! For more information about configuration you can visit the documentation for the [`config`]) module. diff --git a/src/web/api/v1/contexts/settings/mod.rs b/src/web/api/v1/contexts/settings/mod.rs index 5bb35151..bd8cd54c 100644 --- a/src/web/api/v1/contexts/settings/mod.rs +++ b/src/web/api/v1/contexts/settings/mod.rs @@ -75,6 +75,7 @@ //! }, //! "tracker_statistics_importer": { //! "torrent_info_update_interval": 3600 +//! "port": 3002 //! } //! } //! } diff --git a/src/web/api/v1/routes.rs b/src/web/api/v1/routes.rs index 44098f4c..c1666a38 100644 --- a/src/web/api/v1/routes.rs +++ b/src/web/api/v1/routes.rs @@ -4,7 +4,8 @@ use std::sync::Arc; use axum::extract::DefaultBodyLimit; use axum::routing::get; -use axum::Router; +use axum::{Json, Router}; +use serde_json::{json, Value}; use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; @@ -34,7 +35,8 @@ pub fn router(app_data: Arc) -> Router { .nest("/proxy", proxy::routes::router(app_data.clone())); let router = Router::new() - .route("/", get(about_page_handler).with_state(app_data)) + .route("/", get(about_page_handler).with_state(app_data.clone())) + .route("/health_check", get(health_check_handler).with_state(app_data)) .nest(&format!("/{API_VERSION_URL_PREFIX}"), v1_api_routes); let router = if env::var(ENV_VAR_CORS_PERMISSIVE).is_ok() { @@ -45,3 +47,8 @@ pub fn router(app_data: Arc) -> Router { router.layer(DefaultBodyLimit::max(10_485_760)).layer(CompressionLayer::new()) } + +/// Endpoint for container health check. +async fn health_check_handler() -> Json { + Json(json!({ "status": "Ok" })) +} diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 5ba7a0db..ece13b6d 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -82,6 +82,7 @@ pub struct Api { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct TrackerStatisticsImporter { pub torrent_info_update_interval: u64, + port: u16, } impl From for Settings { @@ -185,6 +186,7 @@ impl From for TrackerStatisticsImporter { fn from(tracker_statistics_importer: DomainTrackerStatisticsImporter) -> Self { Self { torrent_info_update_interval: tracker_statistics_importer.torrent_info_update_interval, + port: tracker_statistics_importer.port, } } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index e30c8907..175976f2 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -82,6 +82,9 @@ fn ephemeral(temp_dir: &TempDir) -> config::TorrustIndex { // Ephemeral API port configuration.net.port = FREE_PORT; + // Ephemeral Importer API port + configuration.tracker_statistics_importer.port = FREE_PORT; + // Ephemeral SQLite database configuration.database.connect_url = format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir)); From 052e855bea34bd18f7a877f51174e5492c5b3f83 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 20 Nov 2023 11:46:30 +0000 Subject: [PATCH 033/309] fix: [#392] use a different DB for E2E testing to avoid accidentally overwriting the default database. --- compose.yaml | 2 + .../container/e2e/mysql/e2e-env-down.sh | 5 +- .../container/e2e/mysql/e2e-env-reset.sh | 32 ------------ .../container/e2e/mysql/e2e-env-restart.sh | 4 -- .../container/e2e/mysql/e2e-env-up.sh | 11 ++-- .../dev-tools/container/e2e/mysql/install.sh | 16 +++++- .../container/e2e/mysql/run-e2e-tests.sh | 5 +- .../container/e2e/sqlite/e2e-env-down.sh | 4 +- .../container/e2e/sqlite/e2e-env-reset.sh | 23 --------- .../container/e2e/sqlite/e2e-env-restart.sh | 4 -- .../container/e2e/sqlite/e2e-env-up.sh | 11 ++-- .../dev-tools/container/e2e/sqlite/install.sh | 20 ++++++-- .../container/e2e/sqlite/run-e2e-tests.sh | 5 +- contrib/dev-tools/init/install-local.sh | 13 ----- .../default/config/index.container.mysql.toml | 2 +- .../config/index.e2e.container.mysql.toml | 51 +++++++++++++++++++ .../config/index.e2e.container.sqlite3.toml | 51 +++++++++++++++++++ .../config/tracker.e2e.container.sqlite3.toml | 38 ++++++++++++++ tests/e2e/environment.rs | 4 +- 19 files changed, 198 insertions(+), 103 deletions(-) delete mode 100755 contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh delete mode 100755 contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh delete mode 100755 contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh delete mode 100755 contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh delete mode 100755 contrib/dev-tools/init/install-local.sh create mode 100644 share/default/config/index.e2e.container.mysql.toml create mode 100644 share/default/config/index.e2e.container.sqlite3.toml create mode 100644 share/default/config/tracker.e2e.container.sqlite3.toml diff --git a/compose.yaml b/compose.yaml index db66954f..84a7296b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,6 +10,7 @@ services: environment: - USER_ID=${USER_ID} - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} + - TORRUST_INDEX_DATABASE=${TORRUST_INDEX_DATABASE:-e2e_testing_sqlite3} - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - TORRUST_INDEX_TRACKER_API_TOKEN=${TORRUST_INDEX_TRACKER_API_TOKEN:-MyAccessToken} networks: @@ -31,6 +32,7 @@ services: environment: - USER_ID=${USER_ID} - TORRUST_TRACKER_CONFIG=${TORRUST_TRACKER_CONFIG} + - TORRUST_TRACKER_DATABASE=${TORRUST_TRACKER_DATABASE:-e2e_testing_sqlite3} - TORRUST_TRACKER_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-sqlite3} - TORRUST_TRACKER_API_ADMIN_TOKEN=${TORRUST_TRACKER_API_ADMIN_TOKEN:-MyAccessToken} networks: diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh index 9db1ca2f..d21e953b 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -1,6 +1,5 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.mysql.toml) \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite.toml) \ docker compose down - diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh deleted file mode 100755 index 75408e4d..00000000 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-reset.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# Delete the databases and recreate them. - -./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh - -# Index - -# Database credentials -MYSQL_USER="root" -MYSQL_PASSWORD="root_secret_password" -MYSQL_HOST="localhost" -MYSQL_DATABASE="torrust_index_e2e_testing" - -# Create the MySQL database for the index. Assumes MySQL client is installed. -echo "Creating MySQL database $MYSQL_DATABASE for E2E testing ..." -mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e "DROP DATABASE IF EXISTS $MYSQL_DATABASE; CREATE DATABASE $MYSQL_DATABASE;" - -# Tracker - -# Delete tracker database -rm -f ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db - -# Generate storage directory if it does not exist -mkdir -p "./storage/tracker/lib/database" - -# Generate the sqlite database for the tracker if it does not exist -if ! [ -f "./storage/tracker/lib/database/torrust_tracker_e2e_testing.db" ]; then - sqlite3 ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db "VACUUM;" -fi - -./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh deleted file mode 100755 index 48163040..00000000 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-restart.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -./contrib/dev-tools/container/e2e/mysql/e2e-env-downp.sh -./contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index e2af81f8..cb941681 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -1,15 +1,16 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.mysql.toml) \ + TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ + TORRUST_INDEX_DATABASE="torrust_index_e2e_testing" \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ - docker compose up -d - + docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/mysql/install.sh b/contrib/dev-tools/container/e2e/mysql/install.sh index de8e1cf3..5cbb5a09 100755 --- a/contrib/dev-tools/container/e2e/mysql/install.sh +++ b/contrib/dev-tools/container/e2e/mysql/install.sh @@ -2,14 +2,26 @@ # This script is only intended to be used for E2E testing environment. +## Index + # Database credentials MYSQL_USER="root" MYSQL_PASSWORD="root_secret_password" MYSQL_HOST="127.0.0.1" -MYSQL_DATABASE="torrust_index_e2e_testing" +MYSQL_DATABASE=$TORRUST_INDEX_DATABASE # Create the MySQL database for the index. Assumes MySQL client is installed. # The docker compose configuration already creates the database the first time # the container is created. -echo "Creating MySQL database $MYSQL_DATABASE for for E2E testing ..." +echo "Creating MySQL database '$MYSQL_DATABASE' for for E2E testing ..." MYSQL_PWD=$MYSQL_PASSWORD mysql -h $MYSQL_HOST -u $MYSQL_USER -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" + +## Tracker + +# Generate the Tracker sqlite database directory and file if it does not exist +mkdir -p ./storage/tracker/lib/database + +if ! [ -f "./storage/tracker/lib/database/${TORRUST_TRACKER_DATABASE}.db" ]; then + echo "Creating tracker database '${TORRUST_TRACKER_DATABASE}.db'" + sqlite3 "./storage/tracker/lib/database/${TORRUST_TRACKER_DATABASE}.db" "VACUUM;" +fi diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 03f23079..74bf602b 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -10,6 +10,9 @@ TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID export USER_ID export TORRUST_TRACKER_USER_UID +export TORRUST_INDEX_DATABASE="torrust_index_e2e_testing" +export TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" + # Install tool to create torrent files. # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 @@ -38,7 +41,7 @@ docker ps ./contrib/dev-tools/container/e2e/mysql/install.sh || exit 1 # Run E2E tests with shared app instance -TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.mysql.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" cargo test || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh index 1a2aebed..74ed5e77 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh deleted file mode 100755 index e5890ac1..00000000 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-reset.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Delete the databases and recreate them. - -./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh - -rm -f ./storage/database/torrust_index_e2e_testing.db -rm -f ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db - -# Generate storage directory if it does not exist -mkdir -p "./storage/database" - -# Generate the sqlite database for the index if it does not exist -if ! [ -f "./storage/database/torrust_index_e2e_testing.db" ]; then - sqlite3 ./storage/database/torrust_index_e2e_testing.db "VACUUM;" -fi - -# Generate the sqlite database for the tracker if it does not exist -if ! [ -f "./storage/tracker/lib/database/torrust_tracker_e2e_testing.db" ]; then - sqlite3 ./storage/tracker/lib/database/torrust_tracker_e2e_testing.db "VACUUM;" -fi - -./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh deleted file mode 100755 index 7a9e55d2..00000000 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-restart.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh -./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index 7d2d224f..952d1738 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -1,14 +1,15 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.container.sqlite3.toml) \ + TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ + TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ - docker compose up -d - + docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/install.sh b/contrib/dev-tools/container/e2e/sqlite/install.sh index a1b800e6..24bb5cd7 100755 --- a/contrib/dev-tools/container/e2e/sqlite/install.sh +++ b/contrib/dev-tools/container/e2e/sqlite/install.sh @@ -2,12 +2,22 @@ # This script is only intended to be used for E2E testing environment. -# Generate storage directory if it does not exist +## Index + +# Generate the Index sqlite database directory and file if it does not exist mkdir -p ./storage/index/lib/database -# Generate the sqlite database if it does not exist -if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then - # todo: it should get the path from tracker.toml and only do it when we use sqlite - sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" +if ! [ -f "./storage/index/lib/database/${TORRUST_INDEX_DATABASE}.db" ]; then + echo "Creating index database '${TORRUST_INDEX_DATABASE}.db'" + sqlite3 "./storage/index/lib/database/${TORRUST_INDEX_DATABASE}.db" "VACUUM;" fi +## Tracker + +# Generate the Tracker sqlite database directory and file if it does not exist +mkdir -p ./storage/tracker/lib/database + +if ! [ -f "./storage/tracker/lib/database/${TORRUST_TRACKER_DATABASE}.db" ]; then + echo "Creating tracker database '${TORRUST_TRACKER_DATABASE}.db'" + sqlite3 "./storage/tracker/lib/database/${TORRUST_TRACKER_DATABASE}.db" "VACUUM;" +fi diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index afedc5f6..d6d38f60 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -10,6 +10,9 @@ TORRUST_TRACKER_USER_UID=$CURRENT_USER_ID export USER_ID export TORRUST_TRACKER_USER_UID +export TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" +export TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" + # Install tool to create torrent files. # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 @@ -36,7 +39,7 @@ sleep 20s docker ps # Run E2E tests with shared app instance -TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.container.sqlite3.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" cargo test || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/init/install-local.sh b/contrib/dev-tools/init/install-local.sh deleted file mode 100755 index 7c37a06f..00000000 --- a/contrib/dev-tools/init/install-local.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# This script is only intended to be used for local development or testing environments. - -# Generate storage directory if it does not exist -mkdir -p ./storage/index/lib/database - -# Generate the sqlite database if it does not exist -if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then - # todo: it should get the path from tracker.toml and only do it when we use sqlite - sqlite3 ./storage/index/lib/database/sqlite3.db "VACUUM;" -fi - diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index c7b4c33c..9857d6ae 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -24,7 +24,7 @@ max_password_length = 64 secret_key = "MaxVerstappenWC2021" [database] -connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" +connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" [mail] email_verification_enabled = false diff --git a/share/default/config/index.e2e.container.mysql.toml b/share/default/config/index.e2e.container.mysql.toml new file mode 100644 index 00000000..c7b4c33c --- /dev/null +++ b/share/default/config/index.e2e.container.mysql.toml @@ -0,0 +1,51 @@ +log_level = "info" + +[website] +name = "Torrust" + +# Please override the tracker token setting the +# `TORRUST_INDEX_TRACKER_API_TOKEN` +# environmental variable! + +[tracker] +url = "udp://tracker:6969" +mode = "Public" +api_url = "http://tracker:1212" +token = "MyAccessToken" +token_valid_seconds = 7257600 + +[net] +port = 3001 + +[auth] +email_on_signup = "Optional" +min_password_length = 6 +max_password_length = 64 +secret_key = "MaxVerstappenWC2021" + +[database] +connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" + +[mail] +email_verification_enabled = false +from = "example@email.com" +reply_to = "noreply@email.com" +username = "" +password = "" +server = "mailcatcher" +port = 1025 + +[image_cache] +max_request_timeout_ms = 1000 +capacity = 128000000 +entry_size_limit = 4000000 +user_quota_period_seconds = 3600 +user_quota_bytes = 64000000 + +[api] +default_torrent_page_size = 10 +max_torrent_page_size = 30 + +[tracker_statistics_importer] +torrent_info_update_interval = 3600 +port = 3002 \ No newline at end of file diff --git a/share/default/config/index.e2e.container.sqlite3.toml b/share/default/config/index.e2e.container.sqlite3.toml new file mode 100644 index 00000000..ec15023b --- /dev/null +++ b/share/default/config/index.e2e.container.sqlite3.toml @@ -0,0 +1,51 @@ +log_level = "info" + +[website] +name = "Torrust" + +# Please override the tracker token setting the +# `TORRUST_INDEX_TRACKER_API_TOKEN` +# environmental variable! + +[tracker] +url = "udp://tracker:6969" +mode = "Public" +api_url = "http://tracker:1212" +token = "MyAccessToken" +token_valid_seconds = 7257600 + +[net] +port = 3001 + +[auth] +email_on_signup = "Optional" +min_password_length = 6 +max_password_length = 64 +secret_key = "MaxVerstappenWC2021" + +[database] +connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" + +[mail] +email_verification_enabled = false +from = "example@email.com" +reply_to = "noreply@email.com" +username = "" +password = "" +server = "mailcatcher" +port = 1025 + +[image_cache] +max_request_timeout_ms = 1000 +capacity = 128000000 +entry_size_limit = 4000000 +user_quota_period_seconds = 3600 +user_quota_bytes = 64000000 + +[api] +default_torrent_page_size = 10 +max_torrent_page_size = 30 + +[tracker_statistics_importer] +torrent_info_update_interval = 3600 +port = 3002 \ No newline at end of file diff --git a/share/default/config/tracker.e2e.container.sqlite3.toml b/share/default/config/tracker.e2e.container.sqlite3.toml new file mode 100644 index 00000000..67605293 --- /dev/null +++ b/share/default/config/tracker.e2e.container.sqlite3.toml @@ -0,0 +1,38 @@ +announce_interval = 120 +db_driver = "Sqlite3" +db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" +external_ip = "0.0.0.0" +inactive_peer_cleanup_interval = 600 +log_level = "info" +max_peer_timeout = 900 +min_announce_interval = 120 +mode = "public" +on_reverse_proxy = false +persistent_torrent_completed_stat = false +remove_peerless_torrents = true +tracker_usage_statistics = true + +[[udp_trackers]] +bind_address = "0.0.0.0:6969" +enabled = false + +[[http_trackers]] +bind_address = "0.0.0.0:7070" +enabled = false +ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" +ssl_enabled = false +ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" + +[http_api] +bind_address = "0.0.0.0:1212" +enabled = true +ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" +ssl_enabled = false +ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" + +# Please override the admin token setting the +# `TORRUST_TRACKER_API_ADMIN_TOKEN` +# environmental variable! + +[http_api.access_tokens] +admin = "MyAccessToken" diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 1c20ca26..a39873a2 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -132,11 +132,11 @@ impl TestEnv { /// /// For: /// - /// `sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc`. + /// `sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc`. /// /// It changes the `mysql` host name to `localhost`: /// - /// `sqlite://./storage/index/lib/database/sqlite3.db?mode=rwc`. + /// `sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc`. /// /// For E2E tests, we use docker compose. Inside the container, the /// `SQLite` file path is not the same as the host path. From 1b33145410c6deb614dbf850f484bec23cfadaa1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 Nov 2023 18:59:32 +0000 Subject: [PATCH 034/309] ci: [#390] inject E2E DB connection info with env var ``` TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ cargo test \ || exit 1 ``` Th new `TORRUST_INDEX_E2E_DB_CONNECT_URL` is used to connect to the E2E DB directly. IT's needed for some tests to setthe initial state needed for the test, when it's not possible do do it otherwise. --- .../container/e2e/mysql/run-e2e-tests.sh | 6 +- .../container/e2e/sqlite/run-e2e-tests.sh | 6 +- tests/e2e/config.rs | 3 + tests/e2e/environment.rs | 63 +++---------------- 4 files changed, 23 insertions(+), 55 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 74bf602b..0d2057fa 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -41,7 +41,11 @@ docker ps ./contrib/dev-tools/container/e2e/mysql/install.sh || exit 1 # Run E2E tests with shared app instance -TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true \ + TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ + TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ + cargo test \ + || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index d6d38f60..4ddc84cb 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -39,7 +39,11 @@ sleep 20s docker ps # Run E2E tests with shared app instance -TORRUST_INDEX_E2E_SHARED=true TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" cargo test || exit 1 +TORRUST_INDEX_E2E_SHARED=true \ + TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ + TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ + cargo test \ + || exit 1 # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index 747e0a05..24cb87bf 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -23,6 +23,9 @@ pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/index.development. /// If present, E2E tests will run against a shared instance of the server pub const ENV_VAR_INDEX_SHARED: &str = "TORRUST_INDEX_E2E_SHARED"; +/// `SQLx` connection string to connect to the E2E database +pub const ENV_VAR_DB_CONNECT_URL: &str = "TORRUST_INDEX_E2E_DB_CONNECT_URL"; + /// It loads the application configuration from the environment. /// /// There are two methods to inject the configuration: diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index a39873a2..0ee8b55b 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,9 +1,8 @@ use std::env; -use torrust_index::databases::database; use torrust_index::web::api::Version; -use super::config::{initialize_configuration, ENV_VAR_INDEX_SHARED}; +use super::config::{initialize_configuration, ENV_VAR_DB_CONNECT_URL, ENV_VAR_INDEX_SHARED}; use crate::common::contexts::settings::Settings; use crate::environments::{isolated, shared}; @@ -98,9 +97,10 @@ impl TestEnv { /// Provides a database connect URL to connect to the database. For example: /// - /// `sqlite://storage/database/torrust_index_e2e_testing.db?mode=rwc`. + /// - `sqlite://storage/database/torrust_index_e2e_testing.db?mode=rwc`. + /// - `mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing`. /// - /// It's used to run SQL queries against the database needed for some tests. + /// It's used to run SQL queries against the E2E database needed for some tests. pub fn database_connect_url(&self) -> Option { let internal_connect_url = self .starting_settings @@ -109,62 +109,19 @@ impl TestEnv { match self.state() { State::RunningShared => { - if let Some(db_path) = internal_connect_url { - let maybe_db_driver = database::get_driver(&db_path); - - return match maybe_db_driver { - Ok(db_driver) => match db_driver { - database::Driver::Sqlite3 => Some(Self::overwrite_sqlite_path(&db_path, "./storage/index/lib")), - database::Driver::Mysql => Some(Self::overwrite_mysql_host(&db_path, "localhost")), - }, - Err(_) => None, - }; + let connect_url_env_var = ENV_VAR_DB_CONNECT_URL; + + if let Ok(connect_url) = env::var(connect_url_env_var) { + Some(connect_url) + } else { + None } - None } State::RunningIsolated => internal_connect_url, State::Stopped => None, } } - /// It overrides the `SQLite` file path in a `SQLx` database connection URL. - /// For example: - /// - /// For: - /// - /// `sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc`. - /// - /// It changes the `mysql` host name to `localhost`: - /// - /// `sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc`. - /// - /// For E2E tests, we use docker compose. Inside the container, the - /// `SQLite` file path is not the same as the host path. - fn overwrite_sqlite_path(db_path: &str, host_path: &str) -> String { - // todo: inject value with env var - db_path.replace("/var/lib/torrust/index", host_path) - } - - /// It overrides the "Host" in a `SQLx` database connection URL. - /// For example: - /// - /// For: - /// - /// `mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing`. - /// - /// It changes the `mysql` host name to `localhost`: - /// - /// `mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing`. - /// - /// For E2E tests, we use docker compose, internally the index connects to - /// the `MySQL` database using the "mysql" host, which is the docker compose - /// service name, but tests connects directly to the localhost since the - /// `MySQL` is exposed to the host. - fn overwrite_mysql_host(db_path: &str, new_host: &str) -> String { - // todo: inject value with env var - db_path.replace("@mysql:", &format!("@{new_host}:")) - } - fn state(&self) -> State { if self.is_shared() { return State::RunningShared; From 1b3d5a095617a23a9749b8adcd09c4ce1645c3e3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 Nov 2023 19:20:47 +0000 Subject: [PATCH 035/309] ci: [#203] stop containers running E2E tests when tests fail --- contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh | 3 +-- contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 0d2057fa..8706e1ae 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -45,8 +45,7 @@ TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ cargo test \ - || exit 1 + || { ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh; exit 1; } # Stop E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 - diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 4ddc84cb..e504c257 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -43,7 +43,7 @@ TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ cargo test \ - || exit 1 + || { ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh; exit 1; } # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 From e00406e633e8fe2606740b3395549dcea862b616 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 27 Nov 2023 10:34:21 +0000 Subject: [PATCH 036/309] ci: [#408] wait for Tracker container to be healthy instead of just waiting 20 seconds. Now the Tracker container has HEALTHCHECHK instruction. --- contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh | 4 +--- contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 4 +--- share/default/config/tracker.container.mysql.toml | 3 +++ share/default/config/tracker.container.sqlite3.toml | 3 +++ share/default/config/tracker.e2e.container.sqlite3.toml | 4 ++++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 8706e1ae..476d2a04 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -29,10 +29,8 @@ echo "Running E2E tests using MySQL ..." # Wait for conatiners to be healthy ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 -# todo: implement healthchecks for the tracker and wait until it's healthy -#./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1 ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 -sleep 20s # Just to make sure that everything is up and running docker ps diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index e504c257..af22e085 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -30,10 +30,8 @@ echo "Running E2E tests using SQLite ..." # Wait for conatiners to be healthy ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 -# todo: implement healthchecks for the tracker and wait until it's healthy -#./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1 ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 -sleep 20s # Just to make sure that everything is up and running docker ps diff --git a/share/default/config/tracker.container.mysql.toml b/share/default/config/tracker.container.mysql.toml index fb9cbf78..e7714c22 100644 --- a/share/default/config/tracker.container.mysql.toml +++ b/share/default/config/tracker.container.mysql.toml @@ -36,3 +36,6 @@ ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" [http_api.access_tokens] admin = "MyAccessToken" + +[health_check_api] +bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.container.sqlite3.toml b/share/default/config/tracker.container.sqlite3.toml index 54cfd402..4ec055c5 100644 --- a/share/default/config/tracker.container.sqlite3.toml +++ b/share/default/config/tracker.container.sqlite3.toml @@ -36,3 +36,6 @@ ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" [http_api.access_tokens] admin = "MyAccessToken" + +[health_check_api] +bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.e2e.container.sqlite3.toml b/share/default/config/tracker.e2e.container.sqlite3.toml index 67605293..316c6f1d 100644 --- a/share/default/config/tracker.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.e2e.container.sqlite3.toml @@ -36,3 +36,7 @@ ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" [http_api.access_tokens] admin = "MyAccessToken" + +[health_check_api] +bind_address = "127.0.0.1:1313" + From 902e8c7e2f505ee8227b0f2ac055279172825933 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 29 Nov 2023 16:47:45 +0000 Subject: [PATCH 037/309] chore: add script to setup DB for development --- contrib/dev-tools/init/install-local.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 contrib/dev-tools/init/install-local.sh diff --git a/contrib/dev-tools/init/install-local.sh b/contrib/dev-tools/init/install-local.sh new file mode 100644 index 00000000..e7b76d49 --- /dev/null +++ b/contrib/dev-tools/init/install-local.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This script is only intended to be used for development environment. + +# Generate the Index sqlite database directory and file if it does not exist +mkdir -p ./storage/index/lib/database + +if ! [ -f "./storage/index/lib/database/sqlite3.db.db" ]; then + echo "Creating index database 'sqlite3.db.db'" + sqlite3 "./storage/index/lib/database/sqlite3.db.db" "VACUUM;" +fi From 471ec38c1c3b49ad1918a5549daad63ca5c01c71 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 20 Dec 2023 14:28:11 +0000 Subject: [PATCH 038/309] feat: [#176] change logging level for SQL statements - SQL statements are only logged when global loggin level is `Debug`. - Slow SQL statements are only logged when global logging level is `info`. --- src/databases/mysql.rs | 4 ++-- src/databases/sqlite.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 8fb0ce12..bb1a0fb4 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -34,8 +34,8 @@ impl Database for Mysql { async fn new(database_url: &str) -> Self { let connection_options = MySqlConnectOptions::from_str(database_url) .expect("Unable to create connection options.") - .log_statements(log::LevelFilter::Error) - .log_slow_statements(log::LevelFilter::Warn, Duration::from_secs(1)); + .log_statements(log::LevelFilter::Debug) + .log_slow_statements(log::LevelFilter::Info, Duration::from_secs(1)); let db = MySqlPoolOptions::new() .connect_with(connection_options) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 4e955151..4b47dd10 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -34,8 +34,8 @@ impl Database for Sqlite { async fn new(database_url: &str) -> Self { let connection_options = SqliteConnectOptions::from_str(database_url) .expect("Unable to create connection options.") - .log_statements(log::LevelFilter::Error) - .log_slow_statements(log::LevelFilter::Warn, Duration::from_secs(1)); + .log_statements(log::LevelFilter::Debug) + .log_slow_statements(log::LevelFilter::Info, Duration::from_secs(1)); let db = SqlitePoolOptions::new() .connect_with(connection_options) @@ -259,7 +259,7 @@ impl Database for Sqlite { } async fn get_categories(&self) -> Result, database::Error> { - query_as::<_, Category>("SELECT tc.category_id, tc.name, COUNT(tt.category_id) as num_torrents FROM torrust_categories tc LEFT JOIN torrust_torrents tt on tc.category_id = tt.category_id GROUP BY tc.name") + query_as::<_, Category>("SELECT tc.category_id, tc.name, COUNT(tt.category_id) as num_torrents FROM torrust_categories tc LEFT JOIN torrust_torrents tt on tc.category_id = tt.category_id GROUP BY tc.name;") .fetch_all(&self.pool) .await .map_err(|_| database::Error::Error) From 9ada2d6a2e1113f2f62854813dc20e59a869585e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 26 Dec 2023 16:17:34 +0000 Subject: [PATCH 039/309] test: [#391] add test for torrust_index::tracker::service::get_torrent_info It seems there is a bug but I cannot reproduce it. --- src/tracker/service.rs | 131 ++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 27 deletions(-) diff --git a/src/tracker/service.rs b/src/tracker/service.rs index e39cf0a6..6c89ed09 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use hyper::StatusCode; use log::error; +use reqwest::Response; use serde::{Deserialize, Serialize}; use super::api::{Client, ConnectionInfo}; @@ -11,7 +12,7 @@ use crate::errors::ServiceError; use crate::models::tracker_key::TrackerKey; use crate::models::user::UserId; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct TorrentInfo { pub info_hash: String, pub seeders: i64, @@ -20,7 +21,7 @@ pub struct TorrentInfo { pub peers: Vec, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Peer { pub peer_id: Option, pub peer_addr: Option, @@ -31,7 +32,7 @@ pub struct Peer { pub event: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct PeerId { pub id: Option, pub client: Option, @@ -140,30 +141,7 @@ impl Service { .await .map_err(|_| ServiceError::InternalServerError)?; - if response.status() == StatusCode::NOT_FOUND { - return Err(ServiceError::TorrentNotFound); - } - - let body = response.text().await; - - if let Ok(body) = body { - if body == *"torrent not known" { - // todo: temporary fix. the service should return a 404 (StatusCode::NOT_FOUND). - return Err(ServiceError::TorrentNotFound); - } - - let torrent_info = serde_json::from_str(&body); - - if let Ok(torrent_info) = torrent_info { - Ok(torrent_info) - } else { - error!("Failed to parse torrent info from tracker response. Body: {}", body); - Err(ServiceError::InternalServerError) - } - } else { - error!("Tracker API response without body"); - Err(ServiceError::InternalServerError) - } + map_torrent_info_response(response).await } /// It builds the announce url appending the user tracker key. @@ -195,3 +173,102 @@ impl Service { Ok(tracker_key) } } + +async fn map_torrent_info_response(response: Response) -> Result { + if response.status() == StatusCode::NOT_FOUND { + return Err(ServiceError::TorrentNotFound); + } + + let body = response.text().await.map_err(|_| { + error!("Tracker API response without body"); + ServiceError::InternalServerError + })?; + + if body == *"torrent not known" { + // todo: temporary fix. the service should return a 404 (StatusCode::NOT_FOUND). + return Err(ServiceError::TorrentNotFound); + } + + serde_json::from_str(&body).map_err(|e| { + error!( + "Failed to parse torrent info from tracker response. Body: {}, Error: {}", + body, e + ); + ServiceError::InternalServerError + }) +} + +#[cfg(test)] +mod tests { + + mod getting_the_torrent_info_from_the_tracker { + use hyper::{Response, StatusCode}; + + use crate::errors::ServiceError; + use crate::tracker::service::{map_torrent_info_response, TorrentInfo}; + + #[tokio::test] + async fn it_should_return_a_torrent_not_found_response_when_the_tracker_returns_the_current_torrent_not_known_response() { + let tracker_response = Response::new("torrent not known"); + + let result = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); + + assert_eq!(result, ServiceError::TorrentNotFound); + } + + #[tokio::test] + async fn it_should_return_a_torrent_not_found_response_when_the_tracker_returns_the_future_torrent_not_known_response() { + // In the future the tracker should return a 4040 response. + // See: https://github.com/torrust/torrust-tracker/issues/144 + + let tracker_response = Response::builder().status(StatusCode::NOT_FOUND).body("").unwrap(); + + let result = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); + + assert_eq!(result, ServiceError::TorrentNotFound); + } + + #[tokio::test] + async fn it_should_return_the_torrent_info_when_the_tracker_returns_the_torrent_info() { + let body = r#" + { + "info_hash": "4f2ae7294f2c4865c38565f92a077d1591a0dd41", + "seeders": 0, + "completed": 0, + "leechers": 0, + "peers": [] + } + "#; + + let tracker_response = Response::new(body); + + let torrent_info = map_torrent_info_response(tracker_response.into()).await.unwrap(); + + assert_eq!( + torrent_info, + TorrentInfo { + info_hash: "4f2ae7294f2c4865c38565f92a077d1591a0dd41".to_string(), + seeders: 0, + completed: 0, + leechers: 0, + peers: vec![] + } + ); + } + + #[tokio::test] + async fn it_should_return_an_internal_server_error_when_the_tracker_response_cannot_be_parsed() { + let invalid_json_body_for_torrent_info = r#" + { + "field": "value" + } + "#; + + let tracker_response = Response::new(invalid_json_body_for_torrent_info); + + let result = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); + + assert_eq!(result, ServiceError::InternalServerError); + } + } +} From 219dff01151088980b0cf56ca2f8a475b2923fb5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 26 Dec 2023 16:25:37 +0000 Subject: [PATCH 040/309] fix: clippy error --- src/models/torrent_file.rs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 0d060095..ebbd1534 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -70,11 +70,7 @@ impl Torrent { /// This function will panic if the `torrent_info.pieces` is not a valid /// hex string. #[must_use] - pub fn from_database( - db_torrent: &DbTorrent, - torrent_files: &Vec, - torrent_announce_urls: Vec>, - ) -> Self { + pub fn from_database(db_torrent: &DbTorrent, torrent_files: &[TorrentFile], torrent_announce_urls: Vec>) -> Self { let info_dict = TorrentInfoDictionary::with( &db_torrent.name, db_torrent.piece_length, @@ -195,14 +191,7 @@ impl TorrentInfoDictionary { /// - The `pieces` field is not a valid hex string. /// - For single files torrents the `TorrentFile` path is empty. #[must_use] - pub fn with( - name: &str, - piece_length: i64, - private: Option, - root_hash: i64, - pieces: &str, - files: &Vec, - ) -> Self { + pub fn with(name: &str, piece_length: i64, private: Option, root_hash: i64, pieces: &str, files: &[TorrentFile]) -> Self { let mut info_dict = Self { name: name.to_string(), pieces: None, @@ -249,7 +238,7 @@ impl TorrentInfoDictionary { info_dict.path = path; } else { - info_dict.files = Some(files.clone()); + info_dict.files = Some(files.to_vec()); } info_dict From 2c9c3ddb905a5c2cdf89082ec93ca5d97816c2f8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 26 Dec 2023 17:24:40 +0000 Subject: [PATCH 041/309] fix: [#391] patch to identify 'torrent not found' tracker API response --- src/tracker/service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 6c89ed09..19ac3b95 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -184,7 +184,7 @@ async fn map_torrent_info_response(response: Response) -> Result Date: Tue, 26 Dec 2023 17:26:02 +0000 Subject: [PATCH 042/309] fix: [#419] url-encode tracker API token in the HTTP requests When the tracker API token contains specials characters, the token must be URL-encoded. For example: Wrong URL: http://tracker.torrust-demo.com:1212/v1/stats?token=123g7#@agJtWFSgkdA5R$K22yeo$k6IhNq%T2$r Rigth URL with encoded token: http://tracker.torrust-demo.com:1212/v1/stats?token=123g7%23%40agJtWFSgkdA5R%24K22yeo%24k6IhNq%25T2%24r --- src/tracker/api.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/tracker/api.rs b/src/tracker/api.rs index d3fa3fcb..f64430b7 100644 --- a/src/tracker/api.rs +++ b/src/tracker/api.rs @@ -34,14 +34,13 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn whitelist_torrent(&self, info_hash: &str) -> Result { - let request_url = format!( - "{}/whitelist/{}?token={}", - self.base_url, info_hash, self.connection_info.token - ); + let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); let client = reqwest::Client::new(); - client.post(request_url).send().await + let params = [("token", &self.connection_info.token)]; + + client.post(request_url).query(¶ms).send().await } /// Remove a torrent from the tracker whitelist. @@ -50,14 +49,13 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Result { - let request_url = format!( - "{}/whitelist/{}?token={}", - self.base_url, info_hash, self.connection_info.token - ); + let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); let client = reqwest::Client::new(); - client.delete(request_url).send().await + let params = [("token", &self.connection_info.token)]; + + client.delete(request_url).query(¶ms).send().await } /// Retrieve a new tracker key. @@ -66,14 +64,13 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result { - let request_url = format!( - "{}/key/{}?token={}", - self.base_url, token_valid_seconds, self.connection_info.token - ); + let request_url = format!("{}/key/{}", self.base_url, token_valid_seconds); let client = reqwest::Client::new(); - client.post(request_url).send().await + let params = [("token", &self.connection_info.token)]; + + client.post(request_url).query(¶ms).send().await } /// Retrieve the info for a torrent. @@ -82,10 +79,12 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn get_torrent_info(&self, info_hash: &str) -> Result { - let request_url = format!("{}/torrent/{}?token={}", self.base_url, info_hash, self.connection_info.token); + let request_url = format!("{}/torrent/{}", self.base_url, info_hash); let client = reqwest::Client::new(); - client.get(request_url).send().await + let params = [("token", &self.connection_info.token)]; + + client.get(request_url).query(¶ms).send().await } } From 35b079fabb28c22f3c6c3951b1335054e123fc45 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 3 Jan 2024 13:56:03 +0000 Subject: [PATCH 043/309] refactor: [#420] decouple tracker API errors from app errors We need the specifix error requesting the tracker API to include the error in the logs. --- src/errors.rs | 43 ++++++++++-- src/services/torrent.rs | 5 +- src/tracker/service.rs | 147 ++++++++++++++++++++++++++-------------- 3 files changed, 136 insertions(+), 59 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 24a9661e..e9cf1580 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,6 +6,7 @@ use hyper::StatusCode; use crate::databases::database; use crate::models::torrent::MetadataError; +use crate::tracker::service::TrackerAPIError; use crate::utils::parse_torrent::DecodeTorrentFileError; pub type ServiceResult = Result; @@ -84,9 +85,6 @@ pub enum ServiceError { /// token invalid TokenInvalid, - #[display(fmt = "Torrent not found.")] - TorrentNotFound, - #[display(fmt = "Uploaded torrent is not valid.")] InvalidTorrentFile, @@ -120,9 +118,6 @@ pub enum ServiceError { #[display(fmt = "This torrent title has already been used.")] TorrentTitleAlreadyExists, - #[display(fmt = "Sorry, we have an error with our tracker connection.")] - TrackerOffline, - #[display(fmt = "Could not whitelist torrent.")] WhitelistingError, @@ -141,6 +136,9 @@ pub enum ServiceError { #[display(fmt = "Tag name cannot be empty.")] TagNameEmpty, + #[display(fmt = "Torrent not found.")] + TorrentNotFound, + #[display(fmt = "Category not found.")] CategoryNotFound, @@ -149,6 +147,19 @@ pub enum ServiceError { #[display(fmt = "Database error.")] DatabaseError, + + // Tracker errors + #[display(fmt = "Sorry, we have an error with our tracker connection.")] + TrackerOffline, + + #[display(fmt = "Tracker response error. The operation could not be performed.")] + TrackerResponseError, + + #[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can be parsed.")] + TrackerUnknownResponse, + + #[display(fmt = "Torrent not found in tracker.")] + TorrentNotFoundInTracker, } impl From for ServiceError { @@ -228,6 +239,23 @@ impl From for ServiceError { } } +impl From for ServiceError { + fn from(e: TrackerAPIError) -> Self { + eprintln!("{e}"); + match e { + TrackerAPIError::TrackerOffline => ServiceError::TrackerOffline, + TrackerAPIError::AddToWhitelistError + | TrackerAPIError::RemoveFromWhitelistError + | TrackerAPIError::RetrieveUserKeyError => ServiceError::TrackerResponseError, + TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker, + TrackerAPIError::MissingResponseBody | TrackerAPIError::FailedToParseTrackerResponse { body: _ } => { + ServiceError::TrackerUnknownResponse + } + TrackerAPIError::CannotSaveUserKey => ServiceError::DatabaseError, + } + } +} + #[must_use] pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { #[allow(clippy::match_same_arms)] @@ -276,6 +304,9 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR, ServiceError::CategoryNotFound => StatusCode::NOT_FOUND, ServiceError::TagNotFound => StatusCode::NOT_FOUND, + ServiceError::TrackerResponseError => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::TrackerUnknownResponse => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::TorrentNotFoundInTracker => StatusCode::NOT_FOUND, } } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 0a1f6b94..73eef75d 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -165,7 +165,7 @@ impl Index { { // If the torrent can't be whitelisted somehow, remove the torrent from database drop(self.torrent_repository.delete(&torrent_id).await); - return Err(e); + return Err(e.into()); } // Build response @@ -304,7 +304,8 @@ impl Index { self.torrent_repository.delete(&torrent_listing.torrent_id).await?; // Remove info-hash from tracker whitelist - let _ = self + // todo: handle the error when the tracker is offline or not well configured. + let _unused = self .tracker_service .remove_info_hash_from_whitelist(info_hash.to_string()) .await; diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 19ac3b95..cb436432 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use derive_more::{Display, Error}; use hyper::StatusCode; use log::error; use reqwest::Response; @@ -8,10 +9,37 @@ use serde::{Deserialize, Serialize}; use super::api::{Client, ConnectionInfo}; use crate::config::Configuration; use crate::databases::database::Database; -use crate::errors::ServiceError; use crate::models::tracker_key::TrackerKey; use crate::models::user::UserId; +#[derive(Debug, Display, PartialEq, Eq, Error)] +#[allow(dead_code)] +pub enum TrackerAPIError { + #[display(fmt = "Error with tracker connection.")] + TrackerOffline, + + #[display(fmt = "Could not whitelist torrent.")] + AddToWhitelistError, + + #[display(fmt = "Could not remove torrent from whitelist.")] + RemoveFromWhitelistError, + + #[display(fmt = "Could not retrieve a new user key.")] + RetrieveUserKeyError, + + #[display(fmt = "Could not save the newly generated user key into the database.")] + CannotSaveUserKey, + + #[display(fmt = "Torrent not found.")] + TorrentNotFound, + + #[display(fmt = "Expected body in tracker response, received empty body.")] + MissingResponseBody, + + #[display(fmt = "Expected body in tracker response, received empty body.")] + FailedToParseTrackerResponse { body: String }, +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct TorrentInfo { pub info_hash: String, @@ -69,7 +97,7 @@ impl Service { /// /// Will return an error if the HTTP request failed (for example if the /// tracker API is offline) or if the tracker API returned an error. - pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), ServiceError> { + pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), TrackerAPIError> { let response = self.api_client.whitelist_torrent(&info_hash).await; match response { @@ -77,10 +105,10 @@ impl Service { if response.status().is_success() { Ok(()) } else { - Err(ServiceError::WhitelistingError) + Err(TrackerAPIError::AddToWhitelistError) } } - Err(_) => Err(ServiceError::TrackerOffline), + Err(_) => Err(TrackerAPIError::TrackerOffline), } } @@ -90,7 +118,7 @@ impl Service { /// /// Will return an error if the HTTP request failed (for example if the /// tracker API is offline) or if the tracker API returned an error. - pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), ServiceError> { + pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), TrackerAPIError> { let response = self.api_client.remove_torrent_from_whitelist(&info_hash).await; match response { @@ -98,10 +126,10 @@ impl Service { if response.status().is_success() { Ok(()) } else { - Err(ServiceError::InternalServerError) + Err(TrackerAPIError::RemoveFromWhitelistError) } } - Err(_) => Err(ServiceError::InternalServerError), + Err(_) => Err(TrackerAPIError::TrackerOffline), } } @@ -116,14 +144,14 @@ impl Service { /// /// Will return an error if the HTTP request to get generated a new /// user tracker key failed. - pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result { + pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result { let tracker_key = self.database.get_user_tracker_key(user_id).await; match tracker_key { - Some(v) => Ok(self.announce_url_with_key(&v)), + Some(tracker_key) => Ok(self.announce_url_with_key(&tracker_key)), None => match self.retrieve_new_tracker_key(user_id).await { - Ok(v) => Ok(self.announce_url_with_key(&v)), - Err(_) => Err(ServiceError::TrackerOffline), + Ok(new_tracker_key) => Ok(self.announce_url_with_key(&new_tracker_key)), + Err(_) => Err(TrackerAPIError::TrackerOffline), }, } } @@ -134,14 +162,19 @@ impl Service { /// /// Will return an error if the HTTP request to get torrent info fails or /// if the response cannot be parsed. - pub async fn get_torrent_info(&self, info_hash: &str) -> Result { - let response = self - .api_client - .get_torrent_info(info_hash) - .await - .map_err(|_| ServiceError::InternalServerError)?; - - map_torrent_info_response(response).await + pub async fn get_torrent_info(&self, info_hash: &str) -> Result { + let response = self.api_client.get_torrent_info(info_hash).await; + + match response { + Ok(response) => { + if response.status().is_success() { + map_torrent_info_response(response).await + } else { + Err(TrackerAPIError::RemoveFromWhitelistError) + } + } + Err(_) => Err(TrackerAPIError::TrackerOffline), + } } /// It builds the announce url appending the user tracker key. @@ -150,43 +183,51 @@ impl Service { format!("{}/{}", self.tracker_url, tracker_key.key) } - /// Issue a new tracker key from tracker and save it in database, - /// tied to a user - async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result { - // Request new tracker key from tracker - let response = self - .api_client - .retrieve_new_tracker_key(self.token_valid_seconds) - .await - .map_err(|_| ServiceError::InternalServerError)?; - - // Parse tracker key from response - let tracker_key = response - .json::() - .await - .map_err(|_| ServiceError::InternalServerError)?; - - // Add tracker key to database (tied to a user) - self.database.add_tracker_key(user_id, &tracker_key).await?; - - // return tracker key - Ok(tracker_key) + /// Issue a new tracker key from tracker. + async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result { + let response = self.api_client.retrieve_new_tracker_key(self.token_valid_seconds).await; + + match response { + Ok(response) => { + if response.status().is_success() { + let body = response.text().await.map_err(|_| { + error!("Tracker API response without body"); + TrackerAPIError::MissingResponseBody + })?; + + // Parse tracker key from response + let tracker_key = + serde_json::from_str(&body).map_err(|_| TrackerAPIError::FailedToParseTrackerResponse { body })?; + + // Add tracker key to database (tied to a user) + self.database + .add_tracker_key(user_id, &tracker_key) + .await + .map_err(|_| TrackerAPIError::CannotSaveUserKey)?; + + Ok(tracker_key) + } else { + Err(TrackerAPIError::RetrieveUserKeyError) + } + } + Err(_) => Err(TrackerAPIError::TrackerOffline), + } } } -async fn map_torrent_info_response(response: Response) -> Result { +async fn map_torrent_info_response(response: Response) -> Result { if response.status() == StatusCode::NOT_FOUND { - return Err(ServiceError::TorrentNotFound); + return Err(TrackerAPIError::TorrentNotFound); } let body = response.text().await.map_err(|_| { error!("Tracker API response without body"); - ServiceError::InternalServerError + TrackerAPIError::MissingResponseBody })?; if body == "\"torrent not known\"" { // todo: temporary fix. the service should return a 404 (StatusCode::NOT_FOUND). - return Err(ServiceError::TorrentNotFound); + return Err(TrackerAPIError::TorrentNotFound); } serde_json::from_str(&body).map_err(|e| { @@ -194,7 +235,7 @@ async fn map_torrent_info_response(response: Response) -> Result Date: Wed, 3 Jan 2024 14:03:16 +0000 Subject: [PATCH 044/309] chore: disable clippy error until we fix it Relates to: https://github.com/torrust/torrust-index/issues/388 --- tests/common/contexts/torrent/responses.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index a6fd4946..a776623f 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -74,17 +74,9 @@ pub struct TorrentDetails { pub encoding: Option, } -#[rustversion::stable] -#[derive(Deserialize, PartialEq, Debug)] -pub struct Category { - pub category_id: CategoryId, // todo: rename to `id` - pub name: String, - pub num_torrents: u64, -} - -#[rustversion::nightly] -#[derive(Deserialize, PartialEq, Debug)] +#[allow(unknown_lints)] #[allow(clippy::struct_field_names)] +#[derive(Deserialize, PartialEq, Debug)] pub struct Category { pub category_id: CategoryId, // todo: rename to `id` pub name: String, From 70ac6643b47d06e4eb81c7e1a7c1a719bde187fd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 3 Jan 2024 16:54:18 +0000 Subject: [PATCH 045/309] fix: [#420] proper handling for tracker API responses Some cases were missing when the tracker service processes the responses from the tracker API. Added debugging and error log. todo: add tests --- src/errors.rs | 18 ++- src/tracker/service.rs | 301 +++++++++++++++++++++-------------------- 2 files changed, 162 insertions(+), 157 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index e9cf1580..301d841c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -148,7 +148,7 @@ pub enum ServiceError { #[display(fmt = "Database error.")] DatabaseError, - // Tracker errors + // Begin tracker errors #[display(fmt = "Sorry, we have an error with our tracker connection.")] TrackerOffline, @@ -160,6 +160,10 @@ pub enum ServiceError { #[display(fmt = "Torrent not found in tracker.")] TorrentNotFoundInTracker, + + #[display(fmt = "Invalid tracker API token.")] + InvalidTrackerToken, + // End tracker errors } impl From for ServiceError { @@ -244,14 +248,13 @@ impl From for ServiceError { eprintln!("{e}"); match e { TrackerAPIError::TrackerOffline => ServiceError::TrackerOffline, - TrackerAPIError::AddToWhitelistError - | TrackerAPIError::RemoveFromWhitelistError - | TrackerAPIError::RetrieveUserKeyError => ServiceError::TrackerResponseError, + TrackerAPIError::InternalServerError => ServiceError::TrackerResponseError, TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker, - TrackerAPIError::MissingResponseBody | TrackerAPIError::FailedToParseTrackerResponse { body: _ } => { - ServiceError::TrackerUnknownResponse - } + TrackerAPIError::UnexpectedResponseStatus + | TrackerAPIError::MissingResponseBody + | TrackerAPIError::FailedToParseTrackerResponse { body: _ } => ServiceError::TrackerUnknownResponse, TrackerAPIError::CannotSaveUserKey => ServiceError::DatabaseError, + TrackerAPIError::InvalidToken => ServiceError::InvalidTrackerToken, } } } @@ -307,6 +310,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::TrackerResponseError => StatusCode::INTERNAL_SERVER_ERROR, ServiceError::TrackerUnknownResponse => StatusCode::INTERNAL_SERVER_ERROR, ServiceError::TorrentNotFoundInTracker => StatusCode::NOT_FOUND, + ServiceError::InvalidTrackerToken => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/src/tracker/service.rs b/src/tracker/service.rs index cb436432..265109d2 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -2,8 +2,7 @@ use std::sync::Arc; use derive_more::{Display, Error}; use hyper::StatusCode; -use log::error; -use reqwest::Response; +use log::{debug, error}; use serde::{Deserialize, Serialize}; use super::api::{Client, ConnectionInfo}; @@ -18,14 +17,14 @@ pub enum TrackerAPIError { #[display(fmt = "Error with tracker connection.")] TrackerOffline, - #[display(fmt = "Could not whitelist torrent.")] - AddToWhitelistError, + #[display(fmt = "Invalid token for tracker API. Check the tracker token in settings.")] + InvalidToken, - #[display(fmt = "Could not remove torrent from whitelist.")] - RemoveFromWhitelistError, + #[display(fmt = "Tracker returned an internal server error.")] + InternalServerError, - #[display(fmt = "Could not retrieve a new user key.")] - RetrieveUserKeyError, + #[display(fmt = "Tracker returned an unexpected response status.")] + UnexpectedResponseStatus, #[display(fmt = "Could not save the newly generated user key into the database.")] CannotSaveUserKey, @@ -98,14 +97,35 @@ impl Service { /// Will return an error if the HTTP request failed (for example if the /// tracker API is offline) or if the tracker API returned an error. pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), TrackerAPIError> { - let response = self.api_client.whitelist_torrent(&info_hash).await; + debug!(target: "tracker-service", "add to whitelist: {info_hash}"); - match response { + let maybe_response = self.api_client.whitelist_torrent(&info_hash).await; + + debug!(target: "tracker-service", "add to whitelist response result: {:?}", maybe_response); + + match maybe_response { Ok(response) => { - if response.status().is_success() { - Ok(()) - } else { - Err(TrackerAPIError::AddToWhitelistError) + let status: StatusCode = response.status(); + + let body = response.text().await.map_err(|_| { + error!(target: "tracker-service", "response without body"); + TrackerAPIError::MissingResponseBody + })?; + + match status { + StatusCode::OK => Ok(()), + StatusCode::INTERNAL_SERVER_ERROR => { + if body == "Unhandled rejection: Err { reason: \"token not valid\" }" { + Err(TrackerAPIError::InvalidToken) + } else { + error!(target: "tracker-service", "add to whitelist 500 response: status {status}, body: {body}"); + Err(TrackerAPIError::InternalServerError) + } + } + _ => { + error!(target: "tracker-service", "add to whitelist unexpected response: status {status}, body: {body}"); + Err(TrackerAPIError::UnexpectedResponseStatus) + } } } Err(_) => Err(TrackerAPIError::TrackerOffline), @@ -119,14 +139,35 @@ impl Service { /// Will return an error if the HTTP request failed (for example if the /// tracker API is offline) or if the tracker API returned an error. pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), TrackerAPIError> { - let response = self.api_client.remove_torrent_from_whitelist(&info_hash).await; + debug!(target: "tracker-service", "remove from whitelist: {info_hash}"); + + let maybe_response = self.api_client.remove_torrent_from_whitelist(&info_hash).await; - match response { + debug!(target: "tracker-service", "remove from whitelist response result: {:?}", maybe_response); + + match maybe_response { Ok(response) => { - if response.status().is_success() { - Ok(()) - } else { - Err(TrackerAPIError::RemoveFromWhitelistError) + let status: StatusCode = response.status(); + + let body = response.text().await.map_err(|_| { + error!(target: "tracker-service", "response without body"); + TrackerAPIError::MissingResponseBody + })?; + + match status { + StatusCode::OK => Ok(()), + StatusCode::INTERNAL_SERVER_ERROR => { + if body == Self::invalid_token_body() { + Err(TrackerAPIError::InvalidToken) + } else { + error!(target: "tracker-service", "remove from whitelist 500 response: status {status}, body: {body}"); + Err(TrackerAPIError::InternalServerError) + } + } + _ => { + error!(target: "tracker-service", "remove from whitelist unexpected response: status {status}, body: {body}"); + Err(TrackerAPIError::UnexpectedResponseStatus) + } } } Err(_) => Err(TrackerAPIError::TrackerOffline), @@ -145,6 +186,8 @@ impl Service { /// Will return an error if the HTTP request to get generated a new /// user tracker key failed. pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result { + debug!(target: "tracker-service", "get personal announce url for user: {user_id}"); + let tracker_key = self.database.get_user_tracker_key(user_id).await; match tracker_key { @@ -163,157 +206,115 @@ impl Service { /// Will return an error if the HTTP request to get torrent info fails or /// if the response cannot be parsed. pub async fn get_torrent_info(&self, info_hash: &str) -> Result { - let response = self.api_client.get_torrent_info(info_hash).await; + debug!(target: "tracker-service", "get torrent info: {info_hash}"); + + let maybe_response = self.api_client.get_torrent_info(info_hash).await; - match response { + debug!(target: "tracker-service", "get torrent info response result: {:?}", maybe_response); + + match maybe_response { Ok(response) => { - if response.status().is_success() { - map_torrent_info_response(response).await - } else { - Err(TrackerAPIError::RemoveFromWhitelistError) + let status: StatusCode = response.status(); + + let body = response.text().await.map_err(|_| { + error!(target: "tracker-service", "response without body"); + TrackerAPIError::MissingResponseBody + })?; + + match status { + StatusCode::NOT_FOUND => Err(TrackerAPIError::TorrentNotFound), + StatusCode::OK => { + if body == Self::torrent_not_known_body() { + // todo: temporary fix. the service should return a 404 (StatusCode::NOT_FOUND). + return Err(TrackerAPIError::TorrentNotFound); + } + + serde_json::from_str(&body).map_err(|e| { + error!( + target: "tracker-service", "Failed to parse torrent info from tracker response. Body: {}, Error: {}", + body, e + ); + TrackerAPIError::FailedToParseTrackerResponse { body } + }) + } + StatusCode::INTERNAL_SERVER_ERROR => { + if body == Self::invalid_token_body() { + Err(TrackerAPIError::InvalidToken) + } else { + error!(target: "tracker-service", "get torrent info 500 response: status {status}, body: {body}"); + Err(TrackerAPIError::InternalServerError) + } + } + _ => { + error!(target: "tracker-service", "get torrent info unhandled response: status {status}, body: {body}"); + Err(TrackerAPIError::UnexpectedResponseStatus) + } } } Err(_) => Err(TrackerAPIError::TrackerOffline), } } - /// It builds the announce url appending the user tracker key. - /// Eg: - fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String { - format!("{}/{}", self.tracker_url, tracker_key.key) - } - /// Issue a new tracker key from tracker. async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result { - let response = self.api_client.retrieve_new_tracker_key(self.token_valid_seconds).await; + debug!(target: "tracker-service", "retrieve key: {user_id}"); + + let maybe_response = self.api_client.retrieve_new_tracker_key(self.token_valid_seconds).await; + + debug!(target: "tracker-service", "retrieve key response result: {:?}", maybe_response); - match response { + match maybe_response { Ok(response) => { - if response.status().is_success() { - let body = response.text().await.map_err(|_| { - error!("Tracker API response without body"); - TrackerAPIError::MissingResponseBody - })?; - - // Parse tracker key from response - let tracker_key = - serde_json::from_str(&body).map_err(|_| TrackerAPIError::FailedToParseTrackerResponse { body })?; - - // Add tracker key to database (tied to a user) - self.database - .add_tracker_key(user_id, &tracker_key) - .await - .map_err(|_| TrackerAPIError::CannotSaveUserKey)?; - - Ok(tracker_key) - } else { - Err(TrackerAPIError::RetrieveUserKeyError) + let status: StatusCode = response.status(); + + let body = response.text().await.map_err(|_| { + error!(target: "tracker-service", "response without body"); + TrackerAPIError::MissingResponseBody + })?; + + match status { + StatusCode::OK => { + // Parse tracker key from response + let tracker_key = + serde_json::from_str(&body).map_err(|_| TrackerAPIError::FailedToParseTrackerResponse { body })?; + + // Add tracker key to database (tied to a user) + self.database + .add_tracker_key(user_id, &tracker_key) + .await + .map_err(|_| TrackerAPIError::CannotSaveUserKey)?; + + Ok(tracker_key) + } + StatusCode::INTERNAL_SERVER_ERROR => { + if body == Self::invalid_token_body() { + Err(TrackerAPIError::InvalidToken) + } else { + error!(target: "tracker-service", "retrieve key 500 response: status {status}, body: {body}"); + Err(TrackerAPIError::InternalServerError) + } + } + _ => { + error!(target: "tracker-service", " retrieve key unexpected response: status {status}, body: {body}"); + Err(TrackerAPIError::UnexpectedResponseStatus) + } } } Err(_) => Err(TrackerAPIError::TrackerOffline), } } -} -async fn map_torrent_info_response(response: Response) -> Result { - if response.status() == StatusCode::NOT_FOUND { - return Err(TrackerAPIError::TorrentNotFound); + /// It builds the announce url appending the user tracker key. + /// Eg: + fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String { + format!("{}/{}", self.tracker_url, tracker_key.key) } - let body = response.text().await.map_err(|_| { - error!("Tracker API response without body"); - TrackerAPIError::MissingResponseBody - })?; - - if body == "\"torrent not known\"" { - // todo: temporary fix. the service should return a 404 (StatusCode::NOT_FOUND). - return Err(TrackerAPIError::TorrentNotFound); + fn invalid_token_body() -> String { + "Unhandled rejection: Err { reason: \"token not valid\" }".to_string() } - serde_json::from_str(&body).map_err(|e| { - error!( - "Failed to parse torrent info from tracker response. Body: {}, Error: {}", - body, e - ); - TrackerAPIError::FailedToParseTrackerResponse { body } - }) -} - -#[cfg(test)] -mod tests { - - mod getting_the_torrent_info_from_the_tracker { - use hyper::{Response, StatusCode}; - - use crate::tracker::service::{map_torrent_info_response, TorrentInfo, TrackerAPIError}; - - #[tokio::test] - async fn it_should_return_a_torrent_not_found_response_when_the_tracker_returns_the_current_torrent_not_known_response() { - let tracker_response = Response::new("\"torrent not known\""); - - let result = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); - - assert_eq!(result, TrackerAPIError::TorrentNotFound); - } - - #[tokio::test] - async fn it_should_return_a_torrent_not_found_response_when_the_tracker_returns_the_future_torrent_not_known_response() { - // In the future the tracker should return a 4040 response. - // See: https://github.com/torrust/torrust-tracker/issues/144 - - let tracker_response = Response::builder().status(StatusCode::NOT_FOUND).body("").unwrap(); - - let result = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); - - assert_eq!(result, TrackerAPIError::TorrentNotFound); - } - - #[tokio::test] - async fn it_should_return_the_torrent_info_when_the_tracker_returns_the_torrent_info() { - let body = r#" - { - "info_hash": "4f2ae7294f2c4865c38565f92a077d1591a0dd41", - "seeders": 0, - "completed": 0, - "leechers": 0, - "peers": [] - } - "#; - - let tracker_response = Response::new(body); - - let torrent_info = map_torrent_info_response(tracker_response.into()).await.unwrap(); - - assert_eq!( - torrent_info, - TorrentInfo { - info_hash: "4f2ae7294f2c4865c38565f92a077d1591a0dd41".to_string(), - seeders: 0, - completed: 0, - leechers: 0, - peers: vec![] - } - ); - } - - #[tokio::test] - async fn it_should_return_an_internal_server_error_when_the_tracker_response_cannot_be_parsed() { - let invalid_json_body_for_torrent_info = r#" - { - "field": "value" - } - "#; - - let tracker_response = Response::new(invalid_json_body_for_torrent_info); - - let err = map_torrent_info_response(tracker_response.into()).await.unwrap_err(); - - assert_eq!( - err, - TrackerAPIError::FailedToParseTrackerResponse { - body: invalid_json_body_for_torrent_info.to_string() - } - ); - } + fn torrent_not_known_body() -> String { + "\"torrent not known\"".to_string() } } From f975bf55dc16bb72b7a86700001b819f776c3603 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 3 Jan 2024 17:18:29 +0000 Subject: [PATCH 046/309] fix: [#420] improve error loggin for statistics importer Now you can see the exact error when the tracker API fails. For example, the following error when the torrent is not found: ``` 2024-01-03T17:25:49.817570509+00:00 [statistics_importer][ERROR] Error updating torrent tracker stats for torrent with id 3; info-hash c0bae61394917c2cc3aa3af9c2291bfe80b5bbf4. Error: TorrentNotFound ``` --- src/tracker/statistics_importer.rs | 40 ++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index 128cce12..08667a0d 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -2,10 +2,9 @@ use std::sync::Arc; use log::{error, info}; -use super::service::{Service, TorrentInfo}; +use super::service::{Service, TorrentInfo, TrackerAPIError}; use crate::config::Configuration; use crate::databases::database::{self, Database}; -use crate::errors::ServiceError; pub struct StatisticsImporter { database: Arc>, @@ -39,22 +38,12 @@ impl StatisticsImporter { let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; - // code-review: should we treat differently for each case?. The - // tracker API could be temporarily offline, or there could be a - // tracker misconfiguration. - // - // This is the log when the torrent is not found in the tracker: - // - // ``` - // 2023-05-09T13:31:24.497465723+00:00 [torrust_index::tracker::statistics_importer][ERROR] Error updating torrent tracker stats for torrent with id 140: TorrentNotFound - // ``` - if let Some(err) = ret.err() { let message = format!( "Error updating torrent tracker stats for torrent with id {}: {:?}", torrent.torrent_id, err ); - error!("{}", message); + error!(target: "statistics_importer", "{}", message); } } @@ -67,17 +56,20 @@ impl StatisticsImporter { /// /// Will return an error if the HTTP request failed or the torrent is not /// found. - pub async fn import_torrent_statistics(&self, torrent_id: i64, info_hash: &str) -> Result { - if let Ok(torrent_info) = self.tracker_service.get_torrent_info(info_hash).await { - drop( - self.database - .update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers) - .await, - ); - Ok(torrent_info) - } else { - drop(self.database.update_tracker_info(torrent_id, &self.tracker_url, 0, 0).await); - Err(ServiceError::TorrentNotFound) + pub async fn import_torrent_statistics(&self, torrent_id: i64, info_hash: &str) -> Result { + match self.tracker_service.get_torrent_info(info_hash).await { + Ok(torrent_info) => { + drop( + self.database + .update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers) + .await, + ); + Ok(torrent_info) + } + Err(err) => { + drop(self.database.update_tracker_info(torrent_id, &self.tracker_url, 0, 0).await); + Err(err) + } } } } From ff90e9e17c9343cccd0734346ac76c5d95f8a9c3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 3 Jan 2024 17:31:40 +0000 Subject: [PATCH 047/309] feat: [#420] ignore torrent not found when importing statistics from tracker --- src/tracker/statistics_importer.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index 08667a0d..91c649df 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -39,11 +39,13 @@ impl StatisticsImporter { let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; if let Some(err) = ret.err() { - let message = format!( - "Error updating torrent tracker stats for torrent with id {}: {:?}", - torrent.torrent_id, err - ); - error!(target: "statistics_importer", "{}", message); + if err != TrackerAPIError::TorrentNotFound { + let message = format!( + "Error updating torrent tracker stats for torrent. Torrent: id {}; infohash {}. Error: {:?}", + torrent.torrent_id, torrent.info_hash, err + ); + error!(target: "statistics_importer", "{}", message); + } } } From 1d5c8c041aad12744d95a114ecfb158e6aae4cde Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 31 Jan 2024 16:44:21 +0100 Subject: [PATCH 048/309] refactor: [#449] fix clippy linting error --- tests/e2e/environment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 0ee8b55b..f8ae9ce4 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -82,7 +82,7 @@ impl TestEnv { /// Returns the server starting settings if the servers was already started. /// We do not know the settings until we start the server. pub fn server_settings(&self) -> Option { - self.starting_settings.as_ref().cloned() + self.starting_settings.clone() } /// Provides the API server socket address. From b1df4e85e1b5ae729c031089d55e199fee934e28 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 12:02:03 +0000 Subject: [PATCH 049/309] refactor: [#453] reorganize console mods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ```console src/console/ ├── commands │   ├── mod.rs │   └── tracker_statistics_importer │   ├── app.rs │   └── mod.rs ├── cronjobs │   ├── mod.rs │   └── tracker_statistics_importer.rs └── mod.rs ``` - We will create a new command to seed the Index with random torrents. - This new strcuture is similar to what we have in the Tracker. --- src/app.rs | 2 +- src/bin/import_tracker_statistics.rs | 6 +++--- src/console/commands/mod.rs | 2 +- .../app.rs} | 2 +- src/console/commands/tracker_statistics_importer/mod.rs | 1 + src/console/cronjobs/mod.rs | 1 + src/console/{ => cronjobs}/tracker_statistics_importer.rs | 8 ++++++-- src/console/mod.rs | 2 +- 8 files changed, 15 insertions(+), 9 deletions(-) rename src/console/commands/{import_tracker_statistics.rs => tracker_statistics_importer/app.rs} (99%) create mode 100644 src/console/commands/tracker_statistics_importer/mod.rs create mode 100644 src/console/cronjobs/mod.rs rename src/console/{ => cronjobs}/tracker_statistics_importer.rs (95%) diff --git a/src/app.rs b/src/app.rs index 22ffd4f7..7fd825df 100644 --- a/src/app.rs +++ b/src/app.rs @@ -159,7 +159,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running // Start cronjob to import tracker torrent data and updating // seeders and leechers info. - let tracker_statistics_importer_handle = console::tracker_statistics_importer::start( + let tracker_statistics_importer_handle = console::cronjobs::tracker_statistics_importer::start( importer_port, importer_torrent_info_update_interval, &tracker_statistics_importer, diff --git a/src/bin/import_tracker_statistics.rs b/src/bin/import_tracker_statistics.rs index a405248b..07a0a0c6 100644 --- a/src/bin/import_tracker_statistics.rs +++ b/src/bin/import_tracker_statistics.rs @@ -1,11 +1,11 @@ //! Import Tracker Statistics command. //! -//! It imports the number of seeders and leechers for all torrent from the linked tracker. +//! It imports the number of seeders and leechers for all torrents from the linked tracker. //! //! You can execute it with: `cargo run --bin import_tracker_statistics` -use torrust_index::console::commands::import_tracker_statistics::run_importer; +use torrust_index::console::commands::tracker_statistics_importer::app::run; #[tokio::main] async fn main() { - run_importer().await; + run().await; } diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs index 6dad4966..43be8073 100644 --- a/src/console/commands/mod.rs +++ b/src/console/commands/mod.rs @@ -1 +1 @@ -pub mod import_tracker_statistics; +pub mod tracker_statistics_importer; diff --git a/src/console/commands/import_tracker_statistics.rs b/src/console/commands/tracker_statistics_importer/app.rs similarity index 99% rename from src/console/commands/import_tracker_statistics.rs rename to src/console/commands/tracker_statistics_importer/app.rs index 08acbb31..115b7bd8 100644 --- a/src/console/commands/import_tracker_statistics.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -75,7 +75,7 @@ fn print_usage() { /// # Panics /// /// Panics if arguments cannot be parsed. -pub async fn run_importer() { +pub async fn run() { parse_args().expect("unable to parse command arguments"); import().await; } diff --git a/src/console/commands/tracker_statistics_importer/mod.rs b/src/console/commands/tracker_statistics_importer/mod.rs new file mode 100644 index 00000000..309be628 --- /dev/null +++ b/src/console/commands/tracker_statistics_importer/mod.rs @@ -0,0 +1 @@ +pub mod app; diff --git a/src/console/cronjobs/mod.rs b/src/console/cronjobs/mod.rs new file mode 100644 index 00000000..43be8073 --- /dev/null +++ b/src/console/cronjobs/mod.rs @@ -0,0 +1 @@ +pub mod tracker_statistics_importer; diff --git a/src/console/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs similarity index 95% rename from src/console/tracker_statistics_importer.rs rename to src/console/cronjobs/tracker_statistics_importer.rs index c0787ad1..4c65c5a0 100644 --- a/src/console/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -33,6 +33,10 @@ struct ImporterState { pub torrent_info_update_interval: u64, } +/// # Panics +/// +/// Will panic if it can't start the tracker statistics importer API +#[must_use] pub fn start( importer_port: u16, torrent_info_update_interval: u64, @@ -60,7 +64,7 @@ pub fn start( let addr = format!("{IMPORTER_API_IP}:{importer_port}"); - info!("Tracker statistics importer API server listening on http://{}", addr); + info!("Tracker statistics importer API server listening on http://{}", addr); // # DevSkim: ignore DS137138 axum::Server::bind(&addr.parse().unwrap()) .serve(app.into_make_service()) @@ -122,7 +126,7 @@ async fn heartbeat_handler(State(state): State>) -> Json Result<(), reqwest::Error> { let client = reqwest::Client::new(); - let url = format!("http://{IMPORTER_API_IP}:{importer_port}/heartbeat"); + let url = format!("http://{IMPORTER_API_IP}:{importer_port}/heartbeat"); // # DevSkim: ignore DS137138 client.post(url).send().await?; diff --git a/src/console/mod.rs b/src/console/mod.rs index 889c06dc..80eff453 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -1,2 +1,2 @@ pub mod commands; -pub(crate) mod tracker_statistics_importer; +pub mod cronjobs; From 118d6a5d0ea76f0fe893577d72128777972207ef Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 12:46:00 +0000 Subject: [PATCH 050/309] refactor: [#453] reorganize web mod Added `server` subfolder. We will add a client folder. --- src/app.rs | 2 +- src/common.rs | 2 +- src/lib.rs | 4 ++-- src/mailer.rs | 2 +- src/services/about.rs | 2 +- src/services/user.rs | 2 +- src/web/api/mod.rs | 1 - src/web/api/{server.rs => server/mod.rs} | 8 +++++--- src/web/api/{ => server}/v1/auth.rs | 2 +- .../v1/contexts/about/handlers.rs | 2 +- .../api/{ => server}/v1/contexts/about/mod.rs | 0 .../{ => server}/v1/contexts/about/routes.rs | 6 +++--- .../v1/contexts/category/forms.rs | 0 .../v1/contexts/category/handlers.rs | 8 ++++---- .../{ => server}/v1/contexts/category/mod.rs | 0 .../v1/contexts/category/responses.rs | 4 ++-- .../v1/contexts/category/routes.rs | 6 +++--- src/web/api/server/v1/contexts/mod.rs | 19 +++++++++++++++++++ .../v1/contexts/proxy/handlers.rs | 4 ++-- .../api/{ => server}/v1/contexts/proxy/mod.rs | 0 .../v1/contexts/proxy/responses.rs | 0 .../{ => server}/v1/contexts/proxy/routes.rs | 6 +++--- .../v1/contexts/settings/handlers.rs | 6 +++--- .../{ => server}/v1/contexts/settings/mod.rs | 0 .../v1/contexts/settings/routes.rs | 6 +++--- .../api/{ => server}/v1/contexts/tag/forms.rs | 2 +- .../{ => server}/v1/contexts/tag/handlers.rs | 8 ++++---- .../api/{ => server}/v1/contexts/tag/mod.rs | 0 .../{ => server}/v1/contexts/tag/responses.rs | 4 ++-- .../{ => server}/v1/contexts/tag/routes.rs | 8 ++++---- .../v1/contexts/torrent/errors.rs | 2 +- .../{ => server}/v1/contexts/torrent/forms.rs | 0 .../v1/contexts/torrent/handlers.rs | 10 +++++----- .../{ => server}/v1/contexts/torrent/mod.rs | 2 +- .../v1/contexts/torrent/responses.rs | 2 +- .../v1/contexts/torrent/routes.rs | 8 ++++---- .../{ => server}/v1/contexts/user/forms.rs | 0 .../{ => server}/v1/contexts/user/handlers.rs | 6 +++--- .../api/{ => server}/v1/contexts/user/mod.rs | 14 +++++++------- .../v1/contexts/user/responses.rs | 2 +- .../{ => server}/v1/contexts/user/routes.rs | 6 +++--- .../v1/extractors/bearer_token.rs | 2 +- src/web/api/{ => server}/v1/extractors/mod.rs | 0 src/web/api/{ => server}/v1/mod.rs | 0 src/web/api/{ => server}/v1/responses.rs | 0 src/web/api/{ => server}/v1/routes.rs | 0 src/web/api/v1/contexts/mod.rs | 19 ------------------- src/web/mod.rs | 2 +- tests/common/asserts.rs | 2 +- .../e2e/web/api/v1/contexts/torrent/steps.rs | 2 +- 50 files changed, 97 insertions(+), 96 deletions(-) rename src/web/api/{server.rs => server/mod.rs} (93%) rename src/web/api/{ => server}/v1/auth.rs (98%) rename src/web/api/{ => server}/v1/contexts/about/handlers.rs (89%) rename src/web/api/{ => server}/v1/contexts/about/mod.rs (100%) rename src/web/api/{ => server}/v1/contexts/about/routes.rs (58%) rename src/web/api/{ => server}/v1/contexts/category/forms.rs (100%) rename src/web/api/{ => server}/v1/contexts/category/handlers.rs (90%) rename src/web/api/{ => server}/v1/contexts/category/mod.rs (100%) rename src/web/api/{ => server}/v1/contexts/category/responses.rs (74%) rename src/web/api/{ => server}/v1/contexts/category/routes.rs (61%) create mode 100644 src/web/api/server/v1/contexts/mod.rs rename src/web/api/{ => server}/v1/contexts/proxy/handlers.rs (91%) rename src/web/api/{ => server}/v1/contexts/proxy/mod.rs (100%) rename src/web/api/{ => server}/v1/contexts/proxy/responses.rs (100%) rename src/web/api/{ => server}/v1/contexts/proxy/routes.rs (51%) rename src/web/api/{ => server}/v1/contexts/settings/handlers.rs (87%) rename src/web/api/{ => server}/v1/contexts/settings/mod.rs (100%) rename src/web/api/{ => server}/v1/contexts/settings/routes.rs (62%) rename src/web/api/{ => server}/v1/contexts/tag/forms.rs (76%) rename src/web/api/{ => server}/v1/contexts/tag/handlers.rs (89%) rename src/web/api/{ => server}/v1/contexts/tag/mod.rs (100%) rename src/web/api/{ => server}/v1/contexts/tag/responses.rs (74%) rename src/web/api/{ => server}/v1/contexts/tag/routes.rs (64%) rename src/web/api/{ => server}/v1/contexts/torrent/errors.rs (96%) rename src/web/api/{ => server}/v1/contexts/torrent/forms.rs (100%) rename src/web/api/{ => server}/v1/contexts/torrent/handlers.rs (97%) rename src/web/api/{ => server}/v1/contexts/torrent/mod.rs (99%) rename src/web/api/{ => server}/v1/contexts/torrent/responses.rs (96%) rename src/web/api/{ => server}/v1/contexts/torrent/routes.rs (75%) rename src/web/api/{ => server}/v1/contexts/user/forms.rs (100%) rename src/web/api/{ => server}/v1/contexts/user/handlers.rs (95%) rename src/web/api/{ => server}/v1/contexts/user/mod.rs (93%) rename src/web/api/{ => server}/v1/contexts/user/responses.rs (95%) rename src/web/api/{ => server}/v1/contexts/user/routes.rs (83%) rename src/web/api/{ => server}/v1/extractors/bearer_token.rs (93%) rename src/web/api/{ => server}/v1/extractors/mod.rs (100%) rename src/web/api/{ => server}/v1/mod.rs (100%) rename src/web/api/{ => server}/v1/responses.rs (100%) rename src/web/api/{ => server}/v1/routes.rs (100%) delete mode 100644 src/web/api/v1/contexts/mod.rs diff --git a/src/app.rs b/src/app.rs index 7fd825df..bb71d5dc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,7 +18,7 @@ use crate::services::torrent::{ use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository}; use crate::services::{proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; -use crate::web::api::v1::auth::Authentication; +use crate::web::api::server::v1::auth::Authentication; use crate::web::api::Version; use crate::{console, mailer, tracker, web}; diff --git a/src/common.rs b/src/common.rs index bf16889a..755a775b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -13,7 +13,7 @@ use crate::services::torrent::{ use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository}; use crate::services::{proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; -use crate::web::api::v1::auth::Authentication; +use crate::web::api::server::v1::auth::Authentication; use crate::{mailer, tracker}; pub type Username = String; diff --git a/src/lib.rs b/src/lib.rs index ba89003c..45e6ad24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! used with by the [Torrust Tracker Index Gui](https://github.com/torrust/torrust-index-gui). //! //! If you are looking for information on how to use the API, please see the -//! [API v1](crate::web::api::v1) section of the documentation. +//! [API v1](crate::web::api::server::v1) section of the documentation. //! //! # Table of contents //! @@ -37,7 +37,7 @@ //! //! From the end-user perspective the Torrust Tracker exposes three different services. //! -//! - A REST [API](crate::web::api::v1) +//! - A REST [API](crate::web::api::server::v1) //! //! From the administrator perspective, the Torrust Index exposes: //! diff --git a/src/mailer.rs b/src/mailer.rs index 0c48acd6..39c1d0d4 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -13,7 +13,7 @@ use tera::{try_get_value, Context, Tera}; use crate::config::Configuration; use crate::errors::ServiceError; use crate::utils::clock; -use crate::web::api::v1::routes::API_VERSION_URL_PREFIX; +use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; lazy_static! { pub static ref TEMPLATES: Tera = { diff --git a/src/services/about.rs b/src/services/about.rs index 82175bf6..28eedc02 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -1,5 +1,5 @@ //! Templates for "about" static pages. -use crate::web::api::v1::routes::API_VERSION_URL_PREFIX; +use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; #[must_use] pub fn index_page() -> String { diff --git a/src/services/user.rs b/src/services/user.rs index 358e7431..cd4e5937 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -14,7 +14,7 @@ use crate::mailer; use crate::mailer::VerifyClaims; use crate::models::user::{UserCompact, UserId, UserProfile}; use crate::utils::validation::validate_email_address; -use crate::web::api::v1::contexts::user::forms::RegistrationForm; +use crate::web::api::server::v1::contexts::user::forms::RegistrationForm; /// Since user email could be optional, we need a way to represent "no email" /// in the database. This function returns the string that should be used for diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index 749008f1..a9789a7f 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -4,7 +4,6 @@ //! //! Refer to the [`v1`]) module for more information. pub mod server; -pub mod v1; use std::net::SocketAddr; use std::sync::Arc; diff --git a/src/web/api/server.rs b/src/web/api/server/mod.rs similarity index 93% rename from src/web/api/server.rs rename to src/web/api/server/mod.rs index 8fa1e704..a9756c68 100644 --- a/src/web/api/server.rs +++ b/src/web/api/server/mod.rs @@ -1,3 +1,5 @@ +pub mod v1; + use std::net::SocketAddr; use std::sync::Arc; @@ -5,9 +7,9 @@ use futures::Future; use log::info; use tokio::sync::oneshot::{self, Sender}; -use super::v1::routes::router; use super::{Running, ServerStartedMessage}; use crate::common::AppData; +use v1::routes::router; /// Starts the API server. /// @@ -57,7 +59,7 @@ fn start_server( .local_addr() .expect("tcp listener to be bound to a socket address."); - info!("API server listening on http://{}", bound_addr); + info!("API server listening on http://{}", bound_addr); // # DevSkim: ignore DS137138 let app = router(app_data); @@ -70,6 +72,6 @@ fn start_server( server.with_graceful_shutdown(async move { tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping API server on http://{} ...", bound_addr); + info!("Stopping API server on http://{} ...", bound_addr); // # DevSkim: ignore DS137138 }) } diff --git a/src/web/api/v1/auth.rs b/src/web/api/server/v1/auth.rs similarity index 98% rename from src/web/api/v1/auth.rs rename to src/web/api/server/v1/auth.rs index e52542cc..3e355b30 100644 --- a/src/web/api/v1/auth.rs +++ b/src/web/api/server/v1/auth.rs @@ -86,7 +86,7 @@ use crate::common::AppData; use crate::errors::ServiceError; use crate::models::user::{UserClaims, UserCompact, UserId}; use crate::services::authentication::JsonWebToken; -use crate::web::api::v1::extractors::bearer_token::BearerToken; +use crate::web::api::server::v1::extractors::bearer_token::BearerToken; pub struct Authentication { json_web_token: Arc, diff --git a/src/web/api/v1/contexts/about/handlers.rs b/src/web/api/server/v1/contexts/about/handlers.rs similarity index 89% rename from src/web/api/v1/contexts/about/handlers.rs rename to src/web/api/server/v1/contexts/about/handlers.rs index 07d5977b..76004133 100644 --- a/src/web/api/v1/contexts/about/handlers.rs +++ b/src/web/api/server/v1/contexts/about/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the the [`about`](crate::web::api::v1::contexts::about) API +//! API handlers for the the [`about`](crate::web::api::server::v1::contexts::about) API //! context. use std::sync::Arc; diff --git a/src/web/api/v1/contexts/about/mod.rs b/src/web/api/server/v1/contexts/about/mod.rs similarity index 100% rename from src/web/api/v1/contexts/about/mod.rs rename to src/web/api/server/v1/contexts/about/mod.rs diff --git a/src/web/api/v1/contexts/about/routes.rs b/src/web/api/server/v1/contexts/about/routes.rs similarity index 58% rename from src/web/api/v1/contexts/about/routes.rs rename to src/web/api/server/v1/contexts/about/routes.rs index d3877a3b..8a9ccef7 100644 --- a/src/web/api/v1/contexts/about/routes.rs +++ b/src/web/api/server/v1/contexts/about/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`about`](crate::web::api::v1::contexts::about) API context. +//! API routes for the [`about`](crate::web::api::server::v1::contexts::about) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::about). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::about). use std::sync::Arc; use axum::routing::get; @@ -9,7 +9,7 @@ use axum::Router; use super::handlers::{about_page_handler, license_page_handler}; use crate::common::AppData; -/// Routes for the [`about`](crate::web::api::v1::contexts::about) API context. +/// Routes for the [`about`](crate::web::api::server::v1::contexts::about) API context. pub fn router(app_data: Arc) -> Router { Router::new() .route("/", get(about_page_handler).with_state(app_data.clone())) diff --git a/src/web/api/v1/contexts/category/forms.rs b/src/web/api/server/v1/contexts/category/forms.rs similarity index 100% rename from src/web/api/v1/contexts/category/forms.rs rename to src/web/api/server/v1/contexts/category/forms.rs diff --git a/src/web/api/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs similarity index 90% rename from src/web/api/v1/contexts/category/handlers.rs rename to src/web/api/server/v1/contexts/category/handlers.rs index da0c1209..b73b15c5 100644 --- a/src/web/api/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the the [`category`](crate::web::api::v1::contexts::category) API +//! API handlers for the the [`category`](crate::web::api::server::v1::contexts::category) API //! context. use std::sync::Arc; @@ -8,8 +8,8 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddCategoryForm, DeleteCategoryForm}; use super::responses::{added_category, deleted_category}; use crate::common::AppData; -use crate::web::api::v1::extractors::bearer_token::Extract; -use crate::web::api::v1::responses::{self}; +use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the categories. /// @@ -18,7 +18,7 @@ use crate::web::api::v1::responses::{self}; /// - `200` response with a json containing the category list [`Vec`](crate::databases::database::Category). /// - Other error status codes if there is a database error. /// -/// Refer to the [API endpoint documentation](crate::web::api::v1::contexts::category) +/// Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::category) /// for more information about this endpoint. /// /// # Errors diff --git a/src/web/api/v1/contexts/category/mod.rs b/src/web/api/server/v1/contexts/category/mod.rs similarity index 100% rename from src/web/api/v1/contexts/category/mod.rs rename to src/web/api/server/v1/contexts/category/mod.rs diff --git a/src/web/api/v1/contexts/category/responses.rs b/src/web/api/server/v1/contexts/category/responses.rs similarity index 74% rename from src/web/api/v1/contexts/category/responses.rs rename to src/web/api/server/v1/contexts/category/responses.rs index b1e20d19..2f761d2c 100644 --- a/src/web/api/v1/contexts/category/responses.rs +++ b/src/web/api/server/v1/contexts/category/responses.rs @@ -1,8 +1,8 @@ -//! API responses for the the [`category`](crate::web::api::v1::contexts::category) API +//! API responses for the the [`category`](crate::web::api::server::v1::contexts::category) API //! context. use axum::Json; -use crate::web::api::v1::responses::OkResponseData; +use crate::web::api::server::v1::responses::OkResponseData; /// Response after successfully creating a new category. pub fn added_category(category_name: &str) -> Json> { diff --git a/src/web/api/v1/contexts/category/routes.rs b/src/web/api/server/v1/contexts/category/routes.rs similarity index 61% rename from src/web/api/v1/contexts/category/routes.rs rename to src/web/api/server/v1/contexts/category/routes.rs index 2d762c47..df8e728d 100644 --- a/src/web/api/v1/contexts/category/routes.rs +++ b/src/web/api/server/v1/contexts/category/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`category`](crate::web::api::v1::contexts::category) API context. +//! API routes for the [`category`](crate::web::api::server::v1::contexts::category) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::category). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::category). use std::sync::Arc; use axum::routing::{delete, get, post}; @@ -9,7 +9,7 @@ use axum::Router; use super::handlers::{add_handler, delete_handler, get_all_handler}; use crate::common::AppData; -/// Routes for the [`category`](crate::web::api::v1::contexts::category) API context. +/// Routes for the [`category`](crate::web::api::server::v1::contexts::category) API context. pub fn router(app_data: Arc) -> Router { Router::new() .route("/", get(get_all_handler).with_state(app_data.clone())) diff --git a/src/web/api/server/v1/contexts/mod.rs b/src/web/api/server/v1/contexts/mod.rs new file mode 100644 index 00000000..a39cbf4e --- /dev/null +++ b/src/web/api/server/v1/contexts/mod.rs @@ -0,0 +1,19 @@ +//! The API is organized in the following contexts: +//! +//! Context | Description | Version +//! ---|---|--- +//! `About` | Metadata about the API | [`v1`](crate::web::api::server::v1::contexts::about) +//! `Category` | Torrent categories | [`v1`](crate::web::api::server::v1::contexts::category) +//! `Proxy` | Image proxy cache | [`v1`](crate::web::api::server::v1::contexts::proxy) +//! `Settings` | Index settings | [`v1`](crate::web::api::server::v1::contexts::settings) +//! `Tag` | Torrent tags | [`v1`](crate::web::api::server::v1::contexts::tag) +//! `Torrent` | Indexed torrents | [`v1`](crate::web::api::server::v1::contexts::torrent) +//! `User` | Users | [`v1`](crate::web::api::server::v1::contexts::user) +//! +pub mod about; +pub mod category; +pub mod proxy; +pub mod settings; +pub mod tag; +pub mod torrent; +pub mod user; diff --git a/src/web/api/v1/contexts/proxy/handlers.rs b/src/web/api/server/v1/contexts/proxy/handlers.rs similarity index 91% rename from src/web/api/v1/contexts/proxy/handlers.rs rename to src/web/api/server/v1/contexts/proxy/handlers.rs index 1e5105ee..7c04e50f 100644 --- a/src/web/api/v1/contexts/proxy/handlers.rs +++ b/src/web/api/server/v1/contexts/proxy/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the the [`proxy`](crate::web::api::v1::contexts::proxy) API +//! API handlers for the the [`proxy`](crate::web::api::server::v1::contexts::proxy) API //! context. use std::sync::Arc; @@ -9,7 +9,7 @@ use super::responses::png_image; use crate::cache::image::manager::Error; use crate::common::AppData; use crate::ui::proxy::map_error_to_image; -use crate::web::api::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::bearer_token::Extract; /// Get the remote image. It uses the cached image if available. #[allow(clippy::unused_async)] diff --git a/src/web/api/v1/contexts/proxy/mod.rs b/src/web/api/server/v1/contexts/proxy/mod.rs similarity index 100% rename from src/web/api/v1/contexts/proxy/mod.rs rename to src/web/api/server/v1/contexts/proxy/mod.rs diff --git a/src/web/api/v1/contexts/proxy/responses.rs b/src/web/api/server/v1/contexts/proxy/responses.rs similarity index 100% rename from src/web/api/v1/contexts/proxy/responses.rs rename to src/web/api/server/v1/contexts/proxy/responses.rs diff --git a/src/web/api/v1/contexts/proxy/routes.rs b/src/web/api/server/v1/contexts/proxy/routes.rs similarity index 51% rename from src/web/api/v1/contexts/proxy/routes.rs rename to src/web/api/server/v1/contexts/proxy/routes.rs index e6bd7bef..b12ff0ee 100644 --- a/src/web/api/v1/contexts/proxy/routes.rs +++ b/src/web/api/server/v1/contexts/proxy/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`proxy`](crate::web::api::v1::contexts::proxy) API context. +//! API routes for the [`proxy`](crate::web::api::server::v1::contexts::proxy) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::proxy). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::proxy). use std::sync::Arc; use axum::routing::get; @@ -9,7 +9,7 @@ use axum::Router; use super::handlers::get_proxy_image_handler; use crate::common::AppData; -/// Routes for the [`about`](crate::web::api::v1::contexts::about) API context. +/// Routes for the [`about`](crate::web::api::server::v1::contexts::about) API context. pub fn router(app_data: Arc) -> Router { Router::new().route("/image/:url", get(get_proxy_image_handler).with_state(app_data)) } diff --git a/src/web/api/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs similarity index 87% rename from src/web/api/v1/contexts/settings/handlers.rs rename to src/web/api/server/v1/contexts/settings/handlers.rs index f4d94541..48c0cb91 100644 --- a/src/web/api/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the the [`category`](crate::web::api::v1::contexts::category) API +//! API handlers for the the [`category`](crate::web::api::server::v1::contexts::category) API //! context. use std::sync::Arc; @@ -6,8 +6,8 @@ use axum::extract::State; use axum::response::{IntoResponse, Json, Response}; use crate::common::AppData; -use crate::web::api::v1::extractors::bearer_token::Extract; -use crate::web::api::v1::responses; +use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::responses; /// Get all settings. /// diff --git a/src/web/api/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs similarity index 100% rename from src/web/api/v1/contexts/settings/mod.rs rename to src/web/api/server/v1/contexts/settings/mod.rs diff --git a/src/web/api/v1/contexts/settings/routes.rs b/src/web/api/server/v1/contexts/settings/routes.rs similarity index 62% rename from src/web/api/v1/contexts/settings/routes.rs rename to src/web/api/server/v1/contexts/settings/routes.rs index e0990f52..ebca3bb6 100644 --- a/src/web/api/v1/contexts/settings/routes.rs +++ b/src/web/api/server/v1/contexts/settings/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`settings`](crate::web::api::v1::contexts::settings) API context. +//! API routes for the [`settings`](crate::web::api::server::v1::contexts::settings) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::settings). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::settings). use std::sync::Arc; use axum::routing::get; @@ -9,7 +9,7 @@ use axum::Router; use super::handlers::{get_all_handler, get_public_handler, get_site_name_handler}; use crate::common::AppData; -/// Routes for the [`category`](crate::web::api::v1::contexts::category) API context. +/// Routes for the [`category`](crate::web::api::server::v1::contexts::category) API context. pub fn router(app_data: Arc) -> Router { Router::new() .route("/", get(get_all_handler).with_state(app_data.clone())) diff --git a/src/web/api/v1/contexts/tag/forms.rs b/src/web/api/server/v1/contexts/tag/forms.rs similarity index 76% rename from src/web/api/v1/contexts/tag/forms.rs rename to src/web/api/server/v1/contexts/tag/forms.rs index 12c751ad..d254b324 100644 --- a/src/web/api/v1/contexts/tag/forms.rs +++ b/src/web/api/server/v1/contexts/tag/forms.rs @@ -1,4 +1,4 @@ -//! API forms for the the [`tag`](crate::web::api::v1::contexts::tag) API +//! API forms for the the [`tag`](crate::web::api::server::v1::contexts::tag) API //! context. use serde::{Deserialize, Serialize}; diff --git a/src/web/api/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs similarity index 89% rename from src/web/api/v1/contexts/tag/handlers.rs rename to src/web/api/server/v1/contexts/tag/handlers.rs index f750c385..5ba38249 100644 --- a/src/web/api/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the [`tag`](crate::web::api::v1::contexts::tag) API +//! API handlers for the [`tag`](crate::web::api::server::v1::contexts::tag) API //! context. use std::sync::Arc; @@ -8,8 +8,8 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddTagForm, DeleteTagForm}; use super::responses::{added_tag, deleted_tag}; use crate::common::AppData; -use crate::web::api::v1::extractors::bearer_token::Extract; -use crate::web::api::v1::responses::{self}; +use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the tags. /// @@ -18,7 +18,7 @@ use crate::web::api::v1::responses::{self}; /// - `200` response with a json containing the tag list [`Vec`](crate::models::torrent_tag::TorrentTag). /// - Other error status codes if there is a database error. /// -/// Refer to the [API endpoint documentation](crate::web::api::v1::contexts::tag) +/// Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::tag) /// for more information about this endpoint. /// /// # Errors diff --git a/src/web/api/v1/contexts/tag/mod.rs b/src/web/api/server/v1/contexts/tag/mod.rs similarity index 100% rename from src/web/api/v1/contexts/tag/mod.rs rename to src/web/api/server/v1/contexts/tag/mod.rs diff --git a/src/web/api/v1/contexts/tag/responses.rs b/src/web/api/server/v1/contexts/tag/responses.rs similarity index 74% rename from src/web/api/v1/contexts/tag/responses.rs rename to src/web/api/server/v1/contexts/tag/responses.rs index a1645994..ac1e2c75 100644 --- a/src/web/api/v1/contexts/tag/responses.rs +++ b/src/web/api/server/v1/contexts/tag/responses.rs @@ -1,9 +1,9 @@ -//! API responses for the [`tag`](crate::web::api::v1::contexts::tag) API +//! API responses for the [`tag`](crate::web::api::server::v1::contexts::tag) API //! context. use axum::Json; use crate::models::torrent_tag::TagId; -use crate::web::api::v1::responses::OkResponseData; +use crate::web::api::server::v1::responses::OkResponseData; /// Response after successfully creating a new tag. pub fn added_tag(tag_name: &str) -> Json> { diff --git a/src/web/api/v1/contexts/tag/routes.rs b/src/web/api/server/v1/contexts/tag/routes.rs similarity index 64% rename from src/web/api/v1/contexts/tag/routes.rs rename to src/web/api/server/v1/contexts/tag/routes.rs index 4d72970a..0ec554e4 100644 --- a/src/web/api/v1/contexts/tag/routes.rs +++ b/src/web/api/server/v1/contexts/tag/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`tag`](crate::web::api::v1::contexts::tag) API context. +//! API routes for the [`tag`](crate::web::api::server::v1::contexts::tag) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::tag). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::tag). use std::sync::Arc; use axum::routing::{delete, get, post}; @@ -11,14 +11,14 @@ use crate::common::AppData; // code-review: should we use `tags` also for single resources? -/// Routes for the [`tag`](crate::web::api::v1::contexts::tag) API context. +/// Routes for the [`tag`](crate::web::api::server::v1::contexts::tag) API context. pub fn router_for_single_resources(app_data: Arc) -> Router { Router::new() .route("/", post(add_handler).with_state(app_data.clone())) .route("/", delete(delete_handler).with_state(app_data)) } -/// Routes for the [`tag`](crate::web::api::v1::contexts::tag) API context. +/// Routes for the [`tag`](crate::web::api::server::v1::contexts::tag) API context. pub fn router_for_multiple_resources(app_data: Arc) -> Router { Router::new().route("/", get(get_all_handler).with_state(app_data)) } diff --git a/src/web/api/v1/contexts/torrent/errors.rs b/src/web/api/server/v1/contexts/torrent/errors.rs similarity index 96% rename from src/web/api/v1/contexts/torrent/errors.rs rename to src/web/api/server/v1/contexts/torrent/errors.rs index 9bf24d48..87166419 100644 --- a/src/web/api/v1/contexts/torrent/errors.rs +++ b/src/web/api/server/v1/contexts/torrent/errors.rs @@ -2,7 +2,7 @@ use axum::response::{IntoResponse, Response}; use derive_more::{Display, Error}; use hyper::StatusCode; -use crate::web::api::v1::responses::{json_error_response, ErrorResponseData}; +use crate::web::api::server::v1::responses::{json_error_response, ErrorResponseData}; #[derive(Debug, Display, PartialEq, Eq, Error)] pub enum Request { diff --git a/src/web/api/v1/contexts/torrent/forms.rs b/src/web/api/server/v1/contexts/torrent/forms.rs similarity index 100% rename from src/web/api/v1/contexts/torrent/forms.rs rename to src/web/api/server/v1/contexts/torrent/forms.rs diff --git a/src/web/api/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs similarity index 97% rename from src/web/api/v1/contexts/torrent/handlers.rs rename to src/web/api/server/v1/contexts/torrent/handlers.rs index bab51443..745a81df 100644 --- a/src/web/api/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the [`torrent`](crate::web::api::v1::contexts::torrent) API +//! API handlers for the [`torrent`](crate::web::api::server::v1::contexts::torrent) API //! context. use std::io::{Cursor, Write}; use std::str::FromStr; @@ -21,10 +21,10 @@ use crate::models::torrent_tag::TagId; use crate::services::torrent::{AddTorrentRequest, ListingRequest}; use crate::services::torrent_file::generate_random_torrent; use crate::utils::parse_torrent; -use crate::web::api::v1::auth::get_optional_logged_in_user; -use crate::web::api::v1::extractors::bearer_token::Extract; -use crate::web::api::v1::responses::OkResponseData; -use crate::web::api::v1::routes::API_VERSION_URL_PREFIX; +use crate::web::api::server::v1::auth::get_optional_logged_in_user; +use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::responses::OkResponseData; +use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; /// Upload a new torrent file to the Index /// diff --git a/src/web/api/v1/contexts/torrent/mod.rs b/src/web/api/server/v1/contexts/torrent/mod.rs similarity index 99% rename from src/web/api/v1/contexts/torrent/mod.rs rename to src/web/api/server/v1/contexts/torrent/mod.rs index 6b047940..3f915f76 100644 --- a/src/web/api/v1/contexts/torrent/mod.rs +++ b/src/web/api/server/v1/contexts/torrent/mod.rs @@ -271,7 +271,7 @@ //! `tags` | `Option>` | The tag Id list | No | `[1,2,3]` //! //! -//! Refer to the [`UpdateTorrentInfoForm`](crate::web::api::v1::contexts::torrent::forms::UpdateTorrentInfoForm) +//! Refer to the [`UpdateTorrentInfoForm`](crate::web::api::server::v1::contexts::torrent::forms::UpdateTorrentInfoForm) //! struct for more information about the request attributes. //! //! **Example request** diff --git a/src/web/api/v1/contexts/torrent/responses.rs b/src/web/api/server/v1/contexts/torrent/responses.rs similarity index 96% rename from src/web/api/v1/contexts/torrent/responses.rs rename to src/web/api/server/v1/contexts/torrent/responses.rs index 9873b420..d928f675 100644 --- a/src/web/api/v1/contexts/torrent/responses.rs +++ b/src/web/api/server/v1/contexts/torrent/responses.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::models::torrent::TorrentId; use crate::services::torrent::AddTorrentResponse; -use crate::web::api::v1::responses::OkResponseData; +use crate::web::api::server::v1::responses::OkResponseData; #[allow(clippy::module_name_repetitions)] #[derive(Serialize, Deserialize, Debug)] diff --git a/src/web/api/v1/contexts/torrent/routes.rs b/src/web/api/server/v1/contexts/torrent/routes.rs similarity index 75% rename from src/web/api/v1/contexts/torrent/routes.rs rename to src/web/api/server/v1/contexts/torrent/routes.rs index 1c529599..952ff396 100644 --- a/src/web/api/v1/contexts/torrent/routes.rs +++ b/src/web/api/server/v1/contexts/torrent/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`torrent`](crate::web::api::v1::contexts::torrent) API context. +//! API routes for the [`torrent`](crate::web::api::server::v1::contexts::torrent) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::torrent). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::torrent). use std::sync::Arc; use axum::routing::{delete, get, post, put}; @@ -12,7 +12,7 @@ use super::handlers::{ }; use crate::common::AppData; -/// Routes for the [`torrent`](crate::web::api::v1::contexts::torrent) API context for single resources. +/// Routes for the [`torrent`](crate::web::api::server::v1::contexts::torrent) API context for single resources. pub fn router_for_single_resources(app_data: Arc) -> Router { let torrent_info_routes = Router::new() .route("/", get(get_torrent_info_handler).with_state(app_data.clone())) @@ -32,7 +32,7 @@ pub fn router_for_single_resources(app_data: Arc) -> Router { .nest("/:info_hash", torrent_info_routes) } -/// Routes for the [`torrent`](crate::web::api::v1::contexts::torrent) API context for multiple resources. +/// Routes for the [`torrent`](crate::web::api::server::v1::contexts::torrent) API context for multiple resources. pub fn router_for_multiple_resources(app_data: Arc) -> Router { Router::new().route("/", get(get_torrents_handler).with_state(app_data)) } diff --git a/src/web/api/v1/contexts/user/forms.rs b/src/web/api/server/v1/contexts/user/forms.rs similarity index 100% rename from src/web/api/v1/contexts/user/forms.rs rename to src/web/api/server/v1/contexts/user/forms.rs diff --git a/src/web/api/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs similarity index 95% rename from src/web/api/v1/contexts/user/handlers.rs rename to src/web/api/server/v1/contexts/user/handlers.rs index 170bd073..ee33d2e0 100644 --- a/src/web/api/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -1,4 +1,4 @@ -//! API handlers for the the [`user`](crate::web::api::v1::contexts::user) API +//! API handlers for the the [`user`](crate::web::api::server::v1::contexts::user) API //! context. use std::sync::Arc; @@ -10,8 +10,8 @@ use serde::Deserialize; use super::forms::{JsonWebToken, LoginForm, RegistrationForm}; use super::responses::{self}; use crate::common::AppData; -use crate::web::api::v1::extractors::bearer_token::Extract; -use crate::web::api::v1::responses::OkResponseData; +use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::responses::OkResponseData; // Registration diff --git a/src/web/api/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs similarity index 93% rename from src/web/api/v1/contexts/user/mod.rs rename to src/web/api/server/v1/contexts/user/mod.rs index 4f4682e0..a13c2bb8 100644 --- a/src/web/api/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -6,7 +6,7 @@ //! - User authentication //! - User ban //! -//! For more information about the API authentication, refer to the [`auth`](crate::web::api::v1::auth) +//! For more information about the API authentication, refer to the [`auth`](crate::web::api::server::v1::auth) //! module. //! //! # Endpoints @@ -50,7 +50,7 @@ //! max_password_length = 64 //! ``` //! -//! Refer to the [`RegistrationForm`](crate::web::api::v1::contexts::user::forms::RegistrationForm) +//! Refer to the [`RegistrationForm`](crate::web::api::server::v1::contexts::user::forms::RegistrationForm) //! struct for more information about the registration form. //! //! **Example request** @@ -63,7 +63,7 @@ //! http://127.0.0.1:3001/v1/user/register //! ``` //! -//! For more information about the registration process, refer to the [`auth`](crate::web::api::v1::auth) +//! For more information about the registration process, refer to the [`auth`](crate::web::api::server::v1::auth) //! module. //! //! # Email verification @@ -97,7 +97,7 @@ //! `login` | `String` | The password | Yes | `indexadmin` //! `password` | `String` | The password | Yes | `BenoitMandelbrot1924` //! -//! Refer to the [`LoginForm`](crate::web::api::v1::contexts::user::forms::LoginForm) +//! Refer to the [`LoginForm`](crate::web::api::server::v1::contexts::user::forms::LoginForm) //! struct for more information about the login form. //! //! **Example request** @@ -110,7 +110,7 @@ //! http://127.0.0.1:3001/v1/user/login //! ``` //! -//! For more information about the login process, refer to the [`auth`](crate::web::api::v1::auth) +//! For more information about the login process, refer to the [`auth`](crate::web::api::server::v1::auth) //! module. //! //! # Token verification @@ -125,7 +125,7 @@ //! ---|---|---|---|--- //! `token` | `String` | The token you want to verify | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` //! -//! Refer to the [`JsonWebToken`](crate::web::api::v1::contexts::user::forms::JsonWebToken) +//! Refer to the [`JsonWebToken`](crate::web::api::server::v1::contexts::user::forms::JsonWebToken) //! struct for more information about the token. //! //! **Example request** @@ -171,7 +171,7 @@ //! ---|---|---|---|--- //! `token` | `String` | The current valid token | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` //! -//! Refer to the [`JsonWebToken`](crate::web::api::v1::contexts::user::forms::JsonWebToken) +//! Refer to the [`JsonWebToken`](crate::web::api::server::v1::contexts::user::forms::JsonWebToken) //! struct for more information about the token. //! //! **Example request** diff --git a/src/web/api/v1/contexts/user/responses.rs b/src/web/api/server/v1/contexts/user/responses.rs similarity index 95% rename from src/web/api/v1/contexts/user/responses.rs rename to src/web/api/server/v1/contexts/user/responses.rs index 17a06bdf..fde7c78b 100644 --- a/src/web/api/v1/contexts/user/responses.rs +++ b/src/web/api/server/v1/contexts/user/responses.rs @@ -2,7 +2,7 @@ use axum::Json; use serde::{Deserialize, Serialize}; use crate::models::user::{UserCompact, UserId}; -use crate::web::api::v1::responses::OkResponseData; +use crate::web::api::server::v1::responses::OkResponseData; // Registration diff --git a/src/web/api/v1/contexts/user/routes.rs b/src/web/api/server/v1/contexts/user/routes.rs similarity index 83% rename from src/web/api/v1/contexts/user/routes.rs rename to src/web/api/server/v1/contexts/user/routes.rs index b2a21624..04ae9980 100644 --- a/src/web/api/v1/contexts/user/routes.rs +++ b/src/web/api/server/v1/contexts/user/routes.rs @@ -1,6 +1,6 @@ -//! API routes for the [`user`](crate::web::api::v1::contexts::user) API context. +//! API routes for the [`user`](crate::web::api::server::v1::contexts::user) API context. //! -//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::user). +//! Refer to the [API endpoint documentation](crate::web::api::server::v1::contexts::user). use std::sync::Arc; use axum::routing::{delete, get, post}; @@ -11,7 +11,7 @@ use super::handlers::{ }; use crate::common::AppData; -/// Routes for the [`user`](crate::web::api::v1::contexts::user) API context. +/// Routes for the [`user`](crate::web::api::server::v1::contexts::user) API context. pub fn router(app_data: Arc) -> Router { Router::new() // Registration diff --git a/src/web/api/v1/extractors/bearer_token.rs b/src/web/api/server/v1/extractors/bearer_token.rs similarity index 93% rename from src/web/api/v1/extractors/bearer_token.rs rename to src/web/api/server/v1/extractors/bearer_token.rs index 1c9b5be9..7a166503 100644 --- a/src/web/api/v1/extractors/bearer_token.rs +++ b/src/web/api/server/v1/extractors/bearer_token.rs @@ -4,7 +4,7 @@ use axum::http::request::Parts; use axum::response::Response; use serde::Deserialize; -use crate::web::api::v1::auth::parse_token; +use crate::web::api::server::v1::auth::parse_token; pub struct Extract(pub Option); diff --git a/src/web/api/v1/extractors/mod.rs b/src/web/api/server/v1/extractors/mod.rs similarity index 100% rename from src/web/api/v1/extractors/mod.rs rename to src/web/api/server/v1/extractors/mod.rs diff --git a/src/web/api/v1/mod.rs b/src/web/api/server/v1/mod.rs similarity index 100% rename from src/web/api/v1/mod.rs rename to src/web/api/server/v1/mod.rs diff --git a/src/web/api/v1/responses.rs b/src/web/api/server/v1/responses.rs similarity index 100% rename from src/web/api/v1/responses.rs rename to src/web/api/server/v1/responses.rs diff --git a/src/web/api/v1/routes.rs b/src/web/api/server/v1/routes.rs similarity index 100% rename from src/web/api/v1/routes.rs rename to src/web/api/server/v1/routes.rs diff --git a/src/web/api/v1/contexts/mod.rs b/src/web/api/v1/contexts/mod.rs deleted file mode 100644 index f6ef4069..00000000 --- a/src/web/api/v1/contexts/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! The API is organized in the following contexts: -//! -//! Context | Description | Version -//! ---|---|--- -//! `About` | Metadata about the API | [`v1`](crate::web::api::v1::contexts::about) -//! `Category` | Torrent categories | [`v1`](crate::web::api::v1::contexts::category) -//! `Proxy` | Image proxy cache | [`v1`](crate::web::api::v1::contexts::proxy) -//! `Settings` | Index settings | [`v1`](crate::web::api::v1::contexts::settings) -//! `Tag` | Torrent tags | [`v1`](crate::web::api::v1::contexts::tag) -//! `Torrent` | Indexed torrents | [`v1`](crate::web::api::v1::contexts::torrent) -//! `User` | Users | [`v1`](crate::web::api::v1::contexts::user) -//! -pub mod about; -pub mod category; -pub mod proxy; -pub mod settings; -pub mod tag; -pub mod torrent; -pub mod user; diff --git a/src/web/mod.rs b/src/web/mod.rs index 9007e88f..d51f4b78 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -2,5 +2,5 @@ //! //! Currently, the API has only one version: `v1`. //! -//! Refer to the [`v1`](crate::web::api::v1) module for more information. +//! Refer to the [`v1`](crate::web::api::server::v1) module for more information. pub mod api; diff --git a/tests/common/asserts.rs b/tests/common/asserts.rs index 50a60e26..0760d1cd 100644 --- a/tests/common/asserts.rs +++ b/tests/common/asserts.rs @@ -1,6 +1,6 @@ // Text responses -use torrust_index::web::api::v1::responses::ErrorResponseData; +use torrust_index::web::api::server::v1::responses::ErrorResponseData; use super::responses::TextResponse; diff --git a/tests/e2e/web/api/v1/contexts/torrent/steps.rs b/tests/e2e/web/api/v1/contexts/torrent/steps.rs index 56c7a648..3782f307 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/steps.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/steps.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use torrust_index::models::info_hash::InfoHash; -use torrust_index::web::api::v1::responses::ErrorResponseData; +use torrust_index::web::api::server::v1::responses::ErrorResponseData; use crate::common::client::Client; use crate::common::contexts::torrent::fixtures::{random_torrent, TestTorrent, TorrentIndexInfo, TorrentListedInIndex}; From ac21c4972fa92b9a1c7fac0a47cd1d1e13527955 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 13:40:28 +0000 Subject: [PATCH 051/309] feat: [#452] move API client to production code It will be use in a new console command (seeder). --- Cargo.toml | 2 +- src/web/api/client/mod.rs | 1 + src/web/api/client/v1/client.rs | 333 ++++++++++++++++++ src/web/api/client/v1/connection_info.rs | 26 ++ .../api/client/v1/contexts/category/forms.rs | 9 + .../api/client/v1/contexts/category/mod.rs | 2 + .../client/v1/contexts/category/responses.rs | 23 ++ src/web/api/client/v1/contexts/mod.rs | 5 + .../api/client/v1/contexts/settings/mod.rs | 192 ++++++++++ .../client/v1/contexts/settings/responses.rs | 26 ++ src/web/api/client/v1/contexts/tag/forms.rs | 11 + src/web/api/client/v1/contexts/tag/mod.rs | 2 + .../api/client/v1/contexts/tag/responses.rs | 46 +++ .../api/client/v1/contexts/torrent/forms.rs | 60 ++++ src/web/api/client/v1/contexts/torrent/mod.rs | 3 + .../client/v1/contexts/torrent/requests.rs | 1 + .../client/v1/contexts/torrent/responses.rs | 126 +++++++ src/web/api/client/v1/contexts/user/forms.rs | 38 ++ src/web/api/client/v1/contexts/user/mod.rs | 2 + .../api/client/v1/contexts/user/responses.rs | 45 +++ src/web/api/client/v1/http.rs | 57 +++ src/web/api/client/v1/mod.rs | 6 + src/web/api/client/v1/random.rs | 10 + src/web/api/client/v1/responses.rs | 89 +++++ src/web/api/mod.rs | 1 + 25 files changed, 1115 insertions(+), 1 deletion(-) create mode 100644 src/web/api/client/mod.rs create mode 100644 src/web/api/client/v1/client.rs create mode 100644 src/web/api/client/v1/connection_info.rs create mode 100644 src/web/api/client/v1/contexts/category/forms.rs create mode 100644 src/web/api/client/v1/contexts/category/mod.rs create mode 100644 src/web/api/client/v1/contexts/category/responses.rs create mode 100644 src/web/api/client/v1/contexts/mod.rs create mode 100644 src/web/api/client/v1/contexts/settings/mod.rs create mode 100644 src/web/api/client/v1/contexts/settings/responses.rs create mode 100644 src/web/api/client/v1/contexts/tag/forms.rs create mode 100644 src/web/api/client/v1/contexts/tag/mod.rs create mode 100644 src/web/api/client/v1/contexts/tag/responses.rs create mode 100644 src/web/api/client/v1/contexts/torrent/forms.rs create mode 100644 src/web/api/client/v1/contexts/torrent/mod.rs create mode 100644 src/web/api/client/v1/contexts/torrent/requests.rs create mode 100644 src/web/api/client/v1/contexts/torrent/responses.rs create mode 100644 src/web/api/client/v1/contexts/user/forms.rs create mode 100644 src/web/api/client/v1/contexts/user/mod.rs create mode 100644 src/web/api/client/v1/contexts/user/responses.rs create mode 100644 src/web/api/client/v1/http.rs create mode 100644 src/web/api/client/v1/mod.rs create mode 100644 src/web/api/client/v1/random.rs create mode 100644 src/web/api/client/v1/responses.rs diff --git a/Cargo.toml b/Cargo.toml index cc884b39..24f7fc8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ lazy_static = "1.4.0" lettre = { version = "0", features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls", "tokio1-rustls-tls"] } log = "0" pbkdf2 = { version = "0", features = ["simple"] } +rand = "0" rand_core = { version = "0", features = ["std"] } regex = "1" reqwest = { version = "0", features = ["json", "multipart"] } @@ -76,7 +77,6 @@ urlencoding = "2" uuid = { version = "1", features = ["v4"] } [dev-dependencies] -rand = "0" tempfile = "3" uuid = { version = "1", features = ["v4"] } which = "5" diff --git a/src/web/api/client/mod.rs b/src/web/api/client/mod.rs new file mode 100644 index 00000000..a3a6d96c --- /dev/null +++ b/src/web/api/client/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs new file mode 100644 index 00000000..15b73516 --- /dev/null +++ b/src/web/api/client/v1/client.rs @@ -0,0 +1,333 @@ +use reqwest::multipart; +use serde::Serialize; + +use super::connection_info::ConnectionInfo; +use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; +use super::contexts::tag::forms::{AddTagForm, DeleteTagForm}; +use super::contexts::torrent::forms::UpdateTorrentFrom; +use super::contexts::torrent::requests::InfoHash; +use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username}; +use super::http::{Query, ReqwestQuery}; +use super::responses::{self, BinaryResponse, TextResponse}; + +/// API Client +pub struct Client { + http_client: Http, +} + +impl Client { + // todo: forms in POST requests can be passed by reference. + + fn base_path() -> String { + "/v1".to_string() + } + + #[must_use] + pub fn unauthenticated(bind_address: &str) -> Self { + Self::new(ConnectionInfo::anonymous(bind_address, &Self::base_path())) + } + + #[must_use] + pub fn authenticated(bind_address: &str, token: &str) -> Self { + Self::new(ConnectionInfo::new(bind_address, &Self::base_path(), token)) + } + + #[must_use] + pub fn new(connection_info: ConnectionInfo) -> Self { + Self { + http_client: Http::new(connection_info), + } + } + + /// It checks if the server is running. + pub async fn server_is_running(&self) -> bool { + let response = self.http_client.inner_get("").await; + response.is_ok() + } + + // Context: about + + pub async fn about(&self) -> TextResponse { + self.http_client.get("/about", Query::empty()).await + } + + pub async fn license(&self) -> TextResponse { + self.http_client.get("/about/license", Query::empty()).await + } + + // Context: category + + pub async fn get_categories(&self) -> TextResponse { + self.http_client.get("/category", Query::empty()).await + } + + pub async fn add_category(&self, add_category_form: AddCategoryForm) -> TextResponse { + self.http_client.post("/category", &add_category_form).await + } + + pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> TextResponse { + self.http_client.delete_with_body("/category", &delete_category_form).await + } + + // Context: tag + + pub async fn get_tags(&self) -> TextResponse { + // code-review: some endpoint are using plural + // (for instance, `get_categories`) and some singular. + self.http_client.get("/tags", Query::empty()).await + } + + pub async fn add_tag(&self, add_tag_form: AddTagForm) -> TextResponse { + self.http_client.post("/tag", &add_tag_form).await + } + + pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> TextResponse { + self.http_client.delete_with_body("/tag", &delete_tag_form).await + } + + // Context: root + + pub async fn root(&self) -> TextResponse { + self.http_client.get("", Query::empty()).await + } + + // Context: settings + + pub async fn get_public_settings(&self) -> TextResponse { + self.http_client.get("/settings/public", Query::empty()).await + } + + pub async fn get_site_name(&self) -> TextResponse { + self.http_client.get("/settings/name", Query::empty()).await + } + + pub async fn get_settings(&self) -> TextResponse { + self.http_client.get("/settings", Query::empty()).await + } + + // Context: torrent + + pub async fn get_torrents(&self, params: Query) -> TextResponse { + self.http_client.get("/torrents", params).await + } + + pub async fn get_torrent(&self, info_hash: &InfoHash) -> TextResponse { + self.http_client.get(&format!("/torrent/{info_hash}"), Query::empty()).await + } + + pub async fn delete_torrent(&self, info_hash: &InfoHash) -> TextResponse { + self.http_client.delete(&format!("/torrent/{info_hash}")).await + } + + pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentFrom) -> TextResponse { + self.http_client + .put(&format!("/torrent/{info_hash}"), &update_torrent_form) + .await + } + + pub async fn upload_torrent(&self, form: multipart::Form) -> TextResponse { + self.http_client.post_multipart("/torrent/upload", form).await + } + + pub async fn download_torrent(&self, info_hash: &InfoHash) -> responses::BinaryResponse { + self.http_client + .get_binary(&format!("/torrent/download/{info_hash}"), Query::empty()) + .await + } + + // Context: user + + pub async fn register_user(&self, registration_form: RegistrationForm) -> TextResponse { + self.http_client.post("/user/register", ®istration_form).await + } + + pub async fn login_user(&self, registration_form: LoginForm) -> TextResponse { + self.http_client.post("/user/login", ®istration_form).await + } + + pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> TextResponse { + self.http_client.post("/user/token/verify", &token_verification_form).await + } + + pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> TextResponse { + self.http_client.post("/user/token/renew", &token_verification_form).await + } + + pub async fn ban_user(&self, username: Username) -> TextResponse { + self.http_client.delete(&format!("/user/ban/{}", &username.value)).await + } +} + +/// Generic HTTP Client +struct Http { + connection_info: ConnectionInfo, +} + +impl Http { + pub fn new(connection_info: ConnectionInfo) -> Self { + Self { connection_info } + } + + pub async fn get(&self, path: &str, params: Query) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await + .unwrap(), + }; + // todo: If the response is a JSON, it returns the JSON body in a byte + // array. This is not the expected behavior. + // - Rename BinaryResponse to BinaryTorrentResponse + // - Return an error if the response is not a bittorrent file + BinaryResponse::from(response).await + } + + pub async fn inner_get(&self, path: &str) -> Result { + reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .send() + .await + } + + pub async fn post(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .post(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .post(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .post(self.base_url(path).clone()) + .multipart(form) + .bearer_auth(token) + .send() + .await + .expect("failed to send multipart request with token"), + None => reqwest::Client::builder() + .build() + .unwrap() + .post(self.base_url(path).clone()) + .multipart(form) + .send() + .await + .expect("failed to send multipart request without token"), + }; + TextResponse::from(response).await + } + + pub async fn put(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .put(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .put(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + async fn delete(&self, path: &str) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + async fn delete_with_body(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + fn base_url(&self, path: &str) -> String { + format!( + "http://{}{}{path}", + &self.connection_info.bind_address, &self.connection_info.base_path + ) + } +} diff --git a/src/web/api/client/v1/connection_info.rs b/src/web/api/client/v1/connection_info.rs new file mode 100644 index 00000000..2183f4b9 --- /dev/null +++ b/src/web/api/client/v1/connection_info.rs @@ -0,0 +1,26 @@ +#[derive(Clone)] +pub struct ConnectionInfo { + pub bind_address: String, + pub base_path: String, + pub token: Option, +} + +impl ConnectionInfo { + #[must_use] + pub fn new(bind_address: &str, base_path: &str, token: &str) -> Self { + Self { + bind_address: bind_address.to_string(), + base_path: base_path.to_string(), + token: Some(token.to_string()), + } + } + + #[must_use] + pub fn anonymous(bind_address: &str, base_path: &str) -> Self { + Self { + bind_address: bind_address.to_string(), + base_path: base_path.to_string(), + token: None, + } + } +} diff --git a/src/web/api/client/v1/contexts/category/forms.rs b/src/web/api/client/v1/contexts/category/forms.rs new file mode 100644 index 00000000..ea9cf429 --- /dev/null +++ b/src/web/api/client/v1/contexts/category/forms.rs @@ -0,0 +1,9 @@ +use serde::Serialize; + +#[derive(Serialize)] +pub struct AddCategoryForm { + pub name: String, + pub icon: Option, +} + +pub type DeleteCategoryForm = AddCategoryForm; diff --git a/src/web/api/client/v1/contexts/category/mod.rs b/src/web/api/client/v1/contexts/category/mod.rs new file mode 100644 index 00000000..ea737db8 --- /dev/null +++ b/src/web/api/client/v1/contexts/category/mod.rs @@ -0,0 +1,2 @@ +pub mod forms; +pub mod responses; diff --git a/src/web/api/client/v1/contexts/category/responses.rs b/src/web/api/client/v1/contexts/category/responses.rs new file mode 100644 index 00000000..cbadb631 --- /dev/null +++ b/src/web/api/client/v1/contexts/category/responses.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct AddedCategoryResponse { + pub data: String, +} + +#[derive(Deserialize)] +pub struct DeletedCategoryResponse { + pub data: String, +} + +#[derive(Deserialize, Debug)] +pub struct ListResponse { + pub data: Vec, +} + +#[derive(Deserialize, Debug, PartialEq)] +pub struct ListItem { + pub category_id: i64, + pub name: String, + pub num_torrents: i64, +} diff --git a/src/web/api/client/v1/contexts/mod.rs b/src/web/api/client/v1/contexts/mod.rs new file mode 100644 index 00000000..44f74414 --- /dev/null +++ b/src/web/api/client/v1/contexts/mod.rs @@ -0,0 +1,5 @@ +pub mod category; +pub mod settings; +pub mod tag; +pub mod torrent; +pub mod user; diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs new file mode 100644 index 00000000..356e602a --- /dev/null +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -0,0 +1,192 @@ +pub mod responses; + +use crate::config::{ + Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, + Network as DomainNetwork, TorrustIndex as DomainSettings, Tracker as DomainTracker, + TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Settings { + pub website: Website, + pub tracker: Tracker, + pub net: Network, + pub auth: Auth, + pub database: Database, + pub mail: Mail, + pub image_cache: ImageCache, + pub api: Api, + pub tracker_statistics_importer: TrackerStatisticsImporter, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Website { + pub name: String, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Tracker { + pub url: String, + pub mode: String, + pub api_url: String, + pub token: String, + pub token_valid_seconds: u64, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Network { + pub port: u16, + pub base_url: Option, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Auth { + pub email_on_signup: String, + pub min_password_length: usize, + pub max_password_length: usize, + pub secret_key: String, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Database { + pub connect_url: String, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Mail { + pub email_verification_enabled: bool, + pub from: String, + pub reply_to: String, + pub username: String, + pub password: String, + pub server: String, + pub port: u16, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct ImageCache { + pub max_request_timeout_ms: u64, + pub capacity: usize, + pub entry_size_limit: usize, + pub user_quota_period_seconds: u64, + pub user_quota_bytes: usize, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Api { + pub default_torrent_page_size: u8, + pub max_torrent_page_size: u8, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct TrackerStatisticsImporter { + pub torrent_info_update_interval: u64, + port: u16, +} + +impl From for Settings { + fn from(settings: DomainSettings) -> Self { + Settings { + website: Website::from(settings.website), + tracker: Tracker::from(settings.tracker), + net: Network::from(settings.net), + auth: Auth::from(settings.auth), + database: Database::from(settings.database), + mail: Mail::from(settings.mail), + image_cache: ImageCache::from(settings.image_cache), + api: Api::from(settings.api), + tracker_statistics_importer: TrackerStatisticsImporter::from(settings.tracker_statistics_importer), + } + } +} + +impl From for Website { + fn from(website: DomainWebsite) -> Self { + Self { name: website.name } + } +} + +impl From for Tracker { + fn from(tracker: DomainTracker) -> Self { + Self { + url: tracker.url, + mode: format!("{:?}", tracker.mode), + api_url: tracker.api_url, + token: tracker.token, + token_valid_seconds: tracker.token_valid_seconds, + } + } +} + +impl From for Network { + fn from(net: DomainNetwork) -> Self { + Self { + port: net.port, + base_url: net.base_url, + } + } +} + +impl From for Auth { + fn from(auth: DomainAuth) -> Self { + Self { + email_on_signup: format!("{:?}", auth.email_on_signup), + min_password_length: auth.min_password_length, + max_password_length: auth.max_password_length, + secret_key: auth.secret_key, + } + } +} + +impl From for Database { + fn from(database: DomainDatabase) -> Self { + Self { + connect_url: database.connect_url, + } + } +} + +impl From for Mail { + fn from(mail: DomainMail) -> Self { + Self { + email_verification_enabled: mail.email_verification_enabled, + from: mail.from, + reply_to: mail.reply_to, + username: mail.username, + password: mail.password, + server: mail.server, + port: mail.port, + } + } +} + +impl From for ImageCache { + fn from(image_cache: DomainImageCache) -> Self { + Self { + max_request_timeout_ms: image_cache.max_request_timeout_ms, + capacity: image_cache.capacity, + entry_size_limit: image_cache.entry_size_limit, + user_quota_period_seconds: image_cache.user_quota_period_seconds, + user_quota_bytes: image_cache.user_quota_bytes, + } + } +} + +impl From for Api { + fn from(api: DomainApi) -> Self { + Self { + default_torrent_page_size: api.default_torrent_page_size, + max_torrent_page_size: api.max_torrent_page_size, + } + } +} + +impl From for TrackerStatisticsImporter { + fn from(tracker_statistics_importer: DomainTrackerStatisticsImporter) -> Self { + Self { + torrent_info_update_interval: tracker_statistics_importer.torrent_info_update_interval, + port: tracker_statistics_importer.port, + } + } +} diff --git a/src/web/api/client/v1/contexts/settings/responses.rs b/src/web/api/client/v1/contexts/settings/responses.rs new file mode 100644 index 00000000..096ef1f4 --- /dev/null +++ b/src/web/api/client/v1/contexts/settings/responses.rs @@ -0,0 +1,26 @@ +use serde::Deserialize; + +use super::Settings; + +#[derive(Deserialize)] +pub struct AllSettingsResponse { + pub data: Settings, +} + +#[derive(Deserialize)] +pub struct PublicSettingsResponse { + pub data: Public, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct Public { + pub website_name: String, + pub tracker_url: String, + pub tracker_mode: String, + pub email_on_signup: String, +} + +#[derive(Deserialize)] +pub struct SiteNameResponse { + pub data: String, +} diff --git a/src/web/api/client/v1/contexts/tag/forms.rs b/src/web/api/client/v1/contexts/tag/forms.rs new file mode 100644 index 00000000..26d1395d --- /dev/null +++ b/src/web/api/client/v1/contexts/tag/forms.rs @@ -0,0 +1,11 @@ +use serde::Serialize; + +#[derive(Serialize)] +pub struct AddTagForm { + pub name: String, +} + +#[derive(Serialize)] +pub struct DeleteTagForm { + pub tag_id: i64, +} diff --git a/src/web/api/client/v1/contexts/tag/mod.rs b/src/web/api/client/v1/contexts/tag/mod.rs new file mode 100644 index 00000000..ea737db8 --- /dev/null +++ b/src/web/api/client/v1/contexts/tag/mod.rs @@ -0,0 +1,2 @@ +pub mod forms; +pub mod responses; diff --git a/src/web/api/client/v1/contexts/tag/responses.rs b/src/web/api/client/v1/contexts/tag/responses.rs new file mode 100644 index 00000000..a08cdf55 --- /dev/null +++ b/src/web/api/client/v1/contexts/tag/responses.rs @@ -0,0 +1,46 @@ +use serde::Deserialize; + +// code-review: we should always include a API resource in the `data`attribute. +// +// ``` +// pub struct DeletedTagResponse { +// pub data: DeletedTag, +// } +// +// pub struct DeletedTag { +// pub tag_id: i64, +// } +// ``` +// +// This way the API client knows what's the meaning of the `data` attribute. + +#[derive(Deserialize)] +pub struct AddedTagResponse { + pub data: String, +} + +#[derive(Deserialize)] +pub struct DeletedTagResponse { + pub data: i64, +} + +#[derive(Deserialize, Debug)] +pub struct ListResponse { + pub data: Vec, +} + +impl ListResponse { + /// # Panics + /// + /// Will panic if it can't fin the tag in the response. + #[must_use] + pub fn find_tag_id(&self, tag_name: &str) -> i64 { + self.data.iter().find(|tag| tag.name == tag_name).unwrap().tag_id + } +} + +#[derive(Deserialize, Debug, PartialEq)] +pub struct ListItem { + pub tag_id: i64, + pub name: String, +} diff --git a/src/web/api/client/v1/contexts/torrent/forms.rs b/src/web/api/client/v1/contexts/torrent/forms.rs new file mode 100644 index 00000000..7f948b99 --- /dev/null +++ b/src/web/api/client/v1/contexts/torrent/forms.rs @@ -0,0 +1,60 @@ +use std::fs; +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct UpdateTorrentFrom { + pub title: Option, + pub description: Option, + pub category: Option, + pub tags: Option>, +} + +use reqwest::multipart::Form; + +pub struct UploadTorrentMultipartForm { + pub title: String, + pub description: String, + pub category: String, + pub torrent_file: BinaryFile, +} + +#[derive(Clone)] +pub struct BinaryFile { + pub name: String, + pub contents: Vec, +} + +impl BinaryFile { + /// # Panics + /// + /// Will panic if: + /// + /// - The path is not a file. + /// - The path can't be converted into string. + /// - The file can't be read. + #[must_use] + pub fn from_file_at_path(path: &Path) -> Self { + BinaryFile { + name: path.file_name().unwrap().to_owned().into_string().unwrap(), + contents: fs::read(path).unwrap(), + } + } +} + +impl From for Form { + fn from(form: UploadTorrentMultipartForm) -> Self { + Form::new() + .text("title", form.title) + .text("description", form.description) + .text("category", form.category) + .part( + "torrent", + reqwest::multipart::Part::bytes(form.torrent_file.contents) + .file_name(form.torrent_file.name) + .mime_str("application/x-bittorrent") + .unwrap(), + ) + } +} diff --git a/src/web/api/client/v1/contexts/torrent/mod.rs b/src/web/api/client/v1/contexts/torrent/mod.rs new file mode 100644 index 00000000..a3bb0936 --- /dev/null +++ b/src/web/api/client/v1/contexts/torrent/mod.rs @@ -0,0 +1,3 @@ +pub mod forms; +pub mod requests; +pub mod responses; diff --git a/src/web/api/client/v1/contexts/torrent/requests.rs b/src/web/api/client/v1/contexts/torrent/requests.rs new file mode 100644 index 00000000..1d4ac583 --- /dev/null +++ b/src/web/api/client/v1/contexts/torrent/requests.rs @@ -0,0 +1 @@ +pub type InfoHash = String; diff --git a/src/web/api/client/v1/contexts/torrent/responses.rs b/src/web/api/client/v1/contexts/torrent/responses.rs new file mode 100644 index 00000000..2a9598df --- /dev/null +++ b/src/web/api/client/v1/contexts/torrent/responses.rs @@ -0,0 +1,126 @@ +use serde::Deserialize; + +pub type Id = i64; +pub type CategoryId = i64; +pub type TagId = i64; +pub type UtcDateTime = String; // %Y-%m-%d %H:%M:%S + +#[derive(Deserialize, PartialEq, Debug)] +pub struct ErrorResponse { + pub error: String, +} + +#[derive(Deserialize)] +pub struct TorrentListResponse { + pub data: TorrentList, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct TorrentList { + pub total: u32, + pub results: Vec, +} + +impl TorrentList { + #[must_use] + pub fn _contains(&self, torrent_id: Id) -> bool { + self.results.iter().any(|item| item.torrent_id == torrent_id) + } +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct ListItem { + pub torrent_id: i64, + pub uploader: String, + pub info_hash: String, + pub title: String, + pub description: Option, + pub category_id: i64, + pub date_uploaded: String, + pub file_size: i64, + pub seeders: i64, + pub leechers: i64, + pub name: String, + pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct TorrentDetailsResponse { + pub data: TorrentDetails, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct TorrentDetails { + pub torrent_id: Id, + pub uploader: String, + pub info_hash: String, + pub title: String, + pub description: String, + pub category: Category, + pub upload_date: UtcDateTime, + pub file_size: u64, + pub seeders: u64, + pub leechers: u64, + pub files: Vec, + pub trackers: Vec, + pub magnet_link: String, + pub tags: Vec, + pub name: String, + pub comment: Option, + pub creation_date: Option, + pub created_by: Option, + pub encoding: Option, +} + +#[allow(unknown_lints)] +#[allow(clippy::struct_field_names)] +#[derive(Deserialize, PartialEq, Debug)] +pub struct Category { + pub category_id: CategoryId, // todo: rename to `id` + pub name: String, + pub num_torrents: u64, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct Tag { + pub tag_id: TagId, + pub name: String, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct File { + pub path: Vec, + pub length: u64, + pub md5sum: Option, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct UploadedTorrentResponse { + pub data: UploadedTorrent, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct UploadedTorrent { + pub torrent_id: Id, + pub info_hash: String, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct DeletedTorrentResponse { + pub data: DeletedTorrent, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct DeletedTorrent { + pub torrent_id: Id, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct UpdatedTorrentResponse { + pub data: UpdatedTorrent, +} + +pub type UpdatedTorrent = TorrentDetails; diff --git a/src/web/api/client/v1/contexts/user/forms.rs b/src/web/api/client/v1/contexts/user/forms.rs new file mode 100644 index 00000000..18d08f9e --- /dev/null +++ b/src/web/api/client/v1/contexts/user/forms.rs @@ -0,0 +1,38 @@ +use serde::Serialize; + +#[derive(Clone, Serialize)] +pub struct RegistrationForm { + pub username: String, + pub email: Option, + pub password: String, + pub confirm_password: String, +} + +pub type RegisteredUser = RegistrationForm; + +#[derive(Serialize)] +pub struct LoginForm { + pub login: String, + pub password: String, +} + +#[derive(Serialize)] +pub struct TokenVerificationForm { + pub token: String, +} + +#[derive(Serialize)] +pub struct TokenRenewalForm { + pub token: String, +} + +pub struct Username { + pub value: String, +} + +impl Username { + #[must_use] + pub fn new(value: String) -> Self { + Self { value } + } +} diff --git a/src/web/api/client/v1/contexts/user/mod.rs b/src/web/api/client/v1/contexts/user/mod.rs new file mode 100644 index 00000000..ea737db8 --- /dev/null +++ b/src/web/api/client/v1/contexts/user/mod.rs @@ -0,0 +1,2 @@ +pub mod forms; +pub mod responses; diff --git a/src/web/api/client/v1/contexts/user/responses.rs b/src/web/api/client/v1/contexts/user/responses.rs new file mode 100644 index 00000000..1a9a3837 --- /dev/null +++ b/src/web/api/client/v1/contexts/user/responses.rs @@ -0,0 +1,45 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct AddedUserResponse { + pub data: NewUserData, +} + +#[derive(Deserialize, Debug)] +pub struct NewUserData { + pub user_id: i64, +} + +#[derive(Deserialize, Debug)] +pub struct SuccessfulLoginResponse { + pub data: LoggedInUserData, +} + +#[derive(Deserialize, Debug)] +pub struct LoggedInUserData { + pub token: String, + pub username: String, + pub admin: bool, +} + +#[derive(Deserialize)] +pub struct TokenVerifiedResponse { + pub data: String, +} + +#[derive(Deserialize)] +pub struct BannedUserResponse { + pub data: String, +} + +#[derive(Deserialize)] +pub struct TokenRenewalResponse { + pub data: TokenRenewalData, +} + +#[derive(Deserialize, PartialEq, Debug)] +pub struct TokenRenewalData { + pub token: String, + pub username: String, + pub admin: bool, +} diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs new file mode 100644 index 00000000..a3b172dc --- /dev/null +++ b/src/web/api/client/v1/http.rs @@ -0,0 +1,57 @@ +pub type ReqwestQuery = Vec; +pub type ReqwestQueryParam = (String, String); + +/// URL Query component +#[derive(Default, Debug)] +pub struct Query { + params: Vec, +} + +impl Query { + #[must_use] + pub fn empty() -> Self { + Self { params: vec![] } + } + + #[must_use] + pub fn with_params(params: Vec) -> Self { + Self { params } + } + + pub fn add_param(&mut self, param: QueryParam) { + self.params.push(param); + } +} + +impl From for ReqwestQuery { + fn from(url_search_params: Query) -> Self { + url_search_params + .params + .iter() + .map(|param| ReqwestQueryParam::from((*param).clone())) + .collect() + } +} + +/// URL query param +#[derive(Clone, Debug)] +pub struct QueryParam { + name: String, + value: String, +} + +impl QueryParam { + #[must_use] + pub fn new(name: &str, value: &str) -> Self { + Self { + name: name.to_string(), + value: value.to_string(), + } + } +} + +impl From for ReqwestQueryParam { + fn from(param: QueryParam) -> Self { + (param.name, param.value) + } +} diff --git a/src/web/api/client/v1/mod.rs b/src/web/api/client/v1/mod.rs new file mode 100644 index 00000000..5d0fbf2f --- /dev/null +++ b/src/web/api/client/v1/mod.rs @@ -0,0 +1,6 @@ +pub mod client; +pub mod connection_info; +pub mod contexts; +pub mod http; +pub mod random; +pub mod responses; diff --git a/src/web/api/client/v1/random.rs b/src/web/api/client/v1/random.rs new file mode 100644 index 00000000..2133dcd2 --- /dev/null +++ b/src/web/api/client/v1/random.rs @@ -0,0 +1,10 @@ +//! Random data generators for testing. +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; + +/// Returns a random alphanumeric string of a certain size. +/// +/// It is useful for generating random names, IDs, etc for testing. +pub fn string(size: usize) -> String { + thread_rng().sample_iter(&Alphanumeric).take(size).map(char::from).collect() +} diff --git a/src/web/api/client/v1/responses.rs b/src/web/api/client/v1/responses.rs new file mode 100644 index 00000000..aae5a338 --- /dev/null +++ b/src/web/api/client/v1/responses.rs @@ -0,0 +1,89 @@ +use reqwest::Response as ReqwestResponse; + +#[derive(Debug)] +pub struct TextResponse { + pub status: u16, + pub content_type: Option, + pub body: String, +} + +impl TextResponse { + /// # Panics + /// + /// Will panic if: + /// + /// - It can't map the content type in the response header to string. + /// - It can't get the response bytes. + pub async fn from(response: ReqwestResponse) -> Self { + Self { + status: response.status().as_u16(), + content_type: response + .headers() + .get("content-type") + .map(|content_type| content_type.to_str().unwrap().to_owned()), + body: response.text().await.unwrap(), + } + } + + #[must_use] + pub fn is_json_and_ok(&self) -> bool { + self.is_ok() && self.is_json() + } + + #[must_use] + pub fn is_json(&self) -> bool { + if let Some(content_type) = &self.content_type { + return content_type == "application/json"; + } + false + } + + #[must_use] + pub fn is_ok(&self) -> bool { + self.status == 200 + } +} + +#[derive(Debug)] +pub struct BinaryResponse { + pub status: u16, + pub content_type: Option, + pub bytes: Vec, +} + +impl BinaryResponse { + /// # Panics + /// + /// Will panic if: + /// + /// - It can't map the content type in the response header to string. + /// - It can't get the response bytes. + pub async fn from(response: ReqwestResponse) -> Self { + Self { + status: response.status().as_u16(), + content_type: response + .headers() + .get("content-type") + .map(|content_type| content_type.to_str().unwrap().to_owned()), + bytes: response.bytes().await.unwrap().to_vec(), + } + } + + #[must_use] + pub fn is_a_bit_torrent_file(&self) -> bool { + self.is_ok() && self.is_bittorrent_content_type() + } + + #[must_use] + pub fn is_bittorrent_content_type(&self) -> bool { + if let Some(content_type) = &self.content_type { + return content_type == "application/x-bittorrent"; + } + false + } + + #[must_use] + pub fn is_ok(&self) -> bool { + self.status == 200 + } +} diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index a9789a7f..8bfdacd9 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -3,6 +3,7 @@ //! Currently, the API has only one version: `v1`. //! //! Refer to the [`v1`]) module for more information. +pub mod client; pub mod server; use std::net::SocketAddr; From 98fa40f79d7f2736cfaed2b5f6db87dd39f7034f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 14:14:18 +0000 Subject: [PATCH 052/309] feat: [#453] add cargo dependencies: clap, anyhow They will be used in console commands. --- Cargo.lock | 230 +++++++++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 2 + 2 files changed, 207 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90e50a75..a1e872cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,60 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + [[package]] name = "argon2" version = "0.5.2" @@ -359,7 +413,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -372,6 +426,52 @@ dependencies = [ "stacker", ] +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" version = "2.0.4" @@ -380,7 +480,7 @@ checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ "is-terminal", "lazy_static", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -619,7 +719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -630,7 +730,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -980,7 +1080,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1155,7 +1255,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1397,7 +1497,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1604,7 +1704,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1983,7 +2083,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2061,7 +2161,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2147,7 +2247,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2393,7 +2493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2656,6 +2756,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.5.0" @@ -2730,7 +2836,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2879,7 +2985,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2985,12 +3091,14 @@ dependencies = [ name = "torrust-index" version = "3.0.0-alpha.3-develop" dependencies = [ + "anyhow", "argon2", "async-trait", "axum", "binascii", "bytes", "chrono", + "clap", "config", "derive_more", "email_address", @@ -3315,6 +3423,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.5.0" @@ -3453,7 +3567,7 @@ dependencies = [ "home", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3499,7 +3613,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3508,7 +3622,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -3517,13 +3640,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3532,42 +3670,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.19" @@ -3584,7 +3764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 24f7fc8b..e2be04f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,12 +34,14 @@ version = "3.0.0-alpha.3-develop" opt-level = 3 [dependencies] +anyhow = "1.0.79" argon2 = "0" async-trait = "0" axum = { version = "0", features = ["multipart"] } binascii = "0" bytes = "1" chrono = { version = "0", default-features = false, features = ["clock"] } +clap = { version = "4.4.18", features = ["derive", "env"]} config = "0" derive_more = "0" email_address = "0" From df3a9be77d723713f04bc3df23f5fc02a84a29aa Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 14:15:37 +0000 Subject: [PATCH 053/309] feat: [#453] new console command seeder. Only scaffolding. WIP --- src/bin/seeder.rs | 6 +++ src/console/commands/mod.rs | 1 + src/console/commands/seeder/app.rs | 75 ++++++++++++++++++++++++++++++ src/console/commands/seeder/mod.rs | 1 + 4 files changed, 83 insertions(+) create mode 100644 src/bin/seeder.rs create mode 100644 src/console/commands/seeder/app.rs create mode 100644 src/console/commands/seeder/mod.rs diff --git a/src/bin/seeder.rs b/src/bin/seeder.rs new file mode 100644 index 00000000..ffc0f938 --- /dev/null +++ b/src/bin/seeder.rs @@ -0,0 +1,6 @@ +//! Program to upload random torrents to a live Index API. +use torrust_index::console::commands::seeder::app; + +fn main() -> anyhow::Result<()> { + app::run() +} diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs index 43be8073..38506b77 100644 --- a/src/console/commands/mod.rs +++ b/src/console/commands/mod.rs @@ -1 +1,2 @@ +pub mod seeder; pub mod tracker_statistics_importer; diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs new file mode 100644 index 00000000..228c9947 --- /dev/null +++ b/src/console/commands/seeder/app.rs @@ -0,0 +1,75 @@ +//! Program to upload random torrent to a live Index API. +//! +//! Run with: +//! +//! ```text +//! cargo run --bin seeder -- --number-of-torrents --user --password --interval +//! ``` +//! +//! For example: +//! +//! ```text +//! cargo run --bin seeder -- --number-of-torrents 1000 --user admin --password 12345678 --interval 0 +//! ``` +//! +//! That command would upload 100o random torrents to the Index using the user +//! account admin with password 123456 and waiting 1 second between uploads. +use clap::Parser; +use log::{debug, LevelFilter}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[arg(short, long)] + number_of_torrents: i32, + + #[arg(short, long)] + user: String, + + #[arg(short, long)] + password: String, + + #[arg(short, long)] + interval: i32, +} + +/// # Errors +/// +/// Will not return any errors for the time being. +pub fn run() -> anyhow::Result<()> { + setup_logging(LevelFilter::Info); + + let args = Args::parse(); + + println!("Number of torrents: {}", args.number_of_torrents); + println!("User: {}", args.user); + println!("Password: {}", args.password); + println!("Interval: {:?}", args.interval); + + /* todo: + - Use a client to upload a random torrent every "interval" seconds. + */ + + Ok(()) +} + +fn setup_logging(level: LevelFilter) { + if let Err(_err) = fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{} [{}][{}] {}", + chrono::Local::now().format("%+"), + record.target(), + record.level(), + message + )); + }) + .level(level) + .chain(std::io::stdout()) + .apply() + { + panic!("Failed to initialize logging.") + } + + debug!("logging initialized."); +} diff --git a/src/console/commands/seeder/mod.rs b/src/console/commands/seeder/mod.rs new file mode 100644 index 00000000..309be628 --- /dev/null +++ b/src/console/commands/seeder/mod.rs @@ -0,0 +1 @@ +pub mod app; From 935facbea1b5d19f6ebe6bc056e26543285e5ea8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 16:13:17 +0000 Subject: [PATCH 054/309] feat: [#453] new console command New console command to upload torrent to the Index remotely by using the API client. ```console cargo run --bin seeder -- --api-base-url --number-of-torrents --user --password --interval ``` For example: ```console cargo run --bin seeder -- --api-base-url "localhost:3001" --number-of-torrents 1000 --user admin --password 12345678 --interval 0 ``` That command would upload 1000 random torrents to the Index using the user account `admin` with password `123456` and waiting `1` second between uploads. --- src/bin/seeder.rs | 5 +- src/console/commands/seeder/api.rs | 108 +++++++++++++++ src/console/commands/seeder/app.rs | 129 +++++++++++++----- src/console/commands/seeder/logging.rs | 25 ++++ src/console/commands/seeder/mod.rs | 2 + src/web/api/client/v1/client.rs | 4 +- .../api/client/v1/contexts/torrent/forms.rs | 12 +- .../client/v1/contexts/torrent/responses.rs | 4 +- 8 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 src/console/commands/seeder/api.rs create mode 100644 src/console/commands/seeder/logging.rs diff --git a/src/bin/seeder.rs b/src/bin/seeder.rs index ffc0f938..ae512041 100644 --- a/src/bin/seeder.rs +++ b/src/bin/seeder.rs @@ -1,6 +1,7 @@ //! Program to upload random torrents to a live Index API. use torrust_index::console::commands::seeder::app; -fn main() -> anyhow::Result<()> { - app::run() +#[tokio::main] +async fn main() -> anyhow::Result<()> { + app::run().await } diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs new file mode 100644 index 00000000..e9af0ccd --- /dev/null +++ b/src/console/commands/seeder/api.rs @@ -0,0 +1,108 @@ +use crate::web::api::client::v1::client::Client; +use crate::web::api::client::v1::contexts::category::forms::AddCategoryForm; +use crate::web::api::client::v1::contexts::category::responses::{ListItem, ListResponse}; +use crate::web::api::client::v1::contexts::torrent::forms::UploadTorrentMultipartForm; +use crate::web::api::client::v1::contexts::torrent::responses::{UploadedTorrent, UploadedTorrentResponse}; +use crate::web::api::client::v1::contexts::user::forms::LoginForm; +use crate::web::api::client::v1::contexts::user::responses::{LoggedInUserData, SuccessfulLoginResponse}; +use crate::web::api::client::v1::responses::TextResponse; + +use log::debug; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Torrent with the same info-hash already exist in the database")] + TorrentInfoHashAlreadyExists, + #[error("Torrent with the same title already exist in the database")] + TorrentTitleAlreadyExists, +} + +/// It uploads a torrent file to the Torrust Index. +/// +/// # Errors +/// +/// It returns an error if the torrent already exists in the database. +/// +/// # Panics +/// +/// Panics if the response body is not a valid JSON. +pub async fn upload_torrent(client: &Client, upload_torrent_form: UploadTorrentMultipartForm) -> Result { + let categories = get_categories(client).await; + + if !contains_category_with_name(&categories, &upload_torrent_form.category) { + add_category(client, &upload_torrent_form.category).await; + } + + let response = client.upload_torrent(upload_torrent_form.into()).await; + + debug!(target:"seeder", "response: {}", response.status); + + if response.status == 400 { + if response.body.contains("This torrent already exists in our database") { + return Err(Error::TorrentInfoHashAlreadyExists); + } + + if response.body.contains("This torrent title has already been used") { + return Err(Error::TorrentTitleAlreadyExists); + } + } + + assert!(response.is_json_and_ok(), "Error uploading torrent: {}", response.body); + + let uploaded_torrent_response: UploadedTorrentResponse = + serde_json::from_str(&response.body).expect("a valid JSON response should be returned from the Torrust Index API"); + + Ok(uploaded_torrent_response.data) +} + +/// It logs in the user and returns the user data. +/// +/// # Panics +/// +/// Panics if the response body is not a valid JSON. +pub async fn login(client: &Client, username: &str, password: &str) -> LoggedInUserData { + let response = client + .login_user(LoginForm { + login: username.to_owned(), + password: password.to_owned(), + }) + .await; + + let res: SuccessfulLoginResponse = serde_json::from_str(&response.body).unwrap_or_else(|_| { + panic!( + "a valid JSON response should be returned after login. Received: {}", + response.body + ) + }); + + res.data +} + +/// It returns all the index categories. +/// +/// # Panics +/// +/// Panics if the response body is not a valid JSON. +pub async fn get_categories(client: &Client) -> Vec { + let response = client.get_categories().await; + + let res: ListResponse = serde_json::from_str(&response.body).unwrap(); + + res.data +} + +/// It adds a new category. +pub async fn add_category(client: &Client, name: &str) -> TextResponse { + client + .add_category(AddCategoryForm { + name: name.to_owned(), + icon: None, + }) + .await +} + +/// It checks if the category list contains the given category. +fn contains_category_with_name(items: &[ListItem], category_name: &str) -> bool { + items.iter().any(|item| item.name == category_name) +} diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index 228c9947..badc4e21 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -3,23 +3,52 @@ //! Run with: //! //! ```text -//! cargo run --bin seeder -- --number-of-torrents --user --password --interval +//! cargo run --bin seeder -- --api-base-url --number-of-torrents --user --password --interval //! ``` //! //! For example: //! //! ```text -//! cargo run --bin seeder -- --number-of-torrents 1000 --user admin --password 12345678 --interval 0 +//! cargo run --bin seeder -- --api-base-url "localhost:3001" --number-of-torrents 1000 --user admin --password 12345678 --interval 0 //! ``` //! -//! That command would upload 100o random torrents to the Index using the user +//! That command would upload 1000 random torrents to the Index using the user //! account admin with password 123456 and waiting 1 second between uploads. +use std::{thread::sleep, time::Duration}; + +use anyhow::Context; use clap::Parser; -use log::{debug, LevelFilter}; +use log::{debug, info, LevelFilter}; +use text_colorizer::Colorize; +use uuid::Uuid; + +use crate::{ + console::commands::seeder::{ + api::{login, upload_torrent}, + logging, + }, + services::torrent_file::generate_random_torrent, + utils::parse_torrent, + web::api::client::v1::{ + client::Client, + contexts::{ + torrent::{ + forms::{BinaryFile, UploadTorrentMultipartForm}, + responses::UploadedTorrent, + }, + user::responses::LoggedInUserData, + }, + }, +}; + +use super::api::Error; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { + #[arg(short, long)] + api_base_url: String, + #[arg(short, long)] number_of_torrents: i32, @@ -30,46 +59,84 @@ struct Args { password: String, #[arg(short, long)] - interval: i32, + interval: u64, } /// # Errors /// /// Will not return any errors for the time being. -pub fn run() -> anyhow::Result<()> { - setup_logging(LevelFilter::Info); +pub async fn run() -> anyhow::Result<()> { + logging::setup(LevelFilter::Info); let args = Args::parse(); - println!("Number of torrents: {}", args.number_of_torrents); - println!("User: {}", args.user); - println!("Password: {}", args.password); - println!("Interval: {:?}", args.interval); + let api_user = login_index_api(&args.api_base_url, &args.user, &args.password).await; + + let api_client = Client::authenticated(&args.api_base_url, &api_user.token); + + info!(target:"seeder", "Uploading { } random torrents to the Torrust Index with a { } seconds interval...", args.number_of_torrents.to_string().yellow(), args.interval.to_string().yellow()); - /* todo: - - Use a client to upload a random torrent every "interval" seconds. - */ + for i in 1..=args.number_of_torrents { + info!(target:"seeder", "Uploading torrent #{} ...", i.to_string().yellow()); + + match upload_random_torrent(&api_client).await { + Ok(uploaded_torrent) => { + debug!(target:"seeder", "Uploaded torrent {uploaded_torrent:?}"); + + let json = serde_json::to_string(&uploaded_torrent).context("failed to serialize upload response into JSON")?; + + info!(target:"seeder", "Uploaded torrent: {}", json.yellow()); + } + Err(err) => print!("Error uploading torrent {err:?}"), + }; + + if i != args.number_of_torrents { + sleep(Duration::from_secs(args.interval)); + } + } Ok(()) } -fn setup_logging(level: LevelFilter) { - if let Err(_err) = fern::Dispatch::new() - .format(|out, message, record| { - out.finish(format_args!( - "{} [{}][{}] {}", - chrono::Local::now().format("%+"), - record.target(), - record.level(), - message - )); - }) - .level(level) - .chain(std::io::stdout()) - .apply() - { - panic!("Failed to initialize logging.") +/// It logs in a user in the Index API. +pub async fn login_index_api(api_url: &str, username: &str, password: &str) -> LoggedInUserData { + let unauthenticated_client = Client::unauthenticated(api_url); + + info!(target:"seeder", "Trying to login with username: {} ...", username.yellow()); + + let user: LoggedInUserData = login(&unauthenticated_client, username, password).await; + + if user.admin { + info!(target:"seeder", "Logged as admin with account: {} ", username.yellow()); + } else { + info!(target:"seeder", "Logged as {} ", username.yellow()); } - debug!("logging initialized."); + user +} + +async fn upload_random_torrent(api_client: &Client) -> Result { + let uuid = Uuid::new_v4(); + + info!(target:"seeder", "Uploading torrent with uuid: {} ...", uuid.to_string().yellow()); + + let torrent_file = generate_random_torrent_file(uuid); + + let upload_form = UploadTorrentMultipartForm { + title: format!("title-{uuid}"), + description: format!("description-{uuid}"), + category: "test".to_string(), + torrent_file, + }; + + upload_torrent(api_client, upload_form).await +} + +/// It returns the bencoded binary data of the torrent meta file. +fn generate_random_torrent_file(uuid: Uuid) -> BinaryFile { + let torrent = generate_random_torrent(uuid); + + let bytes = parse_torrent::encode_torrent(&torrent).expect("msg:the torrent should be bencoded"); + + BinaryFile::from_bytes(torrent.info.name, bytes) } diff --git a/src/console/commands/seeder/logging.rs b/src/console/commands/seeder/logging.rs new file mode 100644 index 00000000..dc92ef7c --- /dev/null +++ b/src/console/commands/seeder/logging.rs @@ -0,0 +1,25 @@ +use log::{debug, LevelFilter}; + +/// # Panics +/// +/// +pub fn setup(level: LevelFilter) { + if let Err(_err) = fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{} [{}][{}] {}", + chrono::Local::now().format("%+"), + record.target(), + record.level(), + message + )); + }) + .level(level) + .chain(std::io::stdout()) + .apply() + { + panic!("Failed to initialize logging.") + } + + debug!("logging initialized."); +} diff --git a/src/console/commands/seeder/mod.rs b/src/console/commands/seeder/mod.rs index 309be628..c29812b4 100644 --- a/src/console/commands/seeder/mod.rs +++ b/src/console/commands/seeder/mod.rs @@ -1 +1,3 @@ +pub mod api; pub mod app; +pub mod logging; diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index 15b73516..59176203 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -4,7 +4,7 @@ use serde::Serialize; use super::connection_info::ConnectionInfo; use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; use super::contexts::tag::forms::{AddTagForm, DeleteTagForm}; -use super::contexts::torrent::forms::UpdateTorrentFrom; +use super::contexts::torrent::forms::UpdateTorrentForm; use super::contexts::torrent::requests::InfoHash; use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username}; use super::http::{Query, ReqwestQuery}; @@ -119,7 +119,7 @@ impl Client { self.http_client.delete(&format!("/torrent/{info_hash}")).await } - pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentFrom) -> TextResponse { + pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentForm) -> TextResponse { self.http_client .put(&format!("/torrent/{info_hash}"), &update_torrent_form) .await diff --git a/src/web/api/client/v1/contexts/torrent/forms.rs b/src/web/api/client/v1/contexts/torrent/forms.rs index 7f948b99..64a0360d 100644 --- a/src/web/api/client/v1/contexts/torrent/forms.rs +++ b/src/web/api/client/v1/contexts/torrent/forms.rs @@ -4,7 +4,7 @@ use std::path::Path; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] -pub struct UpdateTorrentFrom { +pub struct UpdateTorrentForm { pub title: Option, pub description: Option, pub category: Option, @@ -28,9 +28,9 @@ pub struct BinaryFile { impl BinaryFile { /// # Panics - /// + /// /// Will panic if: - /// + /// /// - The path is not a file. /// - The path can't be converted into string. /// - The file can't be read. @@ -41,6 +41,12 @@ impl BinaryFile { contents: fs::read(path).unwrap(), } } + + /// Build the binary file directly from the binary data provided. + #[must_use] + pub fn from_bytes(name: String, contents: Vec) -> Self { + BinaryFile { name, contents } + } } impl From for Form { diff --git a/src/web/api/client/v1/contexts/torrent/responses.rs b/src/web/api/client/v1/contexts/torrent/responses.rs index 2a9598df..07355e99 100644 --- a/src/web/api/client/v1/contexts/torrent/responses.rs +++ b/src/web/api/client/v1/contexts/torrent/responses.rs @@ -1,4 +1,4 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; pub type Id = i64; pub type CategoryId = i64; @@ -102,7 +102,7 @@ pub struct UploadedTorrentResponse { pub data: UploadedTorrent, } -#[derive(Deserialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct UploadedTorrent { pub torrent_id: Id, pub info_hash: String, From 324fac7edfa937db92add100420a0c7c4f51c53b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Feb 2024 17:28:32 +0000 Subject: [PATCH 055/309] fix: [#453] cargo fmt --check for nigthly toolchain --- src/console/commands/seeder/api.rs | 6 ++-- src/console/commands/seeder/app.rs | 30 +++++++------------ src/console/commands/seeder/logging.rs | 4 +-- .../api/client/v1/contexts/settings/mod.rs | 3 +- src/web/api/server/mod.rs | 2 +- 5 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs index e9af0ccd..2236082f 100644 --- a/src/console/commands/seeder/api.rs +++ b/src/console/commands/seeder/api.rs @@ -1,3 +1,6 @@ +use log::debug; +use thiserror::Error; + use crate::web::api::client::v1::client::Client; use crate::web::api::client::v1::contexts::category::forms::AddCategoryForm; use crate::web::api::client::v1::contexts::category::responses::{ListItem, ListResponse}; @@ -7,9 +10,6 @@ use crate::web::api::client::v1::contexts::user::forms::LoginForm; use crate::web::api::client::v1::contexts::user::responses::{LoggedInUserData, SuccessfulLoginResponse}; use crate::web::api::client::v1::responses::TextResponse; -use log::debug; -use thiserror::Error; - #[derive(Error, Debug)] pub enum Error { #[error("Torrent with the same info-hash already exist in the database")] diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index badc4e21..ac84dc83 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -14,7 +14,8 @@ //! //! That command would upload 1000 random torrents to the Index using the user //! account admin with password 123456 and waiting 1 second between uploads. -use std::{thread::sleep, time::Duration}; +use std::thread::sleep; +use std::time::Duration; use anyhow::Context; use clap::Parser; @@ -22,26 +23,15 @@ use log::{debug, info, LevelFilter}; use text_colorizer::Colorize; use uuid::Uuid; -use crate::{ - console::commands::seeder::{ - api::{login, upload_torrent}, - logging, - }, - services::torrent_file::generate_random_torrent, - utils::parse_torrent, - web::api::client::v1::{ - client::Client, - contexts::{ - torrent::{ - forms::{BinaryFile, UploadTorrentMultipartForm}, - responses::UploadedTorrent, - }, - user::responses::LoggedInUserData, - }, - }, -}; - use super::api::Error; +use crate::console::commands::seeder::api::{login, upload_torrent}; +use crate::console::commands::seeder::logging; +use crate::services::torrent_file::generate_random_torrent; +use crate::utils::parse_torrent; +use crate::web::api::client::v1::client::Client; +use crate::web::api::client::v1::contexts::torrent::forms::{BinaryFile, UploadTorrentMultipartForm}; +use crate::web::api::client::v1::contexts::torrent::responses::UploadedTorrent; +use crate::web::api::client::v1::contexts::user::responses::LoggedInUserData; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] diff --git a/src/console/commands/seeder/logging.rs b/src/console/commands/seeder/logging.rs index dc92ef7c..13d9c745 100644 --- a/src/console/commands/seeder/logging.rs +++ b/src/console/commands/seeder/logging.rs @@ -1,8 +1,8 @@ use log::{debug, LevelFilter}; /// # Panics -/// -/// +/// +/// pub fn setup(level: LevelFilter) { if let Err(_err) = fern::Dispatch::new() .format(|out, message, record| { diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 356e602a..e08a4103 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -1,11 +1,12 @@ pub mod responses; +use serde::{Deserialize, Serialize}; + use crate::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, TorrustIndex as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; -use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Settings { diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index a9756c68..9edda873 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -6,10 +6,10 @@ use std::sync::Arc; use futures::Future; use log::info; use tokio::sync::oneshot::{self, Sender}; +use v1::routes::router; use super::{Running, ServerStartedMessage}; use crate::common::AppData; -use v1::routes::router; /// Starts the API server. /// From adec8218ef6dde8b90da1044dc46f0293996cc28 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 30 Jan 2024 00:59:33 +0100 Subject: [PATCH 056/309] feat: [#445] new user id extractor --- src/web/api/server/v1/extractors/mod.rs | 1 + src/web/api/server/v1/extractors/user_id.rs | 37 +++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/web/api/server/v1/extractors/user_id.rs diff --git a/src/web/api/server/v1/extractors/mod.rs b/src/web/api/server/v1/extractors/mod.rs index 36d737ca..2c55e042 100644 --- a/src/web/api/server/v1/extractors/mod.rs +++ b/src/web/api/server/v1/extractors/mod.rs @@ -1 +1,2 @@ pub mod bearer_token; +pub mod user_id; diff --git a/src/web/api/server/v1/extractors/user_id.rs b/src/web/api/server/v1/extractors/user_id.rs new file mode 100644 index 00000000..71cdad1f --- /dev/null +++ b/src/web/api/server/v1/extractors/user_id.rs @@ -0,0 +1,37 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use axum::extract::{FromRef, FromRequestParts}; +use axum::http::request::Parts; +use axum::response::{IntoResponse, Response}; + +use super::bearer_token; +use crate::common::AppData; +use crate::errors::ServiceError; +use crate::models::user::UserId; + +pub struct ExtractLoggedInUser(pub UserId); + +#[async_trait] +impl FromRequestParts for ExtractLoggedInUser +where + Arc: FromRef, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let maybe_bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { + Ok(maybe_bearer_token) => maybe_bearer_token.0, + Err(_) => return Err(ServiceError::Unauthorized.into_response()), + }; + + //Extracts the app state + let app_data = Arc::from_ref(state); + + match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { + Ok(user_id) => Ok(ExtractLoggedInUser(user_id)), + Err(error) => Err(error.into_response()), + } + } +} From b5da54714e83f45b6714927235c9af397e7be899 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 30 Jan 2024 01:03:09 +0100 Subject: [PATCH 057/309] refactor: [#445] new return error type for user id extractor --- src/web/api/server/v1/extractors/user_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/api/server/v1/extractors/user_id.rs b/src/web/api/server/v1/extractors/user_id.rs index 71cdad1f..145e94b9 100644 --- a/src/web/api/server/v1/extractors/user_id.rs +++ b/src/web/api/server/v1/extractors/user_id.rs @@ -31,7 +31,7 @@ where match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { Ok(user_id) => Ok(ExtractLoggedInUser(user_id)), - Err(error) => Err(error.into_response()), + Err(_) => Err(ServiceError::Unauthorized.into_response()), } } } From 1cecc59e49ef9b39e2a45dec642bcd79b521966d Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 30 Jan 2024 20:48:55 +0100 Subject: [PATCH 058/309] feat: [#445] new custom error and minor refactor to extractor --- src/errors.rs | 4 ++++ src/web/api/server/v1/extractors/user_id.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 301d841c..274e07fd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -148,6 +148,9 @@ pub enum ServiceError { #[display(fmt = "Database error.")] DatabaseError, + #[display(fmt = "You must be logged in!.")] + LoggedInUserNotFound, + // Begin tracker errors #[display(fmt = "Sorry, we have an error with our tracker connection.")] TrackerOffline, @@ -311,6 +314,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::TrackerUnknownResponse => StatusCode::INTERNAL_SERVER_ERROR, ServiceError::TorrentNotFoundInTracker => StatusCode::NOT_FOUND, ServiceError::InvalidTrackerToken => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::LoggedInUserNotFound => StatusCode::UNAUTHORIZED, } } diff --git a/src/web/api/server/v1/extractors/user_id.rs b/src/web/api/server/v1/extractors/user_id.rs index 145e94b9..4ea81900 100644 --- a/src/web/api/server/v1/extractors/user_id.rs +++ b/src/web/api/server/v1/extractors/user_id.rs @@ -23,7 +23,7 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let maybe_bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { Ok(maybe_bearer_token) => maybe_bearer_token.0, - Err(_) => return Err(ServiceError::Unauthorized.into_response()), + Err(_) => return Err(ServiceError::TokenNotFound.into_response()), }; //Extracts the app state @@ -31,7 +31,7 @@ where match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { Ok(user_id) => Ok(ExtractLoggedInUser(user_id)), - Err(_) => Err(ServiceError::Unauthorized.into_response()), + Err(_) => Err(ServiceError::LoggedInUserNotFound.into_response()), } } } From 2f288c6c2699e94a26be8e4411fa068f9d579dbe Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 31 Jan 2024 00:04:47 +0100 Subject: [PATCH 059/309] refactor: [#445] new custom error message --- src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index 274e07fd..cad0a384 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -148,7 +148,7 @@ pub enum ServiceError { #[display(fmt = "Database error.")] DatabaseError, - #[display(fmt = "You must be logged in!.")] + #[display(fmt = "Please sign in!")] LoggedInUserNotFound, // Begin tracker errors From 59b1cced740f0126b10cf1a7982e6c9c4fa4e402 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 5 Feb 2024 21:54:42 +0100 Subject: [PATCH 060/309] refactor: [#445] new more descriptive error message --- src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index cad0a384..b750a852 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -148,7 +148,7 @@ pub enum ServiceError { #[display(fmt = "Database error.")] DatabaseError, - #[display(fmt = "Please sign in!")] + #[display(fmt = "Authentication error, please sign in")] LoggedInUserNotFound, // Begin tracker errors From 82b2f78dba88a293bb3f25e09f3a03ef050eea81 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:13:06 +0000 Subject: [PATCH 061/309] chore(deps): bump codecov/codecov-action from 3 to 4 --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index e84abf1a..f5f3a708 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -74,7 +74,7 @@ jobs: - id: upload name: Upload Coverage Report - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: ${{ steps.coverage.outputs.report }} From cbbca398b1b7a514ed2ddc3ed4088aa8431bfa43 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:14:49 +0000 Subject: [PATCH 062/309] chore(deps): bump zerocopy from 0.7.25 to 0.7.32 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1e872cf..08f1bb34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3796,18 +3796,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.25" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", From 45698a9cc05c439a80a6c177557452f8d722b837 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:16:12 +0000 Subject: [PATCH 063/309] chore(deps): bump config from 0.13.3 to 0.14.0 --- Cargo.lock | 114 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08f1bb34..4274da47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.6" @@ -485,11 +474,12 @@ dependencies = [ [[package]] name = "config" -version = "0.13.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" dependencies = [ "async-trait", + "convert_case 0.6.0", "json5", "lazy_static", "nom", @@ -498,7 +488,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.5.11", + "toml", "yaml-rust", ] @@ -508,12 +498,41 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "const-random" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -582,6 +601,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -638,7 +663,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -659,9 +684,12 @@ dependencies = [ [[package]] name = "dlv-list" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] [[package]] name = "dotenvy" @@ -1012,9 +1040,12 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.7", -] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" @@ -1022,7 +1053,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.6", + "ahash", "allocator-api2", ] @@ -1676,12 +1707,12 @@ dependencies = [ [[package]] name = "ordered-multimap" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" dependencies = [ "dlv-list", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -2088,13 +2119,14 @@ dependencies = [ [[package]] name = "ron" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.5", + "bitflags 2.4.1", "serde", + "serde_derive", ] [[package]] @@ -2128,9 +2160,9 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" dependencies = [ "cfg-if", "ordered-multimap", @@ -2551,7 +2583,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ - "ahash 0.8.6", + "ahash", "atoi", "byteorder", "bytes", @@ -2941,6 +2973,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.6.6" @@ -3044,15 +3085,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.8.8" @@ -3130,7 +3162,7 @@ dependencies = [ "text-to-png", "thiserror", "tokio", - "toml 0.8.8", + "toml", "torrust-index-located-error", "tower-http", "urlencoding", From 2b309d5281ad0bcb2bcfc91a20f1ffba7bb5a1b7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:17:52 +0000 Subject: [PATCH 064/309] chore(deps): bump serde from 1.0.192 to 1.0.193 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4274da47..141f14d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2329,9 +2329,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -2357,9 +2357,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", From 1c139a84ea31e5c1a99e0a339694841141334fa3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:19:26 +0000 Subject: [PATCH 065/309] chore(deps): bump h2 from 0.3.21 to 0.3.24 --- Cargo.lock | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 141f14d1..ed834946 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1018,9 +1018,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -1028,19 +1028,13 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.13.2" @@ -1252,16 +1246,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.1.0" @@ -2599,7 +2583,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.1.0", + "indexmap", "log", "memchr", "native-tls", @@ -3112,7 +3096,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.1.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -3138,7 +3122,7 @@ dependencies = [ "futures", "hex", "hyper", - "indexmap 2.1.0", + "indexmap", "jsonwebtoken", "lazy_static", "lettre", From f123a32f5c7b7d4e40c740cfde477e28bdb0f380 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:20:34 +0000 Subject: [PATCH 066/309] chore(deps): bump uuid from 1.5.0 to 1.7.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed834946..bf8cc9e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3447,9 +3447,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", ] From b39a2bec721ef61240f0baf10d765a3989c40549 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:21:51 +0000 Subject: [PATCH 067/309] chore(deps): bump openssl from 0.10.59 to 0.10.63 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf8cc9e4..2ee94240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,9 +1647,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1679,9 +1679,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", From 96ee3376558d19bfd9f6c9129317524761879044 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Feb 2024 08:28:26 +0000 Subject: [PATCH 068/309] ci: use stable rust for coverage report --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index f5f3a708..014a6cd9 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -38,7 +38,7 @@ jobs: name: Setup Toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly + toolchain: stable components: llvm-tools-preview - id: cache From 3c76794a4601a4e44682ff4b6a246c9a9b20e4f8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Feb 2024 16:22:58 +0000 Subject: [PATCH 069/309] docs: [#466] improve docs for seeder command --- project-words.txt | 1 + src/console/commands/mod.rs | 1 + src/console/commands/seeder/api.rs | 1 + src/console/commands/seeder/app.rs | 119 ++++++++++++++++++++++++- src/console/commands/seeder/logging.rs | 1 + src/console/commands/seeder/mod.rs | 1 + src/console/cronjobs/mod.rs | 1 + src/console/mod.rs | 1 + src/lib.rs | 2 +- src/web/api/mod.rs | 5 +- 10 files changed, 128 insertions(+), 5 deletions(-) diff --git a/project-words.txt b/project-words.txt index b8755b5a..9cc5b6b7 100644 --- a/project-words.txt +++ b/project-words.txt @@ -92,6 +92,7 @@ upgrader Uragqm urlencoding uroot +uuidgen Verstappen waivable webseeding diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs index 38506b77..e218659c 100644 --- a/src/console/commands/mod.rs +++ b/src/console/commands/mod.rs @@ -1,2 +1,3 @@ +//! Console commands that can be run manually. pub mod seeder; pub mod tracker_statistics_importer; diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs index 2236082f..499a8605 100644 --- a/src/console/commands/seeder/api.rs +++ b/src/console/commands/seeder/api.rs @@ -1,3 +1,4 @@ +//! Action that a user can perform on a Index website. use log::debug; use thiserror::Error; diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index ac84dc83..8297e071 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -1,19 +1,132 @@ -//! Program to upload random torrent to a live Index API. +//! Console app to upload random torrents to a live Index API. //! //! Run with: //! //! ```text -//! cargo run --bin seeder -- --api-base-url --number-of-torrents --user --password --interval +//! cargo run --bin seeder -- \ +//! --api-base-url \ +//! --number-of-torrents \ +//! --user \ +//! --password \ +//! --interval //! ``` //! //! For example: //! //! ```text -//! cargo run --bin seeder -- --api-base-url "localhost:3001" --number-of-torrents 1000 --user admin --password 12345678 --interval 0 +//! cargo run --bin seeder -- \ +//! --api-base-url "localhost:3001" \ +//! --number-of-torrents 1000 \ +//! --user admin \ +//! --password 12345678 \ +//! --interval 0 //! ``` //! //! That command would upload 1000 random torrents to the Index using the user //! account admin with password 123456 and waiting 1 second between uploads. +//! +//! The random torrents generated are single-file torrents from a TXT file. +//! All generated torrents used a UUID to identify the test torrent. The torrent +//! is generated on the fly without needing to generate the contents file. +//! However, if you like it, you can generate the contents and the torrent +//! manually with the following commands: +//! +//! ```text +//! cd /tmp +//! mkdir test_torrents +//! cd test_torrents +//! uuidgen +//! echo $'1fd827fb-29dc-47bd-b116-bf96f6466e65' > file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! imdl torrent create file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! imdl torrent show file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt.torrent +//! ``` +//! +//! That could be useful for testing purposes. For example, if you want to seed +//! the torrent with a `BitTorrent` client. +//! +//! Let's explain each line: +//! +//! First, we need to generate the UUID: +//! +//! ```text +//! uuidgen +//! 1fd827fb-29dc-47bd-b116-bf96f6466e65 +//! ```` +//! +//! Then, we need to create a text file and write the UUID into the file: +//! +//! ```text +//! echo $'1fd827fb-29dc-47bd-b116-bf96f6466e65' > file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! ``` +//! +//! Finally you can use a torrent creator like [Intermodal](https://github.com/casey/intermodal) +//! to generate the torrent file. You can use any `BitTorrent` client or other +//! console tool. +//! +//! ```text +//! imdl torrent create file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! $ imdl torrent create file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! [1/3] 🧿 Searching `file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt` for files… +//! [2/3] 🧮 Hashing pieces… +//! [3/3] 💾 Writing metainfo to `file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt.torrent`… +//! ✨✨ Done! ✨✨ +//! ```` +//! +//! The torrent meta file contains this information: +//! +//! ```text +//! $ imdl torrent show file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt.torrent +//! Name file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//! Creation Date 2024-02-07 12:47:32 UTC +//! Created By imdl/0.1.13 +//! Info Hash c8cf845e9771013b5c0e022cb1fc1feebdb24b66 +//! Torrent Size 201 bytes +//! Content Size 37 bytes +//! Private no +//! Piece Size 16 KiB +//! Piece Count 1 +//! File Count 1 +//! Files file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt +//!```` +//! +//! The torrent generated manually contains this info: +//! +//! ```json +//! { +//! "created by": "imdl/0.1.13", +//! "creation date": 1707304810, +//! "encoding": "UTF-8", +//! "info": { +//! "length": 37, +//! "name": "file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt", +//! "piece length": 16384, +//! "pieces": "E2 11 4F 69 79 50 1E CC F6 32 91 A5 12 FA D5 6B 49 20 12 D3" +//! } +//! } +//! ``` +//! +//! If you upload that torrent to the Index and you download it, then you +//! get this torrent information: +//! +//! ```json +//! { +//! "announce": "udp://tracker.torrust-demo.com:6969/k24qT2KgWFh9d5e1iHSJ9kOwfK45fH4V", +//! "announce-list": [ +//! [ +//! "udp://tracker.torrust-demo.com:6969/k24qT2KgWFh9d5e1iHSJ9kOwfK45fH4V" +//! ] +//! ], +//! "info": { +//! "length": 37, +//! "name": "file-1fd827fb-29dc-47bd-b116-bf96f6466e65.txt", +//! "piece length": 16384, +//! "pieces": "E2 11 4F 69 79 50 1E CC F6 32 91 A5 12 FA D5 6B 49 20 12 D3" +//! } +//! } +//! ``` +//! +//! As you can see the `info` dictionary is exactly the same, which produces +//! the same info-hash for the torrent. use std::thread::sleep; use std::time::Duration; diff --git a/src/console/commands/seeder/logging.rs b/src/console/commands/seeder/logging.rs index 13d9c745..85634719 100644 --- a/src/console/commands/seeder/logging.rs +++ b/src/console/commands/seeder/logging.rs @@ -1,3 +1,4 @@ +//! Logging setup for the `seeder`. use log::{debug, LevelFilter}; /// # Panics diff --git a/src/console/commands/seeder/mod.rs b/src/console/commands/seeder/mod.rs index c29812b4..dc37e756 100644 --- a/src/console/commands/seeder/mod.rs +++ b/src/console/commands/seeder/mod.rs @@ -1,3 +1,4 @@ +//! Command to upload random torrents to a live Index API. pub mod api; pub mod app; pub mod logging; diff --git a/src/console/cronjobs/mod.rs b/src/console/cronjobs/mod.rs index 43be8073..3fe5bab6 100644 --- a/src/console/cronjobs/mod.rs +++ b/src/console/cronjobs/mod.rs @@ -1 +1,2 @@ +//! Cronjobs that are executed automatically. pub mod tracker_statistics_importer; diff --git a/src/console/mod.rs b/src/console/mod.rs index 80eff453..486d5d30 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -1,2 +1,3 @@ +//! Console modules. pub mod commands; pub mod cronjobs; diff --git a/src/lib.rs b/src/lib.rs index 45e6ad24..44ee238d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,7 +231,7 @@ //! This console command allows you to manually import the tracker statistics. //! //! For more information about this command you can visit the documentation for -//! the [`Import tracker statistics`](crate::console::commands::import_tracker_statistics) module. +//! the [`Import tracker statistics`](crate::console::commands::tracker_statistics_importer) module. //! //! ## Upgrader //! diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index 8bfdacd9..f2d5b996 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -2,7 +2,10 @@ //! //! Currently, the API has only one version: `v1`. //! -//! Refer to the [`v1`]) module for more information. +//! Refer to: +//! +//! - [`client::v1`]) module for more information about the API client. +//! - [`server::v1`]) module for more information about the API server. pub mod client; pub mod server; From 5f7608ea101a9c9ee50a9f8a33ce46ee0d2fe11c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Feb 2024 16:43:50 +0000 Subject: [PATCH 070/309] docs: [#463] system package dep: libssl-dev Since we are using native TLS support you need to install `libssl-dev` on Linux: ``` sudo apt-get install libssl-dev ``` --- project-words.txt | 1 + src/lib.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index 9cc5b6b7..c6f4a670 100644 --- a/project-words.txt +++ b/project-words.txt @@ -68,6 +68,7 @@ rowid RUSTDOCFLAGS RUSTFLAGS rustfmt +rustls rustversion serde sgxj diff --git a/src/lib.rs b/src/lib.rs index 44ee238d..4f155d12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! ## Prerequisites //! //! In order the run the index you will need a running torrust tracker. In the -//! configuration you need to fill the `index` section with the following: +//! configuration you need to fill the `tracker` section with the following: //! //! ```toml //! [tracker] @@ -70,6 +70,16 @@ //! or you can use the docker to run both the tracker and the index. Refer to the //! [Run with docker](#run-with-docker) section for more information. //! +//! You will also need to install this dependency: +//! +//! ```text +//! sudo apt-get install libssl-dev +//! ``` +//! +//! We needed because we are using native TLS support instead of [rustls](https://github.com/rustls/rustls). +//! +//! More info: . +//! //! If you are using `SQLite3` as database driver, you will need to install the //! following dependency: //! From 90a203514235d4642340672984c4c40eeea2899f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Feb 2024 17:16:59 +0000 Subject: [PATCH 071/309] fix: [#457] fix API about pages with subfolder When the API is istalled using a subfolder links don't work. For exmaple: https://index.torrust-demo.com/api/ License links goes to: https://index.torrust-demo.com/v1/about/license instead of: https://index.torrust-demo.com/api/v1/about/license This change uses relative URLs and also an explicit redirecting to the abnout pages, intead of showing the same content for other pages like - https://index.torrust-demo.com/api/ - https://index.torrust-demo.com/api/v1 - https://index.torrust-demo.com/api/v1/about/ --- src/services/about.rs | 16 ++++++---------- src/web/api/server/v1/contexts/about/mod.rs | 2 +- src/web/api/server/v1/routes.rs | 10 +++++++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/services/about.rs b/src/services/about.rs index 28eedc02..a11bdfb2 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -1,5 +1,4 @@ //! Templates for "about" static pages. -use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; #[must_use] pub fn index_page() -> String { @@ -8,8 +7,7 @@ pub fn index_page() -> String { #[must_use] pub fn page() -> String { - format!( - r#" + r#" About @@ -22,17 +20,16 @@ pub fn page() -> String {

Hi! This is a running torrust-index.

"# - ) + .to_string() } #[must_use] pub fn license_page() -> String { - format!( - r#" + r#" Licensing @@ -55,9 +52,8 @@ pub fn license_page() -> String {

If you want to read more about all the licenses and how they apply please refer to the contributor agreement.

-"# - ) +"#.to_string() } diff --git a/src/web/api/server/v1/contexts/about/mod.rs b/src/web/api/server/v1/contexts/about/mod.rs index ef4668d1..02423b4c 100644 --- a/src/web/api/server/v1/contexts/about/mod.rs +++ b/src/web/api/server/v1/contexts/about/mod.rs @@ -35,7 +35,7 @@ //!

Hi! This is a running torrust-index.

//! //! //! //! ``` diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index c1666a38..a6049f49 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -3,13 +3,13 @@ use std::env; use std::sync::Arc; use axum::extract::DefaultBodyLimit; +use axum::response::Redirect; use axum::routing::get; use axum::{Json, Router}; use serde_json::{json, Value}; use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; -use super::contexts::about::handlers::about_page_handler; use super::contexts::{about, category, proxy, settings, tag, torrent, user}; use crate::bootstrap::config::ENV_VAR_CORS_PERMISSIVE; use crate::common::AppData; @@ -23,7 +23,7 @@ pub fn router(app_data: Arc) -> Router { // See: https://stackoverflow.com/questions/6845772/should-i-use-singular-or-plural-name-convention-for-rest-resources let v1_api_routes = Router::new() - .route("/", get(about_page_handler).with_state(app_data.clone())) + .route("/", get(redirect_to_about)) .nest("/user", user::routes::router(app_data.clone())) .nest("/about", about::routes::router(app_data.clone())) .nest("/category", category::routes::router(app_data.clone())) @@ -35,7 +35,7 @@ pub fn router(app_data: Arc) -> Router { .nest("/proxy", proxy::routes::router(app_data.clone())); let router = Router::new() - .route("/", get(about_page_handler).with_state(app_data.clone())) + .route("/", get(redirect_to_about)) .route("/health_check", get(health_check_handler).with_state(app_data)) .nest(&format!("/{API_VERSION_URL_PREFIX}"), v1_api_routes); @@ -52,3 +52,7 @@ pub fn router(app_data: Arc) -> Router { async fn health_check_handler() -> Json { Json(json!({ "status": "Ok" })) } + +async fn redirect_to_about() -> Redirect { + Redirect::permanent(&format!("/{API_VERSION_URL_PREFIX}/about")) +} From 4e7e920b593341d71fe5e66d3db30df4159daa0c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 10:14:59 +0000 Subject: [PATCH 072/309] feat: [#433] add timeout to health_check binary ``` cargo run --bin health_check http://127.0.0.1:3001/health_check ``` That command now fails if it does not get a response in 5 seconds. --- src/bin/health_check.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bin/health_check.rs b/src/bin/health_check.rs index 16e2d874..1ac6652c 100644 --- a/src/bin/health_check.rs +++ b/src/bin/health_check.rs @@ -4,14 +4,17 @@ //! //! - They are harder to maintain. //! - They introduce new attack vectors. +use std::time::Duration; use std::{env, process}; +use reqwest::Client; + #[tokio::main] async fn main() { let args: Vec = env::args().collect(); if args.len() != 2 { eprintln!("Usage: cargo run --bin health_check "); - eprintln!("Example: cargo run --bin health_check http://localhost:3002/health_check"); + eprintln!("Example: cargo run --bin health_check http://127.0.0.1:3001/health_check"); std::process::exit(1); } @@ -19,7 +22,9 @@ async fn main() { let url = &args[1].clone(); - match reqwest::get(url).await { + let client = Client::builder().timeout(Duration::from_secs(5)).build().unwrap(); + + match client.get(url).send().await { Ok(response) => { if response.status().is_success() { println!("STATUS: {}", response.status()); From f76f1aba239b01c31cdae5b6826d067ad625b52b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 10:26:05 +0000 Subject: [PATCH 073/309] fix: [#456] unknown feature stdsimd with ahash dep It fixes this error: ``` error[E0635]: unknown feature `stdsimd` --> /home/josecelano/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.8.6/src/lib.rs:99:42 | 99 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))] | ^^^^^^^ error[E0635]: unknown feature `stdsimd` --> /home/josecelano/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.7.7/src/lib.rs:33:42 | 33 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))] | ^^^^^^^ Checking bytemuck v1.14.0 For more information about this error, try `rustc --explain E0635`. error: could not compile `ahash` (lib) due to 1 previous error warning: build failed, waiting for other jobs to finish... error: could not compile `ahash` (lib) due to 1 previous error error: could not compile `ahash` (lib) due to 1 previous error ``` --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ee94240..0bf1996b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom", From 6d37f78d65f8036c34ff1c380768e830947fb736 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 10:29:41 +0000 Subject: [PATCH 074/309] ci: Intermodal command needs rust nigthly Coverage workflow is broken becuase Intermodal console app needs the Rust nigthly toolchain. ``` Run cargo install imdl Updating crates.io index Downloading crates ... Downloaded imdl v0.1.13 Installing imdl v0.1.13 error: failed to compile `imdl v0.1.13`, intermediate artifacts can be found at `/tmp/cargo-installB9h5OI`. To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path. Caused by: failed to run `rustc` to learn about target-specific information Caused by: process didn't exit successfully: `/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc - --crate-name ___ --print=file-names -Z profile -C codegen-units=1 -C inline-threshold=0 -C link-dead-code -C overflow-checks=off -C panic=abort -Z panic_abort_tests --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro --print=sysroot --print=split-debuginfo --print=crate-name --print=cfg` (exit status: 1) --- stderr error: the option `Z` is only accepted on the nightly compiler help: consider switching to a nightly toolchain: `rustup default nightly` note: selecting a toolchain with `+toolchain` arguments require a rustup proxy; see note: for more information about Rust's stability policy, see error: 1 nightly option were parsed Error: Process completed with exit code 101. ``` --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 014a6cd9..f5f3a708 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -38,7 +38,7 @@ jobs: name: Setup Toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: nightly components: llvm-tools-preview - id: cache From e341e98bf09cea2145ecb5bf28b361c9419e6233 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 11:01:43 +0000 Subject: [PATCH 075/309] feat: [#424] remove secrets from settings API endpoint These fields: - data.tracker.token - data.database.connect_url - data.mail.password - data.auth.secret_key are replaced with asterisks. --- src/config.rs | 7 ++++++ src/services/settings.rs | 25 ++++++++++++++++++- .../server/v1/contexts/settings/handlers.rs | 2 +- tests/e2e/environment.rs | 15 +++++++++++ .../web/api/v1/contexts/settings/contract.rs | 2 +- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4f90aa47..21fb454e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -389,6 +389,13 @@ impl TorrustIndex { fn override_tracker_api_token(&mut self, tracker_api_token: &str) { self.tracker.override_tracker_api_token(tracker_api_token); } + + pub fn remove_secrets(&mut self) { + self.tracker.token = "***".to_owned(); + self.database.connect_url = "***".to_owned(); + self.mail.password = "***".to_owned(); + self.auth.secret_key = "***".to_owned(); + } } /// The configuration service. diff --git a/src/services/settings.rs b/src/services/settings.rs index 5cfe9baf..1693df73 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -34,7 +34,30 @@ impl Service { return Err(ServiceError::Unauthorized); } - Ok(self.configuration.get_all().await) + let torrust_index_configuration = self.configuration.get_all().await; + + Ok(torrust_index_configuration) + } + + /// It gets all the settings making the secrets with asterisks. + /// + /// # Errors + /// + /// It returns an error if the user does not have the required permissions. + pub async fn get_all_masking_secrets(&self, user_id: &UserId) -> Result { + let user = self.user_repository.get_compact(user_id).await?; + + // Check if user is administrator + // todo: extract authorization service + if !user.administrator { + return Err(ServiceError::Unauthorized); + } + + let mut torrust_index_configuration = self.configuration.get_all().await; + + torrust_index_configuration.remove_secrets(); + + Ok(torrust_index_configuration) } /// It gets only the public settings. diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index 48c0cb91..9c737b42 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -22,7 +22,7 @@ pub async fn get_all_handler(State(app_data): State>, Extract(maybe Err(error) => return error.into_response(), }; - let all_settings = match app_data.settings_service.get_all(&user_id).await { + let all_settings = match app_data.settings_service.get_all_masking_secrets(&user_id).await { Ok(all_settings) => all_settings, Err(error) => return error.into_response(), }; diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index f8ae9ce4..87797a08 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -85,6 +85,21 @@ impl TestEnv { self.starting_settings.clone() } + /// Returns the server starting settings if the servers was already started, + /// masking secrets with asterisks. + pub fn server_settings_masking_secrets(&self) -> Option { + match self.starting_settings.clone() { + Some(mut settings) => { + settings.tracker.token = "***".to_owned(); + settings.database.connect_url = "***".to_owned(); + settings.mail.password = "***".to_owned(); + settings.auth.secret_key = "***".to_owned(); + Some(settings) + } + None => None, + } + } + /// Provides the API server socket address. /// For example: `localhost:3001`. pub fn server_socket_addr(&self) -> Option { diff --git a/tests/e2e/web/api/v1/contexts/settings/contract.rs b/tests/e2e/web/api/v1/contexts/settings/contract.rs index fbef5659..3bc741b7 100644 --- a/tests/e2e/web/api/v1/contexts/settings/contract.rs +++ b/tests/e2e/web/api/v1/contexts/settings/contract.rs @@ -61,7 +61,7 @@ async fn it_should_allow_admins_to_get_all_the_settings() { let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap(); - assert_eq!(res.data, env.server_settings().unwrap()); + assert_eq!(res.data, env.server_settings_masking_secrets().unwrap()); assert_json_ok_response(&response); } From 67a88270f0b8b4fe3682ff1d891417ee08d9cc2f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 13:14:10 +0000 Subject: [PATCH 076/309] docs: fix linter warnings --- docs/containers.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/containers.md b/docs/containers.md index 576f6149..8923d678 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -1,10 +1,10 @@ # Containers (Docker or Podman) ## Demo environment + It is simple to setup the index with the default configuration and run it using the pre-built public docker image: - With Docker: ```sh @@ -17,11 +17,12 @@ or with Podman: podman run -it torrust/index:latest ``` - ## Requirements + - Tested with recent versions of Docker or Podman. ## Volumes + The [Containerfile](../Containerfile) (i.e. the Dockerfile) Defines Three Volumes: ```Dockerfile @@ -38,7 +39,8 @@ When instancing the container image with the `docker run` or `podman run` comman > NOTE: You can adjust this mapping for your preference, however this mapping is the default in our guides and scripts. -### Pre-Create Host-Mapped Folders: +### Pre-Create Host-Mapped Folders + Please run this command where you wish to run the container: ```sh @@ -46,11 +48,13 @@ mkdir -p ./storage/index/lib/ ./storage/index/log/ ./storage/index/etc/ ``` ### Matching Ownership ID's of Host Storage and Container Volumes + It is important that the `torrust` user has the same uid `$(id -u)` as the host mapped folders. In our [entry script](../share/container/entry_script_sh), installed to `/usr/local/bin/entry.sh` inside the container, switches to the `torrust` user created based upon the `USER_UID` environmental variable. When running the container, you may use the `--env USER_ID="$(id -u)"` argument that gets the current user-id and passes to the container. ### Mapped Tree Structure + Using the standard mapping defined above produces this following mapped tree: ```s @@ -78,6 +82,7 @@ git clone https://github.com/torrust/torrust-index.git; cd torrust-index ``` ### (Docker) Setup Context + Before starting, if you are using docker, it is helpful to reset the context to the default: ```sh @@ -107,6 +112,7 @@ podman build --target debug --tag torrust-index:debug --file Containerfile . ## Running the Container ### Basic Run + No arguments are needed for simply checking the container image works: #### (Docker) Run Basic @@ -118,6 +124,7 @@ docker run -it torrust-index:release # Debug Mode docker run -it torrust-index:debug ``` + #### (Podman) Run Basic ```sh @@ -129,11 +136,13 @@ podman run -it torrust-index:debug ``` ### Arguments + The arguments need to be placed before the image tag. i.e. `run [arguments] torrust-index:release` #### Environmental Variables: + Environmental variables are loaded through the `--env`, in the format `--env VAR="value"`. The following environmental variables can be set: @@ -145,8 +154,8 @@ The following environmental variables can be set: - `USER_ID` - The user id for the runtime crated `torrust` user. Please Note: This user id should match the ownership of the host-mapped volumes, (default `1000`). - `API_PORT` - The port for the index API. This should match the port used in the configuration, (default `3001`). - ### Sockets + Socket ports used internally within the container can be mapped to with the `--publish` argument. The format is: `--publish [optional_host_ip]:[host_port]:[container_port]/[optional_protocol]`, for example: `--publish 127.0.0.1:8080:80/tcp`. @@ -159,8 +168,9 @@ The default ports can be mapped with the following: > NOTE: Inside the container it is necessary to expose a socket with the wildcard address `0.0.0.0` so that it may be accessible from the host. Verify that the configuration that the sockets are wildcard. -### Volumes -By default the container will use install volumes for `/var/lib/torrust/index`, `/var/log/torrust/index`, and `/etc/torrust/index`, however for better administration it good to make these volumes host-mapped. +### Mapped Volumes + +By default the container will install volumes for `/var/lib/torrust/index`, `/var/log/torrust/index`, and `/etc/torrust/index`, however for better administration it good to make these volumes host-mapped. The argument to host-map volumes is `--volume`, with the format: `--volume=[host-src:]container-dest[:]`. @@ -172,10 +182,9 @@ The default mapping can be supplied with the following arguments: --volume ./storage/index/etc:/etc/torrust/index:Z \ ``` - Please not the `:Z` at the end of the podman `--volume` mapping arguments, this is to give read-write permission on SELinux enabled systemd, if this doesn't work on your system, you can use `:rw` instead. -## Complete Example: +## Complete Example ### With Docker From 9d8cf7b603e9fa7ef85e4ed09bf77854ede73995 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 13:31:26 +0000 Subject: [PATCH 077/309] feat: [#412] allow to overwrite auth secret conf opt with env var You can overwrite the `auth::secret_key` in the config file: ```toml [auth] email_on_signup = "Optional" min_password_length = 6 max_password_length = 64 secret_key = "MaxVerstappenWC2021" ``` With an env var: `TORRUST_INDEX_AUTH_SECRET_KEY`. That will make it easier to inject secrets when you use docker without modifying the configuration file: `config.toml`. --- .env.local | 2 +- README.md | 5 +++- compose.yaml | 1 + .../container/e2e/sqlite/e2e-env-up.sh | 1 + docs/containers.md | 2 ++ .../default/config/index.container.mysql.toml | 8 +++--- .../config/index.e2e.container.mysql.toml | 8 +++--- .../config/index.e2e.container.sqlite3.toml | 8 +++--- src/bootstrap/config.rs | 4 +++ src/config.rs | 28 +++++++++++++++++-- tests/e2e/config.rs | 4 +++ 11 files changed, 54 insertions(+), 17 deletions(-) diff --git a/.env.local b/.env.local index faf5afb2..2cb1998c 100644 --- a/.env.local +++ b/.env.local @@ -1,7 +1,7 @@ DATABASE_URL=sqlite://storage/database/data.db?mode=rwc TORRUST_INDEX_CONFIG= +TORRUST_INDEX_AUTH_SECRET_KEY=MaxVerstappenWC2021 USER_ID=1000 TORRUST_TRACKER_CONFIG= TORRUST_TRACKER_DATABASE_DRIVER=sqlite3 TORRUST_TRACKER_API_ADMIN_TOKEN=MyAccessToken - diff --git a/README.md b/README.md index ef16a9e0..60348b37 100644 --- a/README.md +++ b/README.md @@ -93,13 +93,16 @@ _Optionally, you may choose to supply the entire configuration as an environment TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") cargo run ``` -_For deployment, you __should__ override the `tracker_api_token` by using an environmental variable:_ +_For deployment, you __should__ override: + +- The `tracker_api_token` and the `index_auth_secret_key` by using environmental variables:_ ```sh # Please use the secret that you generated for the torrust-tracker configuration. # Override secret in configuration using an environmental variable TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") \ TORRUST_INDEX_TRACKER_API_TOKEN=$(cat "./storage/tracker/lib/tracker_api_admin_token.secret") \ + TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ cargo run ``` diff --git a/compose.yaml b/compose.yaml index 84a7296b..946f13c1 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,6 +13,7 @@ services: - TORRUST_INDEX_DATABASE=${TORRUST_INDEX_DATABASE:-e2e_testing_sqlite3} - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - TORRUST_INDEX_TRACKER_API_TOKEN=${TORRUST_INDEX_TRACKER_API_TOKEN:-MyAccessToken} + - TORRUST_INDEX_AUTH_SECRET_KEY=${TORRUST_INDEX_AUTH_SECRET_KEY:-MaxVerstappenWC2021} networks: - server_side ports: diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index 952d1738..519d6018 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -8,6 +8,7 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ + TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ diff --git a/docs/containers.md b/docs/containers.md index 8923d678..f356d2f4 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -149,6 +149,7 @@ The following environmental variables can be set: - `TORRUST_INDEX_PATH_CONFIG` - The in-container path to the index configuration file, (default: `"/etc/torrust/index/index.toml"`). - `TORRUST_INDEX_TRACKER_API_TOKEN` - Override of the admin token. If set, this value overrides any value set in the config. +- `TORRUST_INDEX_AUTH_SECRET_KEY` - Override of the auth secret key. If set, this value overrides any value set in the config. - `TORRUST_INDEX_DATABASE_DRIVER` - The database type used for the container, (options: `sqlite3`, `mysql`, default `sqlite3`). Please Note: This dose not override the database configuration within the `.toml` config file. - `TORRUST_INDEX_CONFIG` - Load config from this environmental variable instead from a file, (i.e: `TORRUST_INDEX_CONFIG=$(cat index-index.toml)`). - `USER_ID` - The user id for the runtime crated `torrust` user. Please Note: This user id should match the ownership of the host-mapped volumes, (default `1000`). @@ -201,6 +202,7 @@ mkdir -p ./storage/index/lib/ ./storage/index/log/ ./storage/index/etc/ ## Run Torrust Index Container Image docker run -it \ --env TORRUST_INDEX_TRACKER_API_TOKEN="MySecretToken" \ + --env TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ --env USER_ID="$(id -u)" \ --publish 0.0.0.0:3001:3001/tcp \ --volume ./storage/index/lib:/var/lib/torrust/index:Z \ diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 9857d6ae..5da07797 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,12 +1,12 @@ +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` +# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` + log_level = "info" [website] name = "Torrust" -# Please override the tracker token setting the -# `TORRUST_INDEX_TRACKER_API_TOKEN` -# environmental variable! - [tracker] url = "udp://tracker:6969" mode = "Public" diff --git a/share/default/config/index.e2e.container.mysql.toml b/share/default/config/index.e2e.container.mysql.toml index c7b4c33c..b5e85813 100644 --- a/share/default/config/index.e2e.container.mysql.toml +++ b/share/default/config/index.e2e.container.mysql.toml @@ -1,12 +1,12 @@ +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` +# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` + log_level = "info" [website] name = "Torrust" -# Please override the tracker token setting the -# `TORRUST_INDEX_TRACKER_API_TOKEN` -# environmental variable! - [tracker] url = "udp://tracker:6969" mode = "Public" diff --git a/share/default/config/index.e2e.container.sqlite3.toml b/share/default/config/index.e2e.container.sqlite3.toml index ec15023b..9e220369 100644 --- a/share/default/config/index.e2e.container.sqlite3.toml +++ b/share/default/config/index.e2e.container.sqlite3.toml @@ -1,12 +1,12 @@ +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` +# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` + log_level = "info" [website] name = "Torrust" -# Please override the tracker token setting the -# `TORRUST_INDEX_TRACKER_API_TOKEN` -# environmental variable! - [tracker] url = "udp://tracker:6969" mode = "Public" diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index e6910a33..782c3d1a 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -13,6 +13,9 @@ const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; /// Token needed to communicate with the Torrust Tracker const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; +/// Secret key used to encrypt and decrypt +const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; + /// The `index.toml` file location. pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; @@ -44,6 +47,7 @@ pub fn initialize_configuration() -> Configuration { ENV_VAR_PATH_CONFIG.to_string(), DEFAULT_PATH_CONFIG.to_string(), ENV_VAR_API_ADMIN_TOKEN.to_string(), + ENV_VAR_AUTH_SECRET_KEY.to_string(), ) .unwrap(); diff --git a/src/config.rs b/src/config.rs index 21fb454e..b0ad49fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ use torrust_index_located_error::{Located, LocatedError}; pub struct Info { index_toml: String, tracker_api_token: Option, + auth_secret_key: Option, } impl Info { @@ -24,8 +25,8 @@ impl Info { /// /// ```no_run /// # use torrust_index::config::Info; - /// # let (env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token) = ("".to_string(), "".to_string(), "".to_string(), "".to_string()); - /// let result = Info::new(env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token); + /// # let (env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token, env_var_auth_secret_key) = ("".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()); + /// let result = Info::new(env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token, env_var_auth_secret_key); /// ``` /// /// # Errors @@ -38,6 +39,7 @@ impl Info { env_var_path_config: String, default_path_config: String, env_var_tracker_api_token: String, + env_var_auth_secret_key: String, ) -> Result { let index_toml = if let Ok(index_toml) = env::var(&env_var_config) { println!("Loading configuration from env var {env_var_config} ..."); @@ -61,11 +63,14 @@ impl Info { .parse() .map_err(|_e: std::convert::Infallible| Error::Infallible)? }; + let tracker_api_token = env::var(env_var_tracker_api_token).ok(); + let auth_secret_key = env::var(env_var_auth_secret_key).ok(); Ok(Self { index_toml, tracker_api_token, + auth_secret_key, }) } } @@ -239,6 +244,12 @@ impl Default for Auth { } } +impl Auth { + fn override_secret_key(&mut self, secret_key: &str) { + self.secret_key = secret_key.to_string(); + } +} + /// Database configuration. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Database { @@ -390,6 +401,10 @@ impl TorrustIndex { self.tracker.override_tracker_api_token(tracker_api_token); } + fn override_auth_secret_key(&mut self, auth_secret_key: &str) { + self.auth.override_secret_key(auth_secret_key); + } + pub fn remove_secrets(&mut self) { self.tracker.token = "***".to_owned(); self.database.connect_url = "***".to_owned(); @@ -457,7 +472,10 @@ impl Configuration { /// Loads the configuration from the `Info` struct. The whole /// configuration in toml format is included in the `info.index_toml` string. /// - /// Optionally will override the tracker api token. + /// Optionally will override the: + /// + /// - Tracker api token. + /// - The auth secret key. /// /// # Errors /// @@ -472,6 +490,10 @@ impl Configuration { index_config.override_tracker_api_token(token); }; + if let Some(ref secret_key) = info.auth_secret_key { + index_config.override_auth_secret_key(secret_key); + }; + Ok(Configuration { settings: RwLock::new(index_config), config_path: None, diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index 24cb87bf..a1f0a721 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -14,6 +14,9 @@ const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_E2E_CONFIG"; /// Token needed to communicate with the Torrust Tracker const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_E2E_TRACKER_API_TOKEN"; +/// Secret key used to encrypt and decrypt +const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_E2E_AUTH_SECRET_KEY"; + /// The `index.toml` file location. pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_E2E_PATH_CONFIG"; @@ -48,6 +51,7 @@ pub fn initialize_configuration() -> Configuration { ENV_VAR_PATH_CONFIG.to_string(), DEFAULT_PATH_CONFIG.to_string(), ENV_VAR_API_ADMIN_TOKEN.to_string(), + ENV_VAR_AUTH_SECRET_KEY.to_string(), ) .unwrap(); From 2cf5f47b61f6e633dd7c4523dcefcaafe962e5fb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 15:30:04 +0000 Subject: [PATCH 078/309] refactor: [#472] move generic HTTP client to the generic HTTP mod. --- src/web/api/client/v1/client.rs | 179 +------------------------- src/web/api/client/v1/http.rs | 217 ++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 177 deletions(-) diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index 59176203..da4c4df4 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -1,5 +1,4 @@ use reqwest::multipart; -use serde::Serialize; use super::connection_info::ConnectionInfo; use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; @@ -7,8 +6,8 @@ use super::contexts::tag::forms::{AddTagForm, DeleteTagForm}; use super::contexts::torrent::forms::UpdateTorrentForm; use super::contexts::torrent::requests::InfoHash; use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username}; -use super::http::{Query, ReqwestQuery}; -use super::responses::{self, BinaryResponse, TextResponse}; +use super::http::{Http, Query}; +use super::responses::{self, TextResponse}; /// API Client pub struct Client { @@ -157,177 +156,3 @@ impl Client { self.http_client.delete(&format!("/user/ban/{}", &username.value)).await } } - -/// Generic HTTP Client -struct Http { - connection_info: ConnectionInfo, -} - -impl Http { - pub fn new(connection_info: ConnectionInfo) -> Self { - Self { connection_info } - } - - pub async fn get(&self, path: &str, params: Query) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::builder() - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .send() - .await - .unwrap(), - }; - TextResponse::from(response).await - } - - pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::builder() - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .send() - .await - .unwrap(), - }; - // todo: If the response is a JSON, it returns the JSON body in a byte - // array. This is not the expected behavior. - // - Rename BinaryResponse to BinaryTorrentResponse - // - Return an error if the response is not a bittorrent file - BinaryResponse::from(response).await - } - - pub async fn inner_get(&self, path: &str) -> Result { - reqwest::Client::builder() - .build() - .unwrap() - .get(self.base_url(path).clone()) - .send() - .await - } - - pub async fn post(&self, path: &str, form: &T) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .post(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .post(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), - }; - TextResponse::from(response).await - } - - pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .build() - .unwrap() - .post(self.base_url(path).clone()) - .multipart(form) - .bearer_auth(token) - .send() - .await - .expect("failed to send multipart request with token"), - None => reqwest::Client::builder() - .build() - .unwrap() - .post(self.base_url(path).clone()) - .multipart(form) - .send() - .await - .expect("failed to send multipart request without token"), - }; - TextResponse::from(response).await - } - - pub async fn put(&self, path: &str, form: &T) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .put(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .put(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), - }; - TextResponse::from(response).await - } - - async fn delete(&self, path: &str) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .send() - .await - .unwrap(), - }; - TextResponse::from(response).await - } - - async fn delete_with_body(&self, path: &str, form: &T) -> TextResponse { - let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), - }; - TextResponse::from(response).await - } - - fn base_url(&self, path: &str) -> String { - format!( - "http://{}{}{path}", - &self.connection_info.bind_address, &self.connection_info.base_path - ) - } -} diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs index a3b172dc..fde56162 100644 --- a/src/web/api/client/v1/http.rs +++ b/src/web/api/client/v1/http.rs @@ -1,3 +1,9 @@ +use reqwest::multipart; +use serde::Serialize; + +use super::connection_info::ConnectionInfo; +use super::responses::{BinaryResponse, TextResponse}; + pub type ReqwestQuery = Vec; pub type ReqwestQueryParam = (String, String); @@ -55,3 +61,214 @@ impl From for ReqwestQueryParam { (param.name, param.value) } } + +/// Generic HTTP Client +pub struct Http { + connection_info: ConnectionInfo, +} + +impl Http { + #[must_use] + pub fn new(connection_info: ConnectionInfo) -> Self { + Self { connection_info } + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn get(&self, path: &str, params: Query) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await + .unwrap(), + }; + // todo: If the response is a JSON, it returns the JSON body in a byte + // array. This is not the expected behavior. + // - Rename BinaryResponse to BinaryTorrentResponse + // - Return an error if the response is not a bittorrent file + BinaryResponse::from(response).await + } + + /// # Errors + /// + /// Will fail if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + /// + /// # Panics + /// + /// This method fails it can't build a `reqwest` client. + pub async fn inner_get(&self, path: &str) -> Result { + reqwest::Client::builder() + .build() + .unwrap() + .get(self.base_url(path).clone()) + .send() + .await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn post(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .post(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .post(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::builder() + .build() + .unwrap() + .post(self.base_url(path).clone()) + .multipart(form) + .bearer_auth(token) + .send() + .await + .expect("failed to send multipart request with token"), + None => reqwest::Client::builder() + .build() + .unwrap() + .post(self.base_url(path).clone()) + .multipart(form) + .send() + .await + .expect("failed to send multipart request without token"), + }; + TextResponse::from(response).await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn put(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .put(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .put(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn delete(&self, path: &str) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + /// # Panics + /// + /// Will panic if there was an error while sending request, redirect loop + /// was detected or redirect limit was exhausted. + pub async fn delete_with_body(&self, path: &str, form: &T) -> TextResponse { + let response = match &self.connection_info.token { + Some(token) => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await + .unwrap(), + None => reqwest::Client::new() + .delete(self.base_url(path).clone()) + .json(&form) + .send() + .await + .unwrap(), + }; + TextResponse::from(response).await + } + + fn base_url(&self, path: &str) -> String { + format!( + "http://{}{}{path}", + &self.connection_info.bind_address, &self.connection_info.base_path + ) + } +} From 9ff4de45d19f216d29bd21ab7ec9c7cbd5307868 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:12:18 +0000 Subject: [PATCH 079/309] feat: [#472] inject URL scheme in API client --- src/console/commands/seeder/app.rs | 12 ++++-- src/web/api/client/v1/client.rs | 10 ++--- src/web/api/client/v1/connection_info.rs | 54 ++++++++++++++++++++++-- src/web/api/client/v1/http.rs | 4 +- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index 8297e071..84ab55cf 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -15,7 +15,7 @@ //! //! ```text //! cargo run --bin seeder -- \ -//! --api-base-url "localhost:3001" \ +//! --api-base-url "http://localhost:3001" \ //! --number-of-torrents 1000 \ //! --user admin \ //! --password 12345678 \ @@ -127,12 +127,14 @@ //! //! As you can see the `info` dictionary is exactly the same, which produces //! the same info-hash for the torrent. +use std::str::FromStr; use std::thread::sleep; use std::time::Duration; use anyhow::Context; use clap::Parser; use log::{debug, info, LevelFilter}; +use reqwest::Url; use text_colorizer::Colorize; use uuid::Uuid; @@ -173,9 +175,11 @@ pub async fn run() -> anyhow::Result<()> { let args = Args::parse(); - let api_user = login_index_api(&args.api_base_url, &args.user, &args.password).await; + let api_url = Url::from_str(&args.api_base_url).context("failed to parse API base URL")?; - let api_client = Client::authenticated(&args.api_base_url, &api_user.token); + let api_user = login_index_api(&api_url, &args.user, &args.password).await; + + let api_client = Client::authenticated(&api_url, &api_user.token); info!(target:"seeder", "Uploading { } random torrents to the Torrust Index with a { } seconds interval...", args.number_of_torrents.to_string().yellow(), args.interval.to_string().yellow()); @@ -202,7 +206,7 @@ pub async fn run() -> anyhow::Result<()> { } /// It logs in a user in the Index API. -pub async fn login_index_api(api_url: &str, username: &str, password: &str) -> LoggedInUserData { +pub async fn login_index_api(api_url: &Url, username: &str, password: &str) -> LoggedInUserData { let unauthenticated_client = Client::unauthenticated(api_url); info!(target:"seeder", "Trying to login with username: {} ...", username.yellow()); diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index da4c4df4..72723959 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -1,4 +1,4 @@ -use reqwest::multipart; +use reqwest::{multipart, Url}; use super::connection_info::ConnectionInfo; use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; @@ -22,13 +22,13 @@ impl Client { } #[must_use] - pub fn unauthenticated(bind_address: &str) -> Self { - Self::new(ConnectionInfo::anonymous(bind_address, &Self::base_path())) + pub fn unauthenticated(base_url: &Url) -> Self { + Self::new(ConnectionInfo::anonymous(base_url, &Self::base_path())) } #[must_use] - pub fn authenticated(bind_address: &str, token: &str) -> Self { - Self::new(ConnectionInfo::new(bind_address, &Self::base_path(), token)) + pub fn authenticated(base_url: &Url, token: &str) -> Self { + Self::new(ConnectionInfo::new(base_url, &Self::base_path(), token)) } #[must_use] diff --git a/src/web/api/client/v1/connection_info.rs b/src/web/api/client/v1/connection_info.rs index 2183f4b9..4aa3e927 100644 --- a/src/web/api/client/v1/connection_info.rs +++ b/src/web/api/client/v1/connection_info.rs @@ -1,24 +1,70 @@ +use std::{fmt, str::FromStr}; + +use reqwest::Url; + +#[derive(Clone)] +pub enum Scheme { + Http, + Https, +} + +impl fmt::Display for Scheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Scheme::Http => write!(f, "http"), + Scheme::Https => write!(f, "https"), + } + } +} + +impl FromStr for Scheme { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "http" => Ok(Scheme::Http), + "https" => Ok(Scheme::Https), + _ => Err(()), + } + } +} + #[derive(Clone)] pub struct ConnectionInfo { + pub scheme: Scheme, pub bind_address: String, pub base_path: String, pub token: Option, } impl ConnectionInfo { + /// # Panics + /// + /// Will panic if the the base URL does not have a valid scheme: `http` or `https`. #[must_use] - pub fn new(bind_address: &str, base_path: &str, token: &str) -> Self { + pub fn new(base_url: &Url, base_path: &str, token: &str) -> Self { Self { - bind_address: bind_address.to_string(), + scheme: base_url + .scheme() + .parse() + .expect("base API URL scheme should be 'http' or 'https"), + bind_address: base_url.authority().to_string(), base_path: base_path.to_string(), token: Some(token.to_string()), } } + /// # Panics + /// + /// Will panic if the the base URL does not have a valid scheme: `http` or `https`. #[must_use] - pub fn anonymous(bind_address: &str, base_path: &str) -> Self { + pub fn anonymous(base_url: &Url, base_path: &str) -> Self { Self { - bind_address: bind_address.to_string(), + scheme: base_url + .scheme() + .parse() + .expect("base API URL scheme should be 'http' or 'https"), + bind_address: base_url.authority().to_string(), base_path: base_path.to_string(), token: None, } diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs index fde56162..7ae7823e 100644 --- a/src/web/api/client/v1/http.rs +++ b/src/web/api/client/v1/http.rs @@ -267,8 +267,8 @@ impl Http { fn base_url(&self, path: &str) -> String { format!( - "http://{}{}{path}", - &self.connection_info.bind_address, &self.connection_info.base_path + "{}://{}{}{path}", + &self.connection_info.scheme, &self.connection_info.bind_address, &self.connection_info.base_path ) } } From 26422fc5348b4f5d124d6c0b32e3c5a1e01a5af9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:25:58 +0000 Subject: [PATCH 080/309] feat: [#472] add timeout to API client requests both in testing and production clients. --- src/web/api/client/v1/connection_info.rs | 3 ++- src/web/api/client/v1/http.rs | 17 ++++++++++++++++- tests/common/client.rs | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/web/api/client/v1/connection_info.rs b/src/web/api/client/v1/connection_info.rs index 4aa3e927..8c186732 100644 --- a/src/web/api/client/v1/connection_info.rs +++ b/src/web/api/client/v1/connection_info.rs @@ -1,4 +1,5 @@ -use std::{fmt, str::FromStr}; +use std::fmt; +use std::str::FromStr; use reqwest::Url; diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs index 7ae7823e..1c90890a 100644 --- a/src/web/api/client/v1/http.rs +++ b/src/web/api/client/v1/http.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use reqwest::multipart; use serde::Serialize; @@ -65,12 +67,18 @@ impl From for ReqwestQueryParam { /// Generic HTTP Client pub struct Http { connection_info: ConnectionInfo, + /// The timeout is applied from when the request starts connecting until the + /// response body has finished. + timeout: Duration, } impl Http { #[must_use] pub fn new(connection_info: ConnectionInfo) -> Self { - Self { connection_info } + Self { + connection_info, + timeout: Duration::from_secs(5), + } } /// # Panics @@ -80,6 +88,7 @@ impl Http { pub async fn get(&self, path: &str, params: Query) -> TextResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -89,6 +98,7 @@ impl Http { .await .unwrap(), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -107,6 +117,7 @@ impl Http { pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -116,6 +127,7 @@ impl Http { .await .unwrap(), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -141,6 +153,7 @@ impl Http { /// This method fails it can't build a `reqwest` client. pub async fn inner_get(&self, path: &str) -> Result { reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -178,6 +191,7 @@ impl Http { pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .post(self.base_url(path).clone()) @@ -187,6 +201,7 @@ impl Http { .await .expect("failed to send multipart request with token"), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .post(self.base_url(path).clone()) diff --git a/tests/common/client.rs b/tests/common/client.rs index 97216bfa..1e938d1d 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use reqwest::multipart; use serde::Serialize; @@ -158,16 +160,23 @@ impl Client { /// Generic HTTP Client struct Http { connection_info: ConnectionInfo, + /// The timeout is applied from when the request starts connecting until the + /// response body has finished. + timeout: Duration, } impl Http { pub fn new(connection_info: ConnectionInfo) -> Self { - Self { connection_info } + Self { + connection_info, + timeout: Duration::from_secs(5), + } } pub async fn get(&self, path: &str, params: Query) -> TextResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -177,6 +186,7 @@ impl Http { .await .unwrap(), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -191,6 +201,7 @@ impl Http { pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -200,6 +211,7 @@ impl Http { .await .unwrap(), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -217,6 +229,7 @@ impl Http { pub async fn inner_get(&self, path: &str) -> Result { reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .get(self.base_url(path).clone()) @@ -246,6 +259,7 @@ impl Http { pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { let response = match &self.connection_info.token { Some(token) => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .post(self.base_url(path).clone()) @@ -255,6 +269,7 @@ impl Http { .await .expect("failed to send multipart request with token"), None => reqwest::Client::builder() + .timeout(self.timeout) .build() .unwrap() .post(self.base_url(path).clone()) From a8c2f84aedba7d8379f60fdc1c80375d557b013e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:44:21 +0000 Subject: [PATCH 081/309] refactor: [#472] move unwraps from generic HTTP client up to Index API client --- src/web/api/client/v1/client.rs | 128 ++++++++++--- src/web/api/client/v1/http.rs | 307 ++++++++++++++++---------------- 2 files changed, 263 insertions(+), 172 deletions(-) diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index 72723959..c56b9b59 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -46,113 +46,199 @@ impl Client { // Context: about + /// # Panics + /// + /// Will panic if the request fails. pub async fn about(&self) -> TextResponse { - self.http_client.get("/about", Query::empty()).await + self.http_client.get("/about", Query::empty()).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn license(&self) -> TextResponse { - self.http_client.get("/about/license", Query::empty()).await + self.http_client.get("/about/license", Query::empty()).await.unwrap() } // Context: category + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_categories(&self) -> TextResponse { - self.http_client.get("/category", Query::empty()).await + self.http_client.get("/category", Query::empty()).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn add_category(&self, add_category_form: AddCategoryForm) -> TextResponse { - self.http_client.post("/category", &add_category_form).await + self.http_client.post("/category", &add_category_form).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> TextResponse { - self.http_client.delete_with_body("/category", &delete_category_form).await + self.http_client + .delete_with_body("/category", &delete_category_form) + .await + .unwrap() } // Context: tag + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_tags(&self) -> TextResponse { // code-review: some endpoint are using plural // (for instance, `get_categories`) and some singular. - self.http_client.get("/tags", Query::empty()).await + self.http_client.get("/tags", Query::empty()).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn add_tag(&self, add_tag_form: AddTagForm) -> TextResponse { - self.http_client.post("/tag", &add_tag_form).await + self.http_client.post("/tag", &add_tag_form).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> TextResponse { - self.http_client.delete_with_body("/tag", &delete_tag_form).await + self.http_client.delete_with_body("/tag", &delete_tag_form).await.unwrap() } // Context: root + /// # Panics + /// + /// Will panic if the request fails. pub async fn root(&self) -> TextResponse { - self.http_client.get("", Query::empty()).await + self.http_client.get("", Query::empty()).await.unwrap() } // Context: settings + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_public_settings(&self) -> TextResponse { - self.http_client.get("/settings/public", Query::empty()).await + self.http_client.get("/settings/public", Query::empty()).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_site_name(&self) -> TextResponse { - self.http_client.get("/settings/name", Query::empty()).await + self.http_client.get("/settings/name", Query::empty()).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_settings(&self) -> TextResponse { - self.http_client.get("/settings", Query::empty()).await + self.http_client.get("/settings", Query::empty()).await.unwrap() } // Context: torrent + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_torrents(&self, params: Query) -> TextResponse { - self.http_client.get("/torrents", params).await + self.http_client.get("/torrents", params).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn get_torrent(&self, info_hash: &InfoHash) -> TextResponse { - self.http_client.get(&format!("/torrent/{info_hash}"), Query::empty()).await + self.http_client + .get(&format!("/torrent/{info_hash}"), Query::empty()) + .await + .unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn delete_torrent(&self, info_hash: &InfoHash) -> TextResponse { - self.http_client.delete(&format!("/torrent/{info_hash}")).await + self.http_client.delete(&format!("/torrent/{info_hash}")).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentForm) -> TextResponse { self.http_client .put(&format!("/torrent/{info_hash}"), &update_torrent_form) .await + .unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn upload_torrent(&self, form: multipart::Form) -> TextResponse { - self.http_client.post_multipart("/torrent/upload", form).await + self.http_client.post_multipart("/torrent/upload", form).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn download_torrent(&self, info_hash: &InfoHash) -> responses::BinaryResponse { self.http_client .get_binary(&format!("/torrent/download/{info_hash}"), Query::empty()) .await + .unwrap() } // Context: user + /// # Panics + /// + /// Will panic if the request fails. pub async fn register_user(&self, registration_form: RegistrationForm) -> TextResponse { - self.http_client.post("/user/register", ®istration_form).await + self.http_client.post("/user/register", ®istration_form).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn login_user(&self, registration_form: LoginForm) -> TextResponse { - self.http_client.post("/user/login", ®istration_form).await + self.http_client.post("/user/login", ®istration_form).await.unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> TextResponse { - self.http_client.post("/user/token/verify", &token_verification_form).await + self.http_client + .post("/user/token/verify", &token_verification_form) + .await + .unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> TextResponse { - self.http_client.post("/user/token/renew", &token_verification_form).await + self.http_client + .post("/user/token/renew", &token_verification_form) + .await + .unwrap() } + /// # Panics + /// + /// Will panic if the request fails. pub async fn ban_user(&self, username: Username) -> TextResponse { - self.http_client.delete(&format!("/user/ban/{}", &username.value)).await + self.http_client + .delete(&format!("/user/ban/{}", &username.value)) + .await + .unwrap() } } diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs index 1c90890a..ca42d462 100644 --- a/src/web/api/client/v1/http.rs +++ b/src/web/api/client/v1/http.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use reqwest::multipart; +use reqwest::{multipart, Error}; use serde::Serialize; use super::connection_info::ConnectionInfo; @@ -81,203 +81,208 @@ impl Http { } } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn get(&self, path: &str, params: Query) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn get(&self, path: &str, params: Query) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await? + } + None => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await? + } }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn get_binary(&self, path: &str, params: Query) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .get(self.base_url(path).clone()) - .query(&ReqwestQuery::from(params)) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .bearer_auth(token) + .send() + .await? + } + None => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .get(self.base_url(path).clone()) + .query(&ReqwestQuery::from(params)) + .send() + .await? + } }; + // todo: If the response is a JSON, it returns the JSON body in a byte // array. This is not the expected behavior. // - Rename BinaryResponse to BinaryTorrentResponse // - Return an error if the response is not a bittorrent file - BinaryResponse::from(response).await + Ok(BinaryResponse::from(response).await) } /// # Errors /// - /// Will fail if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - /// - /// # Panics - /// - /// This method fails it can't build a `reqwest` client. + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. pub async fn inner_get(&self, path: &str) -> Result { reqwest::Client::builder() .timeout(self.timeout) - .build() - .unwrap() + .build()? .get(self.base_url(path).clone()) .send() .await } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn post(&self, path: &str, form: &T) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn post(&self, path: &str, form: &T) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .post(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .post(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::new() + .post(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await? + } + None => { + reqwest::Client::new() + .post(self.base_url(path).clone()) + .json(&form) + .send() + .await? + } }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .post(self.base_url(path).clone()) - .multipart(form) - .bearer_auth(token) - .send() - .await - .expect("failed to send multipart request with token"), - None => reqwest::Client::builder() - .timeout(self.timeout) - .build() - .unwrap() - .post(self.base_url(path).clone()) - .multipart(form) - .send() - .await - .expect("failed to send multipart request without token"), + Some(token) => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .post(self.base_url(path).clone()) + .multipart(form) + .bearer_auth(token) + .send() + .await? + } + None => { + reqwest::Client::builder() + .timeout(self.timeout) + .build()? + .post(self.base_url(path).clone()) + .multipart(form) + .send() + .await? + } }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn put(&self, path: &str, form: &T) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn put(&self, path: &str, form: &T) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .put(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .put(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::new() + .put(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await? + } + None => { + reqwest::Client::new() + .put(self.base_url(path).clone()) + .json(&form) + .send() + .await? + } }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn delete(&self, path: &str) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn delete(&self, path: &str) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .bearer_auth(token) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .send() + .await? + } + None => reqwest::Client::new().delete(self.base_url(path).clone()).send().await?, }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } - /// # Panics + /// # Errors /// - /// Will panic if there was an error while sending request, redirect loop - /// was detected or redirect limit was exhausted. - pub async fn delete_with_body(&self, path: &str, form: &T) -> TextResponse { + /// Will return an error if there was an error while sending request, + /// redirect loop was detected or redirect limit was exhausted. + pub async fn delete_with_body(&self, path: &str, form: &T) -> Result { let response = match &self.connection_info.token { - Some(token) => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .bearer_auth(token) - .json(&form) - .send() - .await - .unwrap(), - None => reqwest::Client::new() - .delete(self.base_url(path).clone()) - .json(&form) - .send() - .await - .unwrap(), + Some(token) => { + reqwest::Client::new() + .delete(self.base_url(path).clone()) + .bearer_auth(token) + .json(&form) + .send() + .await? + } + None => { + reqwest::Client::new() + .delete(self.base_url(path).clone()) + .json(&form) + .send() + .await? + } }; - TextResponse::from(response).await + + Ok(TextResponse::from(response).await) } fn base_url(&self, path: &str) -> String { From 564df072071f66e02346db6b299a5bed1bc7c0ca Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:46:57 +0000 Subject: [PATCH 082/309] docs: move comment --- src/web/api/client/v1/client.rs | 2 -- src/web/api/server/v1/routes.rs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index c56b9b59..41ae631f 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -92,8 +92,6 @@ impl Client { /// /// Will panic if the request fails. pub async fn get_tags(&self) -> TextResponse { - // code-review: some endpoint are using plural - // (for instance, `get_categories`) and some singular. self.http_client.get("/tags", Query::empty()).await.unwrap() } diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index a6049f49..9f33c816 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -20,6 +20,7 @@ pub const API_VERSION_URL_PREFIX: &str = "v1"; #[allow(clippy::needless_pass_by_value)] pub fn router(app_data: Arc) -> Router { // code-review: should we use plural for the resource prefix: `users`, `categories`, `tags`? + // Some endpoint are using plural (for instance, `get_categories`) and some singular. // See: https://stackoverflow.com/questions/6845772/should-i-use-singular-or-plural-name-convention-for-rest-resources let v1_api_routes = Router::new() From 1015945b86583af2930a01c40eea68a813032433 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:50:46 +0000 Subject: [PATCH 083/309] chore: remove comment I think we can consume the forms, they are not likely to be used anymore. --- src/web/api/client/v1/client.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index 41ae631f..1bd29c76 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -15,8 +15,6 @@ pub struct Client { } impl Client { - // todo: forms in POST requests can be passed by reference. - fn base_path() -> String { "/v1".to_string() } From 2fc201676033436a04f9b2b7f55b85498f630c42 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 17:08:27 +0000 Subject: [PATCH 084/309] refactor: [#472] move unwraps from API client up The Index API Client user must handle request errors. --- src/console/commands/seeder/api.rs | 15 +- src/web/api/client/v1/client.rs | 212 +++++++++++++++++------------ 2 files changed, 139 insertions(+), 88 deletions(-) diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs index 499a8605..0af8e969 100644 --- a/src/console/commands/seeder/api.rs +++ b/src/console/commands/seeder/api.rs @@ -35,7 +35,10 @@ pub async fn upload_torrent(client: &Client, upload_torrent_form: UploadTorrentM add_category(client, &upload_torrent_form.category).await; } - let response = client.upload_torrent(upload_torrent_form.into()).await; + let response = client + .upload_torrent(upload_torrent_form.into()) + .await + .expect("API should return a response"); debug!(target:"seeder", "response: {}", response.status); @@ -68,7 +71,8 @@ pub async fn login(client: &Client, username: &str, password: &str) -> LoggedInU login: username.to_owned(), password: password.to_owned(), }) - .await; + .await + .expect("API should return a response"); let res: SuccessfulLoginResponse = serde_json::from_str(&response.body).unwrap_or_else(|_| { panic!( @@ -86,7 +90,7 @@ pub async fn login(client: &Client, username: &str, password: &str) -> LoggedInU /// /// Panics if the response body is not a valid JSON. pub async fn get_categories(client: &Client) -> Vec { - let response = client.get_categories().await; + let response = client.get_categories().await.expect("API should return a response"); let res: ListResponse = serde_json::from_str(&response.body).unwrap(); @@ -94,6 +98,10 @@ pub async fn get_categories(client: &Client) -> Vec { } /// It adds a new category. +/// +/// # Panics +/// +/// Will panic if it doesn't get a response form the API. pub async fn add_category(client: &Client, name: &str) -> TextResponse { client .add_category(AddCategoryForm { @@ -101,6 +109,7 @@ pub async fn add_category(client: &Client, name: &str) -> TextResponse { icon: None, }) .await + .expect("API should return a response") } /// It checks if the category list contains the given category. diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index 1bd29c76..e3f83a86 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -9,6 +9,17 @@ use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm use super::http::{Http, Query}; use super::responses::{self, TextResponse}; +#[derive(Debug)] +pub enum Error { + HttpError(reqwest::Error), +} + +impl From for Error { + fn from(err: reqwest::Error) -> Self { + Error::HttpError(err) + } +} + /// API Client pub struct Client { http_client: Http, @@ -44,197 +55,228 @@ impl Client { // Context: about - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn about(&self) -> TextResponse { - self.http_client.get("/about", Query::empty()).await.unwrap() + pub async fn about(&self) -> Result { + self.http_client.get("/about", Query::empty()).await.map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn license(&self) -> TextResponse { - self.http_client.get("/about/license", Query::empty()).await.unwrap() + /// Will panic if the request fails. + pub async fn license(&self) -> Result { + self.http_client + .get("/about/license", Query::empty()) + .await + .map_err(Error::from) } // Context: category - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn get_categories(&self) -> TextResponse { - self.http_client.get("/category", Query::empty()).await.unwrap() + pub async fn get_categories(&self) -> Result { + self.http_client.get("/category", Query::empty()).await.map_err(Error::from) } - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn add_category(&self, add_category_form: AddCategoryForm) -> TextResponse { - self.http_client.post("/category", &add_category_form).await.unwrap() + pub async fn add_category(&self, add_category_form: AddCategoryForm) -> Result { + self.http_client + .post("/category", &add_category_form) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> TextResponse { + pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> Result { self.http_client .delete_with_body("/category", &delete_category_form) .await - .unwrap() + .map_err(Error::from) } // Context: tag - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn get_tags(&self) -> TextResponse { - self.http_client.get("/tags", Query::empty()).await.unwrap() + /// Will panic if the request fails. + pub async fn get_tags(&self) -> Result { + self.http_client.get("/tags", Query::empty()).await.map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn add_tag(&self, add_tag_form: AddTagForm) -> TextResponse { - self.http_client.post("/tag", &add_tag_form).await.unwrap() + /// Will panic if the request fails. + pub async fn add_tag(&self, add_tag_form: AddTagForm) -> Result { + self.http_client.post("/tag", &add_tag_form).await.map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> TextResponse { - self.http_client.delete_with_body("/tag", &delete_tag_form).await.unwrap() + /// Will panic if the request fails. + pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> Result { + self.http_client + .delete_with_body("/tag", &delete_tag_form) + .await + .map_err(Error::from) } // Context: root - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn root(&self) -> TextResponse { - self.http_client.get("", Query::empty()).await.unwrap() + /// Will panic if the request fails. + pub async fn root(&self) -> Result { + self.http_client.get("", Query::empty()).await.map_err(Error::from) } // Context: settings - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn get_public_settings(&self) -> TextResponse { - self.http_client.get("/settings/public", Query::empty()).await.unwrap() + /// Will panic if the request fails. + pub async fn get_public_settings(&self) -> Result { + self.http_client + .get("/settings/public", Query::empty()) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn get_site_name(&self) -> TextResponse { - self.http_client.get("/settings/name", Query::empty()).await.unwrap() + pub async fn get_site_name(&self) -> Result { + self.http_client + .get("/settings/name", Query::empty()) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn get_settings(&self) -> TextResponse { - self.http_client.get("/settings", Query::empty()).await.unwrap() + pub async fn get_settings(&self) -> Result { + self.http_client.get("/settings", Query::empty()).await.map_err(Error::from) } // Context: torrent - /// # Panics + /// # Errors /// /// Will panic if the request fails. - pub async fn get_torrents(&self, params: Query) -> TextResponse { - self.http_client.get("/torrents", params).await.unwrap() + pub async fn get_torrents(&self, params: Query) -> Result { + self.http_client.get("/torrents", params).await.map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn get_torrent(&self, info_hash: &InfoHash) -> TextResponse { + /// Will panic if the request fails. + pub async fn get_torrent(&self, info_hash: &InfoHash) -> Result { self.http_client .get(&format!("/torrent/{info_hash}"), Query::empty()) .await - .unwrap() + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn delete_torrent(&self, info_hash: &InfoHash) -> TextResponse { - self.http_client.delete(&format!("/torrent/{info_hash}")).await.unwrap() + /// Will panic if the request fails. + pub async fn delete_torrent(&self, info_hash: &InfoHash) -> Result { + self.http_client + .delete(&format!("/torrent/{info_hash}")) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn update_torrent(&self, info_hash: &InfoHash, update_torrent_form: UpdateTorrentForm) -> TextResponse { + /// Will panic if the request fails. + pub async fn update_torrent( + &self, + info_hash: &InfoHash, + update_torrent_form: UpdateTorrentForm, + ) -> Result { self.http_client .put(&format!("/torrent/{info_hash}"), &update_torrent_form) .await - .unwrap() + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn upload_torrent(&self, form: multipart::Form) -> TextResponse { - self.http_client.post_multipart("/torrent/upload", form).await.unwrap() + /// Will panic if the request fails. + pub async fn upload_torrent(&self, form: multipart::Form) -> Result { + self.http_client + .post_multipart("/torrent/upload", form) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn download_torrent(&self, info_hash: &InfoHash) -> responses::BinaryResponse { + /// Will panic if the request fails. + pub async fn download_torrent(&self, info_hash: &InfoHash) -> Result { self.http_client .get_binary(&format!("/torrent/download/{info_hash}"), Query::empty()) .await - .unwrap() + .map_err(Error::from) } // Context: user - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn register_user(&self, registration_form: RegistrationForm) -> TextResponse { - self.http_client.post("/user/register", ®istration_form).await.unwrap() + /// Will panic if the request fails. + pub async fn register_user(&self, registration_form: RegistrationForm) -> Result { + self.http_client + .post("/user/register", ®istration_form) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn login_user(&self, registration_form: LoginForm) -> TextResponse { - self.http_client.post("/user/login", ®istration_form).await.unwrap() + /// Will panic if the request fails. + pub async fn login_user(&self, registration_form: LoginForm) -> Result { + self.http_client + .post("/user/login", ®istration_form) + .await + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> TextResponse { + /// Will panic if the request fails. + pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> Result { self.http_client .post("/user/token/verify", &token_verification_form) .await - .unwrap() + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> TextResponse { + /// Will panic if the request fails. + pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> Result { self.http_client .post("/user/token/renew", &token_verification_form) .await - .unwrap() + .map_err(Error::from) } - /// # Panics + /// # Errors /// - /// Will panic if the request fails. - pub async fn ban_user(&self, username: Username) -> TextResponse { + /// Will panic if the request fails. + pub async fn ban_user(&self, username: Username) -> Result { self.http_client .delete(&format!("/user/ban/{}", &username.value)) .await - .unwrap() + .map_err(Error::from) } } From 3d3fc2dc553e2e4d1f7b833a78d95a7c7ea1b0d0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 17:11:58 +0000 Subject: [PATCH 085/309] chore: add todo to seeder We should handle timeouts errors, that have been added to the API client. --- src/console/commands/seeder/api.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs index 0af8e969..5ddee1d7 100644 --- a/src/console/commands/seeder/api.rs +++ b/src/console/commands/seeder/api.rs @@ -35,6 +35,9 @@ pub async fn upload_torrent(client: &Client, upload_torrent_form: UploadTorrentM add_category(client, &upload_torrent_form.category).await; } + // todo: if we receive timeout error we should retry later. Otherwise we + // have to restart the seeder manually. + let response = client .upload_torrent(upload_torrent_form.into()) .await From 47f52424fa494ee0f04b5d0709fb351718d68464 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 17:18:07 +0000 Subject: [PATCH 086/309] chore: fix comment --- src/web/api/client/v1/client.rs | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index e3f83a86..99069a19 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -57,14 +57,14 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn about(&self) -> Result { self.http_client.get("/about", Query::empty()).await.map_err(Error::from) } /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn license(&self) -> Result { self.http_client .get("/about/license", Query::empty()) @@ -76,14 +76,14 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_categories(&self) -> Result { self.http_client.get("/category", Query::empty()).await.map_err(Error::from) } /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn add_category(&self, add_category_form: AddCategoryForm) -> Result { self.http_client .post("/category", &add_category_form) @@ -93,7 +93,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> Result { self.http_client .delete_with_body("/category", &delete_category_form) @@ -105,21 +105,21 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_tags(&self) -> Result { self.http_client.get("/tags", Query::empty()).await.map_err(Error::from) } /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn add_tag(&self, add_tag_form: AddTagForm) -> Result { self.http_client.post("/tag", &add_tag_form).await.map_err(Error::from) } /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> Result { self.http_client .delete_with_body("/tag", &delete_tag_form) @@ -131,7 +131,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn root(&self) -> Result { self.http_client.get("", Query::empty()).await.map_err(Error::from) } @@ -140,7 +140,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_public_settings(&self) -> Result { self.http_client .get("/settings/public", Query::empty()) @@ -150,7 +150,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_site_name(&self) -> Result { self.http_client .get("/settings/name", Query::empty()) @@ -160,7 +160,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_settings(&self) -> Result { self.http_client.get("/settings", Query::empty()).await.map_err(Error::from) } @@ -169,14 +169,14 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_torrents(&self, params: Query) -> Result { self.http_client.get("/torrents", params).await.map_err(Error::from) } /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn get_torrent(&self, info_hash: &InfoHash) -> Result { self.http_client .get(&format!("/torrent/{info_hash}"), Query::empty()) @@ -186,7 +186,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn delete_torrent(&self, info_hash: &InfoHash) -> Result { self.http_client .delete(&format!("/torrent/{info_hash}")) @@ -196,7 +196,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn update_torrent( &self, info_hash: &InfoHash, @@ -210,7 +210,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn upload_torrent(&self, form: multipart::Form) -> Result { self.http_client .post_multipart("/torrent/upload", form) @@ -220,7 +220,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn download_torrent(&self, info_hash: &InfoHash) -> Result { self.http_client .get_binary(&format!("/torrent/download/{info_hash}"), Query::empty()) @@ -232,7 +232,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn register_user(&self, registration_form: RegistrationForm) -> Result { self.http_client .post("/user/register", ®istration_form) @@ -242,7 +242,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn login_user(&self, registration_form: LoginForm) -> Result { self.http_client .post("/user/login", ®istration_form) @@ -252,7 +252,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> Result { self.http_client .post("/user/token/verify", &token_verification_form) @@ -262,7 +262,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> Result { self.http_client .post("/user/token/renew", &token_verification_form) @@ -272,7 +272,7 @@ impl Client { /// # Errors /// - /// Will panic if the request fails. + /// Will return an error if the request fails. pub async fn ban_user(&self, username: Username) -> Result { self.http_client .delete(&format!("/user/ban/{}", &username.value)) From 7256b4645b3b7fb92661a3b715742a97ad4b7132 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 11:13:04 +0000 Subject: [PATCH 087/309] feat: [#473] add timeout to Tracker API Client requests Default timeout of 5 seconds. --- src/tracker/api.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tracker/api.rs b/src/tracker/api.rs index f64430b7..39a7b42b 100644 --- a/src/tracker/api.rs +++ b/src/tracker/api.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use reqwest::{Error, Response}; pub struct ConnectionInfo { /// The URL of the tracker. Eg: or @@ -15,6 +17,7 @@ impl ConnectionInfo { pub struct Client { pub connection_info: ConnectionInfo, + timeout: Duration, base_url: String, } @@ -24,6 +27,7 @@ impl Client { let base_url = format!("{}/api/v1", connection_info.url); Self { connection_info, + timeout: Duration::from_secs(5), base_url, } } @@ -36,7 +40,7 @@ impl Client { pub async fn whitelist_torrent(&self, info_hash: &str) -> Result { let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); - let client = reqwest::Client::new(); + let client = reqwest::Client::builder().timeout(self.timeout).build()?; let params = [("token", &self.connection_info.token)]; @@ -51,7 +55,7 @@ impl Client { pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Result { let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); - let client = reqwest::Client::new(); + let client = reqwest::Client::builder().timeout(self.timeout).build()?; let params = [("token", &self.connection_info.token)]; @@ -66,7 +70,7 @@ impl Client { pub async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result { let request_url = format!("{}/key/{}", self.base_url, token_valid_seconds); - let client = reqwest::Client::new(); + let client = reqwest::Client::builder().timeout(self.timeout).build()?; let params = [("token", &self.connection_info.token)]; @@ -81,7 +85,7 @@ impl Client { pub async fn get_torrent_info(&self, info_hash: &str) -> Result { let request_url = format!("{}/torrent/{}", self.base_url, info_hash); - let client = reqwest::Client::new(); + let client = reqwest::Client::builder().timeout(self.timeout).build()?; let params = [("token", &self.connection_info.token)]; From 3f629c283d624dfea72af4ba69b3ab815f5d1c3c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 12:11:59 +0000 Subject: [PATCH 088/309] refactor: [#473] tracker API client. Remove duplicate code --- src/tracker/api.rs | 57 ++++++++++++++++++------------------------ src/tracker/service.rs | 6 ++++- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/tracker/api.rs b/src/tracker/api.rs index 39a7b42b..d3c00188 100644 --- a/src/tracker/api.rs +++ b/src/tracker/api.rs @@ -2,7 +2,7 @@ use std::time::Duration; use reqwest::{Error, Response}; pub struct ConnectionInfo { - /// The URL of the tracker. Eg: or + /// The URL of the tracker API. Eg: . pub url: String, /// The token used to authenticate with the tracker API. pub token: String, @@ -17,19 +17,26 @@ impl ConnectionInfo { pub struct Client { pub connection_info: ConnectionInfo, - timeout: Duration, - base_url: String, + api_base_url: String, + client: reqwest::Client, + token_param: [(String, String); 1], } impl Client { - #[must_use] - pub fn new(connection_info: ConnectionInfo) -> Self { + /// # Errors + /// + /// Will fails if it can't build a HTTP client with a timeout. + pub fn new(connection_info: ConnectionInfo) -> Result { let base_url = format!("{}/api/v1", connection_info.url); - Self { + let client = reqwest::Client::builder().timeout(Duration::from_secs(5)).build()?; + let token_param = [("token".to_string(), connection_info.token.to_string())]; + + Ok(Self { connection_info, - timeout: Duration::from_secs(5), - base_url, - } + api_base_url: base_url, + client, + token_param, + }) } /// Add a torrent to the tracker whitelist. @@ -38,13 +45,9 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn whitelist_torrent(&self, info_hash: &str) -> Result { - let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); - - let client = reqwest::Client::builder().timeout(self.timeout).build()?; - - let params = [("token", &self.connection_info.token)]; + let request_url = format!("{}/whitelist/{}", self.api_base_url, info_hash); - client.post(request_url).query(¶ms).send().await + self.client.post(request_url).query(&self.token_param).send().await } /// Remove a torrent from the tracker whitelist. @@ -53,13 +56,9 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Result { - let request_url = format!("{}/whitelist/{}", self.base_url, info_hash); + let request_url = format!("{}/whitelist/{}", self.api_base_url, info_hash); - let client = reqwest::Client::builder().timeout(self.timeout).build()?; - - let params = [("token", &self.connection_info.token)]; - - client.delete(request_url).query(¶ms).send().await + self.client.delete(request_url).query(&self.token_param).send().await } /// Retrieve a new tracker key. @@ -68,13 +67,9 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result { - let request_url = format!("{}/key/{}", self.base_url, token_valid_seconds); - - let client = reqwest::Client::builder().timeout(self.timeout).build()?; + let request_url = format!("{}/key/{}", self.api_base_url, token_valid_seconds); - let params = [("token", &self.connection_info.token)]; - - client.post(request_url).query(¶ms).send().await + self.client.post(request_url).query(&self.token_param).send().await } /// Retrieve the info for a torrent. @@ -83,12 +78,8 @@ impl Client { /// /// Will return an error if the HTTP request fails. pub async fn get_torrent_info(&self, info_hash: &str) -> Result { - let request_url = format!("{}/torrent/{}", self.base_url, info_hash); - - let client = reqwest::Client::builder().timeout(self.timeout).build()?; - - let params = [("token", &self.connection_info.token)]; + let request_url = format!("{}/torrent/{}", self.api_base_url, info_hash); - client.get(request_url).query(¶ms).send().await + self.client.get(request_url).query(&self.token_param).send().await } } diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 265109d2..64a76602 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -73,12 +73,16 @@ pub struct Service { } impl Service { + /// # Panics + /// + /// Will panic if it can't build a Tracker API client. pub async fn new(cfg: Arc, database: Arc>) -> Service { let settings = cfg.settings.read().await; let api_client = Client::new(ConnectionInfo::new( settings.tracker.api_url.clone(), settings.tracker.token.clone(), - )); + )) + .expect("a reqwest client should be provided"); let token_valid_seconds = settings.tracker.token_valid_seconds; let tracker_url = settings.tracker.url.clone(); drop(settings); From df50b91cef95fcf7e9959b26144c6e15218656e6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 12:43:03 +0000 Subject: [PATCH 089/309] ci: run coverage report only in coverage workflow --- .github/workflows/coverage.yaml | 8 ++++++-- .github/workflows/testing.yaml | 11 ----------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index f5f3a708..779162ce 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -68,8 +68,12 @@ jobs: name: Run Unit Tests run: cargo test --tests --benches --examples --workspace --all-targets --all-features - - id: coverage - name: Generate Coverage Report + - id: coverage-llvm + name: Generate Coverage Report with LLVM + run: cargo llvm-cov nextest --tests --benches --examples --workspace --all-targets --all-features + + - id: coverage-grcov + name: Generate Coverage Report with grcov uses: alekitto/grcov@v0.2 - id: upload diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 21a11b23..075ae057 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -111,12 +111,6 @@ jobs: name: Make Build Clean run: cargo clean - - id: tools - name: Install Tools - uses: taiki-e/install-action@v2 - with: - tool: cargo-llvm-cov, cargo-nextest - - id: imdl name: Install Intermodal run: cargo install imdl @@ -129,10 +123,6 @@ jobs: name: Run Unit Tests run: cargo test --tests --benches --examples --workspace --all-targets --all-features - - id: coverage - name: Generate Coverage Report - run: cargo llvm-cov nextest --tests --benches --examples --workspace --all-targets --all-features - integration: name: Integrations runs-on: ubuntu-latest @@ -152,7 +142,6 @@ jobs: uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} - components: llvm-tools-preview - id: cache name: Enable Job Cache From bc003e45a70ac78d24d0159158f492d186c5b167 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 13:26:33 +0000 Subject: [PATCH 090/309] feat: [#468] improve tracker stats importer logs New format using a "target" ("TrackerStats Importer") and more info like the time spent and tracker URL. ```2024-02-12T13:32:50.123422513+00:00 [Tracker Stats Importer][INFO] Importing 2 torrents statistics from tracker udp://localhost:6969 ... 2024-02-12T13:32:50.123461943+00:00 [Tracker Stats Importer][INFO] Importing torrent #1 ... 2024-02-12T13:32:50.242154360+00:00 [Tracker Stats Importer][INFO] Importing torrent #2 ... 2024-02-12T13:32:50.243383523+00:00 [Tracker Stats Importer][INFO] Statistics import completed in 119.92ms ``` --- src/tracker/statistics_importer.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index 91c649df..996008f3 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -1,11 +1,15 @@ use std::sync::Arc; +use std::time::Instant; use log::{error, info}; +use text_colorizer::Colorize; use super::service::{Service, TorrentInfo, TrackerAPIError}; use crate::config::Configuration; use crate::databases::database::{self, Database}; +const LOG_TARGET: &str = "Tracker Stats Importer"; + pub struct StatisticsImporter { database: Arc>, tracker_service: Arc, @@ -30,11 +34,15 @@ impl StatisticsImporter { /// /// Will return an error if the database query failed. pub async fn import_all_torrents_statistics(&self) -> Result<(), database::Error> { - info!("Importing torrents statistics from tracker ..."); let torrents = self.database.get_all_torrents_compact().await?; + info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); + + // Start the timer before the loop + let start_time = Instant::now(); + for torrent in torrents { - info!("Updating torrent {} ...", torrent.torrent_id); + info!(target: LOG_TARGET, "Importing torrent #{} ...", torrent.torrent_id.to_string().yellow()); let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; @@ -49,6 +57,10 @@ impl StatisticsImporter { } } + let elapsed_time = start_time.elapsed(); + + info!(target: LOG_TARGET, "Statistics import completed in {:.2?}", elapsed_time); + Ok(()) } From c1e01f80db1d29b5dc6a0c380132042bdc39dc83 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 14:20:45 +0000 Subject: [PATCH 091/309] chore: [#468] add cargo dependencies for tracing - tower-http = { version = "0", features = ["compression-full", "cors", "trace"] } - trace = "0.1.7" - tracing = "0.1.40" They will be use to add logs to HTTP API requests. --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0bf1996b..ca1502d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3149,6 +3149,8 @@ dependencies = [ "toml", "torrust-index-located-error", "tower-http", + "trace", + "tracing", "urlencoding", "uuid", "which", @@ -3197,6 +3199,7 @@ dependencies = [ "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -3211,6 +3214,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "trace" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ad0c048e114d19d1140662762bfdb10682f3bc806d8be18af846600214dd9af" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tracing" version = "0.1.40" diff --git a/Cargo.toml b/Cargo.toml index e2be04f2..ac5e9a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,9 @@ thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } -tower-http = { version = "0", features = ["compression-full", "cors"] } +tower-http = { version = "0", features = ["compression-full", "cors", "trace"] } +trace = "0.1.7" +tracing = "0.1.40" urlencoding = "2" uuid = { version = "1", features = ["v4"] } From e5ebcc0c188fc6769cfe5624af3e1895c0932381 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 14:23:04 +0000 Subject: [PATCH 092/309] feat: [#468] deafult logging for API requests This change enables the default logging for HTTP requests to the API: ``` 2024-02-12T14:15:15.546548923+00:00 [tower_http::trace::make_span][INFO] request; method=GET uri=/v1/about version=HTTP/1.1 2024-02-12T14:15:15.546840151+00:00 [tower_http::trace::on_response][INFO] finished processing request latency=0 ms status=200 2024-02-12T14:15:34.754336133+00:00 [tower_http::trace::make_span][INFO] request; method=GET uri=/v1/about/license version=HTTP/1.1 2024-02-12T14:15:34.754544571+00:00 [tower_http::trace::on_response][INFO] finished processing request latency=0 ms status=200 ``` --- src/web/api/server/v1/routes.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index 9f33c816..76be1b14 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -9,6 +9,8 @@ use axum::{Json, Router}; use serde_json::{json, Value}; use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; +use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}; +use tracing::Level; use super::contexts::{about, category, proxy, settings, tag, torrent, user}; use crate::bootstrap::config::ENV_VAR_CORS_PERMISSIVE; @@ -37,7 +39,7 @@ pub fn router(app_data: Arc) -> Router { let router = Router::new() .route("/", get(redirect_to_about)) - .route("/health_check", get(health_check_handler).with_state(app_data)) + .route("/health_check", get(health_check_handler).with_state(app_data.clone())) .nest(&format!("/{API_VERSION_URL_PREFIX}"), v1_api_routes); let router = if env::var(ENV_VAR_CORS_PERMISSIVE).is_ok() { @@ -46,7 +48,15 @@ pub fn router(app_data: Arc) -> Router { router }; - router.layer(DefaultBodyLimit::max(10_485_760)).layer(CompressionLayer::new()) + router + .layer(DefaultBodyLimit::max(10_485_760)) + .layer(CompressionLayer::new()) + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) + .on_request(DefaultOnRequest::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)), + ) } /// Endpoint for container health check. From 3ec28bab1ae8401bd76831e44088bcce84db1b22 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 17:01:09 +0000 Subject: [PATCH 093/309] chore: [#468] add new features to propagate ID in requests Add new features to `tower-http` crate to propagate the request ID to the response. If the request does not have an ID it sets a newly generated UUID. ``` $ curl -i localhost:3001/v1/about/license HTTP/1.1 200 OK content-type: text/html; charset=utf-8 content-length: 1262 x-request-id: cea0efcd-84e0-4f59-96b3-625ca4154396 date: Mon, 12 Feb 2024 16:58:05 GMT ``` ``` $ curl -i -H "x-request-id: a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3" localhost:3001/v1/about/license HTTP/1.1 200 OK content-type: text/html; charset=utf-8 content-length: 1262 x-request-id: a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3 date: Mon, 12 Feb 2024 16:59:31 GMT ``` This commit onlu adds the needed features. --- Cargo.lock | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ca1502d3..2a6f1768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,6 +3200,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ac5e9a8e..4d7b381d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } -tower-http = { version = "0", features = ["compression-full", "cors", "trace"] } +tower-http = { version = "0", features = ["compression-full", "cors", "trace", "propagate-header", "request-id"] } trace = "0.1.7" tracing = "0.1.40" urlencoding = "2" From db6a62ff2883a05d396de9c6d9f43f7ca046105a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 17:04:11 +0000 Subject: [PATCH 094/309] chore: [#468] set and propagate request ID It propagates the request ID to the response. If the request does not have an ID it sets a newly generated UUID. Without providing ID: ``` $ curl -i localhost:3001/v1/about/license HTTP/1.1 200 OK content-type: text/html; charset=utf-8 content-length: 1262 x-request-id: cea0efcd-84e0-4f59-96b3-625ca4154396 date: Mon, 12 Feb 2024 16:58:05 GMT ``` Providing ID: ``` $ curl -i -H "x-request-id: a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3" localhost:3001/v1/about/license HTTP/1.1 200 OK content-type: text/html; charset=utf-8 content-length: 1262 x-request-id: a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3 date: Mon, 12 Feb 2024 16:59:31 GMT ``` The response contains the provided ID in the request. --- src/web/api/server/v1/routes.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index 76be1b14..b9150870 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -3,14 +3,19 @@ use std::env; use std::sync::Arc; use axum::extract::DefaultBodyLimit; +use axum::http::{HeaderName, HeaderValue}; use axum::response::Redirect; use axum::routing::get; use axum::{Json, Router}; +use hyper::Request; use serde_json::{json, Value}; use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; +use tower_http::propagate_header::PropagateHeaderLayer; +use tower_http::request_id::{MakeRequestId, RequestId, SetRequestIdLayer}; use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}; use tracing::Level; +use uuid::Uuid; use super::contexts::{about, category, proxy, settings, tag, torrent, user}; use crate::bootstrap::config::ENV_VAR_CORS_PERMISSIVE; @@ -57,6 +62,8 @@ pub fn router(app_data: Arc) -> Router { .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().level(Level::INFO)), ) + .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) + .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) } /// Endpoint for container health check. @@ -67,3 +74,13 @@ async fn health_check_handler() -> Json { async fn redirect_to_about() -> Redirect { Redirect::permanent(&format!("/{API_VERSION_URL_PREFIX}/about")) } + +#[derive(Clone, Default)] +struct RequestIdGenerator; + +impl MakeRequestId for RequestIdGenerator { + fn make_request_id(&mut self, _request: &Request) -> Option { + let id = HeaderValue::from_str(&Uuid::new_v4().to_string()).expect("UUID is a valid HTTP header value"); + Some(RequestId::new(id)) + } +} From 91f818e57d2271769c9832db5e026b2a33a30c3a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 18:20:58 +0000 Subject: [PATCH 095/309] feat: [#468] add request id to response log ``` 2024-02-12T18:20:13.114426999+00:00 [API][INFO] finished processing request; latency=0 status=200 OK request_id=a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3 ``:Wq --- src/web/api/server/v1/routes.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index b9150870..9ac6459a 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -1,10 +1,11 @@ //! Route initialization for the v1 API. use std::env; use std::sync::Arc; +use std::time::Duration; use axum::extract::DefaultBodyLimit; use axum::http::{HeaderName, HeaderValue}; -use axum::response::Redirect; +use axum::response::{Redirect, Response}; use axum::routing::get; use axum::{Json, Router}; use hyper::Request; @@ -13,8 +14,8 @@ use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; use tower_http::propagate_header::PropagateHeaderLayer; use tower_http::request_id::{MakeRequestId, RequestId, SetRequestIdLayer}; -use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}; -use tracing::Level; +use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, TraceLayer}; +use tracing::{Level, Span}; use uuid::Uuid; use super::contexts::{about, category, proxy, settings, tag, torrent, user}; @@ -56,14 +57,26 @@ pub fn router(app_data: Arc) -> Router { router .layer(DefaultBodyLimit::max(10_485_760)) .layer(CompressionLayer::new()) + .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) + .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) .on_request(DefaultOnRequest::new().level(Level::INFO)) - .on_response(DefaultOnResponse::new().level(Level::INFO)), + .on_response(|response: &Response, latency: Duration, _span: &Span| { + let status_code = response.status(); + let request_id = response + .headers() + .get("x-request-id") + .map(|v| v.to_str().unwrap_or_default()) + .unwrap_or_default(); + let latency_ms = latency.as_millis(); + + tracing::span!( + target: "API", + tracing::Level::INFO, "finished processing request", latency = %latency_ms, status = %status_code, request_id = %request_id); + }), ) - .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) - .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) } /// Endpoint for container health check. From a206737817cd82932398f159c131bcd6a1eebd74 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 18:24:44 +0000 Subject: [PATCH 096/309] feat: [#468] add custom request log ``` 2024-02-12T18:24:13.404193511+00:00 [API][INFO] request; method=GET uri=/v1/about/license request_id=a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3 2024-02-12T18:24:13.404380120+00:00 [API][INFO] response; latency=0 status=200 OK request_id=a5030c7a-5302-49d4-8e66-f0f0ab0e3ce3 ``` Add - request id - uri - method --- src/web/api/server/v1/routes.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index 9ac6459a..a4d99843 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -14,7 +14,7 @@ use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; use tower_http::propagate_header::PropagateHeaderLayer; use tower_http::request_id::{MakeRequestId, RequestId, SetRequestIdLayer}; -use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, TraceLayer}; +use tower_http::trace::{DefaultMakeSpan, TraceLayer}; use tracing::{Level, Span}; use uuid::Uuid; @@ -62,7 +62,19 @@ pub fn router(app_data: Arc) -> Router { .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) - .on_request(DefaultOnRequest::new().level(Level::INFO)) + .on_request(|request: &Request, _span: &Span| { + let method = request.method().to_string(); + let uri = request.uri().to_string(); + let request_id = request + .headers() + .get("x-request-id") + .map(|v| v.to_str().unwrap_or_default()) + .unwrap_or_default(); + + tracing::span!( + target: "API", + tracing::Level::INFO, "request", method = %method, uri = %uri, request_id = %request_id); + }) .on_response(|response: &Response, latency: Duration, _span: &Span| { let status_code = response.status(); let request_id = response @@ -74,7 +86,7 @@ pub fn router(app_data: Arc) -> Router { tracing::span!( target: "API", - tracing::Level::INFO, "finished processing request", latency = %latency_ms, status = %status_code, request_id = %request_id); + tracing::Level::INFO, "response", latency = %latency_ms, status = %status_code, request_id = %request_id); }), ) } From 2eaeba8ddd77a6107cb2c2e7a3ee6b3188ea04bf Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 18:44:32 +0000 Subject: [PATCH 097/309] fix: [#468] mising request ID when not provided by client When the client does not provide a request ID it generates one and it's also inclueded in the logs. This commtis fixes the missing request id in the logs for this case. Output without this patch: ``` curl -i localhost:3001/v1/about/license 2024-02-12T18:34:30.331362304+00:00 [API][INFO] request; method=GET uri=/v1/about/license request_id= 2024-02-12T18:34:30.331575353+00:00 [API][INFO] response; latency=0 status=200 OK request_id= ``` --- src/web/api/server/v1/routes.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index a4d99843..853a3e11 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -57,8 +57,8 @@ pub fn router(app_data: Arc) -> Router { router .layer(DefaultBodyLimit::max(10_485_760)) .layer(CompressionLayer::new()) - .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) + .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) @@ -89,6 +89,7 @@ pub fn router(app_data: Arc) -> Router { tracing::Level::INFO, "response", latency = %latency_ms, status = %status_code, request_id = %request_id); }), ) + .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) } /// Endpoint for container health check. From 1e9623793ba5c9def7a4132768962a6420446277 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Feb 2024 21:45:37 +0000 Subject: [PATCH 098/309] ci: fix missing tools in coverage workflow Issues introduced in this PR: https://github.com/torrust/torrust-index/pull/484 --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 779162ce..40d0e301 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -54,7 +54,7 @@ jobs: name: Install Tools uses: taiki-e/install-action@v2 with: - tool: grcov + tool: cargo-llvm-cov, cargo-nextest, grcov - id: imdl name: Install Intermodal From 0bab9454a2ea3315b83841f9d7cd788eea8c63ad Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 20 Feb 2024 09:34:38 +0000 Subject: [PATCH 099/309] chore(deps): update dependencies - jsonwebtoken v9.1.0 -> v9.2.0 - openssl v0.10.63 -> v0.10.64 - openssl-sys v0.9.99 -> v0.9.100 --- Cargo.lock | 15 +++++++++------ src/utils/parse_torrent.rs | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a6f1768..bb86f1dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -982,8 +982,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1316,11 +1318,12 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.1.0" +version = "9.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09" +checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ "base64 0.21.5", + "js-sys", "pem", "ring", "serde", @@ -1647,9 +1650,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1679,9 +1682,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "ae94056a791d0e1217d18b6cbdccb02c61e3054fc69893607f4067e3bb0b1fd1" dependencies = [ "cc", "libc", diff --git a/src/utils/parse_torrent.rs b/src/utils/parse_torrent.rs index 69e69011..dd2ffd7e 100644 --- a/src/utils/parse_torrent.rs +++ b/src/utils/parse_torrent.rs @@ -1,7 +1,7 @@ use std::error; use derive_more::{Display, Error}; -use serde::{self, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use serde_bencode::value::Value; use serde_bencode::{de, Error}; use sha1::{Digest, Sha1}; From 61388de3825259e9ddf71b6cbe43fe3027c9c719 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 14 Feb 2024 23:51:20 +0100 Subject: [PATCH 100/309] fix: [#445] refactor handlers with new user id extractor --- .../server/v1/contexts/category/handlers.rs | 16 +++----------- .../server/v1/contexts/settings/handlers.rs | 9 ++------ .../api/server/v1/contexts/tag/handlers.rs | 16 +++----------- .../server/v1/contexts/torrent/handlers.rs | 22 ++++--------------- .../api/server/v1/contexts/user/handlers.rs | 9 ++------ 5 files changed, 14 insertions(+), 58 deletions(-) diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index b73b15c5..34046f6f 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -8,7 +8,7 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddCategoryForm, DeleteCategoryForm}; use super::responses::{added_category, deleted_category}; use crate::common::AppData; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the categories. @@ -43,14 +43,9 @@ pub async fn get_all_handler(State(app_data): State>) -> Response { #[allow(clippy::unused_async)] pub async fn add_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(category_form): extract::Json, ) -> Response { - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.category_service.add_category(&category_form.name, &user_id).await { Ok(_) => added_category(&category_form.name).into_response(), Err(error) => error.into_response(), @@ -68,18 +63,13 @@ pub async fn add_handler( #[allow(clippy::unused_async)] pub async fn delete_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(category_form): extract::Json, ) -> Response { // code-review: why do we need to send the whole category object to delete it? // And we should use the ID instead of the name, because the name could change // or we could add support for multiple languages. - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.category_service.delete_category(&category_form.name, &user_id).await { Ok(()) => deleted_category(&category_form.name).into_response(), Err(error) => error.into_response(), diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index 9c737b42..ef226c3d 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -6,7 +6,7 @@ use axum::extract::State; use axum::response::{IntoResponse, Json, Response}; use crate::common::AppData; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses; /// Get all settings. @@ -16,12 +16,7 @@ use crate::web::api::server::v1::responses; /// This function will return an error if the user does not have permission to /// view all the settings. #[allow(clippy::unused_async)] -pub async fn get_all_handler(State(app_data): State>, Extract(maybe_bearer_token): Extract) -> Response { - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - +pub async fn get_all_handler(State(app_data): State>, ExtractLoggedInUser(user_id): ExtractLoggedInUser,) -> Response { let all_settings = match app_data.settings_service.get_all_masking_secrets(&user_id).await { Ok(all_settings) => all_settings, Err(error) => return error.into_response(), diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index 5ba38249..18c636ef 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -8,7 +8,7 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddTagForm, DeleteTagForm}; use super::responses::{added_tag, deleted_tag}; use crate::common::AppData; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the tags. @@ -43,14 +43,9 @@ pub async fn get_all_handler(State(app_data): State>) -> Response { #[allow(clippy::unused_async)] pub async fn add_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(add_tag_form): extract::Json, ) -> Response { - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.tag_service.add_tag(&add_tag_form.name, &user_id).await { Ok(_) => added_tag(&add_tag_form.name).into_response(), Err(error) => error.into_response(), @@ -68,14 +63,9 @@ pub async fn add_handler( #[allow(clippy::unused_async)] pub async fn delete_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(delete_tag_form): extract::Json, ) -> Response { - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.tag_service.delete_tag(&delete_tag_form.tag_id, &user_id).await { Ok(()) => deleted_tag(delete_tag_form.tag_id).into_response(), Err(error) => error.into_response(), diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index 745a81df..e4a171d2 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -23,6 +23,7 @@ use crate::services::torrent_file::generate_random_torrent; use crate::utils::parse_torrent; use crate::web::api::server::v1::auth::get_optional_logged_in_user; use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; @@ -37,14 +38,9 @@ use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; #[allow(clippy::unused_async)] pub async fn upload_torrent_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, multipart: Multipart, ) -> Response { - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - let add_torrent_form = match build_add_torrent_request_from_payload(multipart).await { Ok(torrent_request) => torrent_request, Err(error) => return error.into_response(), @@ -220,7 +216,7 @@ async fn redirect_to_details_url_using_canonical_info_hash_if_needed( #[allow(clippy::unused_async)] pub async fn update_torrent_info_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, Path(info_hash): Path, extract::Json(update_torrent_info_form): extract::Json, ) -> Response { @@ -228,11 +224,6 @@ pub async fn update_torrent_info_handler( return errors::Request::InvalidInfoHashParam.into_response(); }; - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data .torrent_service .update_torrent_info( @@ -262,18 +253,13 @@ pub async fn update_torrent_info_handler( #[allow(clippy::unused_async)] pub async fn delete_torrent_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { return errors::Request::InvalidInfoHashParam.into_response(); }; - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.torrent_service.delete_torrent(&info_hash, &user_id).await { Ok(deleted_torrent_response) => Json(OkResponseData { data: deleted_torrent_response, diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index ee33d2e0..58326e9d 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -10,7 +10,7 @@ use serde::Deserialize; use super::forms::{JsonWebToken, LoginForm, RegistrationForm}; use super::responses::{self}; use crate::common::AppData; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; // Registration @@ -135,15 +135,10 @@ pub async fn renew_token_handler( pub async fn ban_handler( State(app_data): State>, Path(to_be_banned_username): Path, - Extract(maybe_bearer_token): Extract, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, ) -> Response { // todo: add reason and `date_expiry` parameters to request - let user_id = match app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await { - Ok(user_id) => user_id, - Err(error) => return error.into_response(), - }; - match app_data.ban_service.ban_user(&to_be_banned_username.0, &user_id).await { Ok(()) => Json(OkResponseData { data: format!("Banned user: {}", to_be_banned_username.0), From 21f533fdfc083679eb60a527b35e09017d624811 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 15 Feb 2024 19:07:14 +0100 Subject: [PATCH 101/309] refactor: [#445] fix formatting warning --- src/web/api/server/v1/contexts/settings/handlers.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index ef226c3d..27b4fc04 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -16,7 +16,10 @@ use crate::web::api::server::v1::responses; /// This function will return an error if the user does not have permission to /// view all the settings. #[allow(clippy::unused_async)] -pub async fn get_all_handler(State(app_data): State>, ExtractLoggedInUser(user_id): ExtractLoggedInUser,) -> Response { +pub async fn get_all_handler( + State(app_data): State>, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, +) -> Response { let all_settings = match app_data.settings_service.get_all_masking_secrets(&user_id).await { Ok(all_settings) => all_settings, Err(error) => return error.into_response(), From 8f7b8a68ab46b341e358717be9dc904e9aa1a858 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 21 Feb 2024 16:39:47 +0100 Subject: [PATCH 102/309] feat: [#446] new optional user id extractor --- src/web/api/server/v1/extractors/mod.rs | 1 + .../server/v1/extractors/optional_user_id.rs | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/web/api/server/v1/extractors/optional_user_id.rs diff --git a/src/web/api/server/v1/extractors/mod.rs b/src/web/api/server/v1/extractors/mod.rs index 2c55e042..50806346 100644 --- a/src/web/api/server/v1/extractors/mod.rs +++ b/src/web/api/server/v1/extractors/mod.rs @@ -1,2 +1,3 @@ pub mod bearer_token; pub mod user_id; +pub mod optional_user_id; \ No newline at end of file diff --git a/src/web/api/server/v1/extractors/optional_user_id.rs b/src/web/api/server/v1/extractors/optional_user_id.rs new file mode 100644 index 00000000..a2b757b4 --- /dev/null +++ b/src/web/api/server/v1/extractors/optional_user_id.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use axum::extract::{FromRef, FromRequestParts}; +use axum::http::request::Parts; +use axum::response::{ Response}; + +use crate::common::AppData; +use crate::models::user::UserId; +use crate::web::api::server::v1::extractors::bearer_token; + +pub struct ExtractOptionalLoggedInUser(pub Option); + +#[async_trait] +impl FromRequestParts for ExtractOptionalLoggedInUser + where + Arc: FromRef, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + /* let maybe_bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { + Ok(maybe_bearer_token) => maybe_bearer_token.0, + Err(_) => return Err(ServiceError::TokenNotFound.into_response()), + }; */ + + let bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { + Ok(bearer_token) => bearer_token.0, + Err(_) => None + }; + + //Extracts the app state + let app_data = Arc::from_ref(state); + + match app_data.auth.get_user_id_from_bearer_token(&bearer_token).await { + Ok(user_id) => Ok(ExtractOptionalLoggedInUser(Some(user_id))), + Err(_) => Ok(ExtractOptionalLoggedInUser(None)), + } + } +} From 4208f3a757d85be189ed5cfebcb3289c3e4dc022 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 21 Feb 2024 21:13:18 +0100 Subject: [PATCH 103/309] refactor: [#446] refactored handlers with new extractor --- .../api/server/v1/contexts/torrent/handlers.rs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index e4a171d2..e72151e0 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -21,8 +21,7 @@ use crate::models::torrent_tag::TagId; use crate::services::torrent::{AddTorrentRequest, ListingRequest}; use crate::services::torrent_file::generate_random_torrent; use crate::utils::parse_torrent; -use crate::web::api::server::v1::auth::get_optional_logged_in_user; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; @@ -69,7 +68,7 @@ impl InfoHashParam { #[allow(clippy::unused_async)] pub async fn download_torrent_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { @@ -82,11 +81,6 @@ pub async fn download_torrent_handler( debug!("Redirecting to URL with canonical info-hash"); redirect_response } else { - let opt_user_id = match get_optional_logged_in_user(maybe_bearer_token, app_data.clone()).await { - Ok(opt_user_id) => opt_user_id, - Err(error) => return error.into_response(), - }; - let torrent = match app_data.torrent_service.get_torrent(&info_hash, opt_user_id).await { Ok(torrent) => torrent, Err(error) => return error.into_response(), @@ -156,7 +150,7 @@ pub async fn get_torrents_handler(State(app_data): State>, Query(cr #[allow(clippy::unused_async)] pub async fn get_torrent_info_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { @@ -166,11 +160,6 @@ pub async fn get_torrent_info_handler( if let Some(redirect_response) = redirect_to_details_url_using_canonical_info_hash_if_needed(&app_data, &info_hash).await { redirect_response } else { - let opt_user_id = match get_optional_logged_in_user(maybe_bearer_token, app_data.clone()).await { - Ok(opt_user_id) => opt_user_id, - Err(error) => return error.into_response(), - }; - match app_data.torrent_service.get_torrent_info(&info_hash, opt_user_id).await { Ok(torrent_response) => Json(OkResponseData { data: torrent_response }).into_response(), Err(error) => error.into_response(), From 3607dc94e423b68f34737fbf008a8811486ea15e Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 21 Feb 2024 21:17:15 +0100 Subject: [PATCH 104/309] refactor: [#446] fixed text formatting --- src/web/api/server/v1/extractors/mod.rs | 2 +- .../api/server/v1/extractors/optional_user_id.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/web/api/server/v1/extractors/mod.rs b/src/web/api/server/v1/extractors/mod.rs index 50806346..acf2d689 100644 --- a/src/web/api/server/v1/extractors/mod.rs +++ b/src/web/api/server/v1/extractors/mod.rs @@ -1,3 +1,3 @@ pub mod bearer_token; +pub mod optional_user_id; pub mod user_id; -pub mod optional_user_id; \ No newline at end of file diff --git a/src/web/api/server/v1/extractors/optional_user_id.rs b/src/web/api/server/v1/extractors/optional_user_id.rs index a2b757b4..cdef65af 100644 --- a/src/web/api/server/v1/extractors/optional_user_id.rs +++ b/src/web/api/server/v1/extractors/optional_user_id.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use async_trait::async_trait; use axum::extract::{FromRef, FromRequestParts}; use axum::http::request::Parts; -use axum::response::{ Response}; +use axum::response::Response; use crate::common::AppData; use crate::models::user::UserId; @@ -13,21 +13,21 @@ pub struct ExtractOptionalLoggedInUser(pub Option); #[async_trait] impl FromRequestParts for ExtractOptionalLoggedInUser - where - Arc: FromRef, - S: Send + Sync, +where + Arc: FromRef, + S: Send + Sync, { type Rejection = Response; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - /* let maybe_bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { + /* let maybe_bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { Ok(maybe_bearer_token) => maybe_bearer_token.0, Err(_) => return Err(ServiceError::TokenNotFound.into_response()), }; */ - let bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { - Ok(bearer_token) => bearer_token.0, - Err(_) => None + let bearer_token = match bearer_token::Extract::from_request_parts(parts, state).await { + Ok(bearer_token) => bearer_token.0, + Err(_) => None, }; //Extracts the app state From cfbe2cef33af001adfda0d31bcef0a9b1aac7f0e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 21 Feb 2024 13:00:42 +0000 Subject: [PATCH 105/309] chore(deps): udpate dependencies --- Cargo.lock | 917 ++++++++++-------- Cargo.toml | 3 +- .../cronjobs/tracker_statistics_importer.rs | 11 +- src/tracker/service.rs | 14 +- src/web/api/server/mod.rs | 33 +- src/web/api/server/v1/routes.rs | 19 +- 6 files changed, 554 insertions(+), 443 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb86f1dd..c939b09f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "getrandom", @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", @@ -125,15 +125,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "argon2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", @@ -161,9 +161,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" dependencies = [ "brotli", "flate2", @@ -177,13 +177,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] @@ -195,6 +195,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -203,18 +213,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-util", "itoa", "matchit", "memchr", @@ -232,23 +243,28 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -274,9 +290,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -298,9 +314,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" dependencies = [ "serde", ] @@ -346,9 +362,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -356,15 +372,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" [[package]] name = "byteorder" @@ -380,9 +396,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" +checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" dependencies = [ "libc", ] @@ -395,14 +411,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -411,15 +427,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", "stacker", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -427,9 +443,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -439,21 +455,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -463,11 +479,10 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] @@ -494,9 +509,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" @@ -535,9 +550,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -545,15 +560,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -575,32 +590,47 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] -name = "crossbeam-queue" -version = "0.3.8" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", + "crossbeam-epoch", "crossbeam-utils", ] [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crunchy" version = "0.2.2" @@ -639,9 +669,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -699,9 +729,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" dependencies = [ "serde", ] @@ -712,7 +742,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "memchr", ] @@ -742,12 +772,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -775,9 +805,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] @@ -858,18 +888,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -882,9 +912,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -892,15 +922,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -920,38 +950,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -977,9 +1007,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -990,21 +1020,21 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1029,7 +1059,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", "indexmap", "slab", "tokio", @@ -1045,9 +1094,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -1059,7 +1108,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1073,9 +1122,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -1085,9 +1134,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1103,11 +1152,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1123,9 +1172,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.10" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", @@ -1134,20 +1194,37 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "pin-project-lite", +] [[package]] name = "httparse" @@ -1163,28 +1240,47 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.2", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1192,17 +1288,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2", + "tokio", +] + [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1223,9 +1335,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1233,29 +1345,28 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ + "crossbeam-deque", "globset", - "lazy_static", "log", "memchr", - "regex", + "regex-automata", "same-file", - "thread_local", "walkdir", "winapi-util", ] [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1264,31 +1375,20 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jpeg-decoder" @@ -1298,9 +1398,9 @@ checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1322,7 +1422,7 @@ version = "9.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "js-sys", "pem", "ring", @@ -1351,12 +1451,12 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a466bc111374ccf4d90877dba636924a2185e67e5be4b35d32043199365097b2" +checksum = "357ff5edb6d8326473a64c82cf41ddf78ab116f89668c50c4fac1b321e5e80f4" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.21.7", "chumsky", "email-encoding", "email_address", @@ -1369,11 +1469,11 @@ dependencies = [ "mime", "native-tls", "nom", - "once_cell", + "percent-encoding", "quoted_printable", "rustls", - "rustls-pemfile", - "socket2 0.5.5", + "rustls-pemfile 2.1.0", + "socket2", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1383,9 +1483,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -1395,9 +1495,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -1412,9 +1512,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1462,9 +1562,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1499,9 +1599,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", "simd-adler32", @@ -1509,9 +1609,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -1520,14 +1620,14 @@ dependencies = [ [[package]] name = "multer" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.0.0", "httparse", "log", "memchr", @@ -1554,6 +1654,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1592,21 +1703,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -1615,9 +1731,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -1635,18 +1751,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" @@ -1654,7 +1770,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -1671,7 +1787,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] @@ -1762,11 +1878,11 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "serde", ] @@ -1781,15 +1897,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.5" +version = "2.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" dependencies = [ "memchr", "thiserror", @@ -1798,9 +1914,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" dependencies = [ "pest", "pest_generator", @@ -1808,22 +1924,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" dependencies = [ "once_cell", "pest", @@ -1838,22 +1954,22 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] @@ -1891,15 +2007,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "png" -version = "0.17.10" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -1922,9 +2038,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -1940,9 +2056,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2000,9 +2116,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -2012,9 +2128,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -2029,19 +2145,19 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-tls", "ipnet", "js-sys", @@ -2052,9 +2168,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -2092,16 +2210,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2110,8 +2229,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.4.2", "serde", "serde_derive", ] @@ -2127,9 +2246,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -2172,27 +2291,29 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.8" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] @@ -2201,16 +2322,33 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +dependencies = [ + "base64 0.21.7", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -2238,9 +2376,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "safe_arch" @@ -2262,11 +2400,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2275,16 +2413,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.9.2" @@ -2310,15 +2438,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -2335,29 +2463,29 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -2366,9 +2494,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" dependencies = [ "itoa", "serde", @@ -2376,9 +2504,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -2491,19 +2619,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -2532,9 +2650,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -2542,9 +2660,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ "itertools", "nom", @@ -2553,9 +2671,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2566,9 +2684,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ "ahash", "atoi", @@ -2608,9 +2726,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" dependencies = [ "proc-macro2", "quote", @@ -2621,10 +2739,11 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" dependencies = [ + "atomic-write-file", "dotenvy", "either", "heck", @@ -2647,13 +2766,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.4.2", "byteorder", "bytes", "crc", @@ -2690,13 +2809,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.4.2", "byteorder", "crc", "dotenvy", @@ -2730,9 +2849,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" dependencies = [ "atoi", "flume", @@ -2749,6 +2868,7 @@ dependencies = [ "time", "tracing", "url", + "urlencoding", ] [[package]] @@ -2777,9 +2897,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" @@ -2809,9 +2929,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -2847,15 +2967,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2903,42 +3022,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", + "syn 2.0.50", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -2953,10 +3063,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -3000,9 +3111,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -3011,7 +3122,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -3024,7 +3135,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] @@ -3039,11 +3150,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] @@ -3074,9 +3186,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", @@ -3095,9 +3207,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" dependencies = [ "indexmap", "serde", @@ -3124,7 +3236,8 @@ dependencies = [ "fern", "futures", "hex", - "hyper", + "http 1.0.0", + "hyper 1.1.0", "indexmap", "jsonwebtoken", "lazy_static", @@ -3185,18 +3298,17 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e" dependencies = [ "async-compression", - "bitflags 2.4.1", + "bitflags 2.4.2", "bytes", "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", "pin-project-lite", "tokio", "tokio-util", @@ -3249,7 +3361,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] @@ -3263,9 +3375,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttf-parser" @@ -3346,9 +3458,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-bidi-mirroring" @@ -3376,24 +3488,24 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-script" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-vo" @@ -3415,9 +3527,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3511,9 +3623,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3521,24 +3633,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -3548,9 +3660,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3558,28 +3670,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -3587,9 +3699,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "which" @@ -3643,11 +3758,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -3784,9 +3899,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.19" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" dependencies = [ "memchr", ] @@ -3845,14 +3960,14 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.50", ] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 4d7b381d..d6ab88ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,8 @@ email_address = "0" fern = "0" futures = "0" hex = "0" -hyper = "0" +http = "1.0.0" +hyper = "1" indexmap = "2" jsonwebtoken = "9" lazy_static = "1.4.0" diff --git a/src/console/cronjobs/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs index 4c65c5a0..0d32ba34 100644 --- a/src/console/cronjobs/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -10,6 +10,7 @@ //! The last heartbeat signal time is used to determine whether the cronjob was //! executed successfully or not. The API has a `health_check` endpoint which is //! used when the application is running in containers. +use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use axum::extract::State; @@ -18,6 +19,7 @@ use axum::{Json, Router}; use chrono::{DateTime, Utc}; use log::{error, info}; use serde_json::{json, Value}; +use tokio::net::TcpListener; use tokio::task::JoinHandle; use crate::tracker::statistics_importer::StatisticsImporter; @@ -66,10 +68,13 @@ pub fn start( info!("Tracker statistics importer API server listening on http://{}", addr); // # DevSkim: ignore DS137138 - axum::Server::bind(&addr.parse().unwrap()) - .serve(app.into_make_service()) + let socket_addr: SocketAddr = addr.parse().expect("importer API to have a valid socket address"); + + let listener = TcpListener::bind(socket_addr) .await - .unwrap(); + .expect("importer API TCP listener to bind to socket address"); + + axum::serve(listener, app).await.unwrap(); }); // Start the Importer cronjob diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 64a76602..598e35fd 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -109,7 +109,7 @@ impl Service { match maybe_response { Ok(response) => { - let status: StatusCode = response.status(); + let status: StatusCode = map_status_code(response.status()); let body = response.text().await.map_err(|_| { error!(target: "tracker-service", "response without body"); @@ -151,7 +151,7 @@ impl Service { match maybe_response { Ok(response) => { - let status: StatusCode = response.status(); + let status: StatusCode = map_status_code(response.status()); let body = response.text().await.map_err(|_| { error!(target: "tracker-service", "response without body"); @@ -218,7 +218,7 @@ impl Service { match maybe_response { Ok(response) => { - let status: StatusCode = response.status(); + let status: StatusCode = map_status_code(response.status()); let body = response.text().await.map_err(|_| { error!(target: "tracker-service", "response without body"); @@ -269,7 +269,7 @@ impl Service { match maybe_response { Ok(response) => { - let status: StatusCode = response.status(); + let status: StatusCode = map_status_code(response.status()); let body = response.text().await.map_err(|_| { error!(target: "tracker-service", "response without body"); @@ -322,3 +322,9 @@ impl Service { "\"torrent not known\"".to_string() } } + +/// Temporary patch to map `StatusCode` from crate `http` 0.2.11 to `http` v1.0.0 +/// until `reqwest` upgrades to hyper 1.0. See +fn map_status_code(status: reqwest::StatusCode) -> hyper::StatusCode { + StatusCode::from_u16(status.as_u16()).unwrap() +} diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index 9edda873..e6943e87 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -3,8 +3,8 @@ pub mod v1; use std::net::SocketAddr; use std::sync::Arc; -use futures::Future; use log::info; +use tokio::net::TcpListener; use tokio::sync::oneshot::{self, Sender}; use v1::routes::router; @@ -27,11 +27,9 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni let join_handle = tokio::spawn(async move { info!("Starting API server with net config: {} ...", config_socket_addr); - let handle = start_server(config_socket_addr, app_data.clone(), tx); + start_server(config_socket_addr, app_data.clone(), tx).await; - if let Ok(()) = handle.await { - info!("API server stopped"); - } + info!("API server stopped"); Ok(()) }); @@ -48,12 +46,10 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni } } -fn start_server( - config_socket_addr: SocketAddr, - app_data: Arc, - tx: Sender, -) -> impl Future> { - let tcp_listener = std::net::TcpListener::bind(config_socket_addr).expect("tcp listener to bind to a socket address"); +async fn start_server(config_socket_addr: SocketAddr, app_data: Arc, tx: Sender) { + let tcp_listener = TcpListener::bind(config_socket_addr) + .await + .expect("tcp listener to bind to a socket address"); let bound_addr = tcp_listener .local_addr() @@ -63,15 +59,14 @@ fn start_server( let app = router(app_data); - let server = axum::Server::from_tcp(tcp_listener) - .expect("a new server from the previously created tcp listener.") - .serve(app.into_make_service_with_connect_info::()); - tx.send(ServerStartedMessage { socket_addr: bound_addr }) .expect("the API server should not be dropped"); - server.with_graceful_shutdown(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping API server on http://{} ...", bound_addr); // # DevSkim: ignore DS137138 - }) + axum::serve(tcp_listener, app.into_make_service_with_connect_info::()) + .with_graceful_shutdown(async move { + tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); + info!("Stopping API server on http://{} ...", bound_addr); // # DevSkim: ignore DS137138 + }) + .await + .expect("API server should be running"); } diff --git a/src/web/api/server/v1/routes.rs b/src/web/api/server/v1/routes.rs index 853a3e11..1826427d 100644 --- a/src/web/api/server/v1/routes.rs +++ b/src/web/api/server/v1/routes.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::time::Duration; use axum::extract::DefaultBodyLimit; -use axum::http::{HeaderName, HeaderValue}; +use axum::http::HeaderName; use axum::response::{Redirect, Response}; use axum::routing::get; use axum::{Json, Router}; @@ -13,10 +13,9 @@ use serde_json::{json, Value}; use tower_http::compression::CompressionLayer; use tower_http::cors::CorsLayer; use tower_http::propagate_header::PropagateHeaderLayer; -use tower_http::request_id::{MakeRequestId, RequestId, SetRequestIdLayer}; +use tower_http::request_id::{MakeRequestUuid, SetRequestIdLayer}; use tower_http::trace::{DefaultMakeSpan, TraceLayer}; use tracing::{Level, Span}; -use uuid::Uuid; use super::contexts::{about, category, proxy, settings, tag, torrent, user}; use crate::bootstrap::config::ENV_VAR_CORS_PERMISSIVE; @@ -57,7 +56,7 @@ pub fn router(app_data: Arc) -> Router { router .layer(DefaultBodyLimit::max(10_485_760)) .layer(CompressionLayer::new()) - .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) + .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid)) .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id"))) .layer( TraceLayer::new_for_http() @@ -89,7 +88,7 @@ pub fn router(app_data: Arc) -> Router { tracing::Level::INFO, "response", latency = %latency_ms, status = %status_code, request_id = %request_id); }), ) - .layer(SetRequestIdLayer::x_request_id(RequestIdGenerator)) + .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid)) } /// Endpoint for container health check. @@ -100,13 +99,3 @@ async fn health_check_handler() -> Json { async fn redirect_to_about() -> Redirect { Redirect::permanent(&format!("/{API_VERSION_URL_PREFIX}/about")) } - -#[derive(Clone, Default)] -struct RequestIdGenerator; - -impl MakeRequestId for RequestIdGenerator { - fn make_request_id(&mut self, _request: &Request) -> Option { - let id = HeaderValue::from_str(&Uuid::new_v4().to_string()).expect("UUID is a valid HTTP header value"); - Some(RequestId::new(id)) - } -} From 8a97b5b1d7bfdf34a8972bcc9294c01f101018f2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 22 Feb 2024 16:11:44 +0000 Subject: [PATCH 106/309] feat: [#483] allow upload torrent with application/octet-stream HTTP header Content-Type. Some users are having problems uploading torrents becuase the client (browser or other clients) use the HTTP header Contetnt-Type: `application/octet-stream` instead of: `application/x-bittorrent` It seems that the reason is they don't have any application associated to that extension. So it uses the generic binary file mime type. The MIME type can be inferred from the file extension. If the system or application uploading the file has a specific association for .torrent files, it might set the MIME type to application/x-bittorrent. In the absence of such association, it might fall back to the generic application/octet-stream. --- src/web/api/client/v1/responses.rs | 10 +++++++++- src/web/api/server/v1/contexts/torrent/errors.rs | 4 +++- src/web/api/server/v1/contexts/torrent/handlers.rs | 4 ++-- tests/common/responses.rs | 9 ++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/web/api/client/v1/responses.rs b/src/web/api/client/v1/responses.rs index aae5a338..bacdcba6 100644 --- a/src/web/api/client/v1/responses.rs +++ b/src/web/api/client/v1/responses.rs @@ -71,7 +71,7 @@ impl BinaryResponse { #[must_use] pub fn is_a_bit_torrent_file(&self) -> bool { - self.is_ok() && self.is_bittorrent_content_type() + self.is_ok() && (self.is_bittorrent_content_type() || self.is_octet_stream_content_type()) } #[must_use] @@ -82,6 +82,14 @@ impl BinaryResponse { false } + #[must_use] + pub fn is_octet_stream_content_type(&self) -> bool { + if let Some(content_type) = &self.content_type { + return content_type == "application/octet-stream"; + } + false + } + #[must_use] pub fn is_ok(&self) -> bool { self.status == 200 diff --git a/src/web/api/server/v1/contexts/torrent/errors.rs b/src/web/api/server/v1/contexts/torrent/errors.rs index 87166419..2c18bbb2 100644 --- a/src/web/api/server/v1/contexts/torrent/errors.rs +++ b/src/web/api/server/v1/contexts/torrent/errors.rs @@ -21,7 +21,9 @@ pub enum Request { #[display(fmt = "torrent tags string is not a valid JSON.")] TagsArrayIsNotValidJson, - #[display(fmt = "upload torrent request header `content-type` should be `application/x-bittorrent`.")] + #[display( + fmt = "upload torrent request header `content-type` should be preferably `application/x-bittorrent` or `application/octet-stream`." + )] InvalidFileType, #[display(fmt = "cannot write uploaded torrent bytes (binary file) into memory.")] diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index e72151e0..ddba8df0 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -301,7 +301,7 @@ pub async fn create_random_torrent_handler(State(_app_data): State> /// /// - The text fields do not contain a valid UTF8 string. /// - The torrent file data is not valid because: -/// - The content type is not `application/x-bittorrent`. +/// - The content type is not `application/x-bittorrent` or `application/octet-stream`. /// - The multipart content is invalid. /// - The torrent file pieces key has a length that is not a multiple of 20. /// - The binary data cannot be decoded as a torrent file. @@ -350,7 +350,7 @@ async fn build_add_torrent_request_from_payload(mut payload: Multipart) -> Resul "torrent" => { let content_type = field.content_type().unwrap(); - if content_type != "application/x-bittorrent" { + if content_type != "application/x-bittorrent" && content_type != "application/octet-stream" { return Err(errors::Request::InvalidFileType); } diff --git a/tests/common/responses.rs b/tests/common/responses.rs index 2545a9e8..0d9388e2 100644 --- a/tests/common/responses.rs +++ b/tests/common/responses.rs @@ -54,7 +54,7 @@ impl BinaryResponse { } } pub fn is_a_bit_torrent_file(&self) -> bool { - self.is_ok() && self.is_bittorrent_content_type() + self.is_ok() && (self.is_bittorrent_content_type() || self.is_octet_stream_content_type()) } pub fn is_bittorrent_content_type(&self) -> bool { @@ -64,6 +64,13 @@ impl BinaryResponse { false } + pub fn is_octet_stream_content_type(&self) -> bool { + if let Some(content_type) = &self.content_type { + return content_type == "application/octet-stream"; + } + false + } + pub fn is_ok(&self) -> bool { self.status == 200 } From 63866f3cc8be51cac34dd592d15d3d5cf62eeeff Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 22 Feb 2024 16:47:35 +0000 Subject: [PATCH 107/309] chore(deps): udpate dependencies - Bump `openssl-sys` from `0.0.100` to `0.9.101` - Bump `bumpalo` from `3.15.1` to `3.15.2` - Bump `hyper` from `1.1.0` to `1.2.0` --- Cargo.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c939b09f..561fa1f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ dependencies = [ "http 1.0.0", "http-body 1.0.0", "http-body-util", - "hyper 1.1.0", + "hyper 1.2.0", "hyper-util", "itoa", "matchit", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.1" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" +checksum = "a3b1be7772ee4501dba05acbe66bb1e8760f6a6c474a36035631638e4415f130" [[package]] name = "bytemuck" @@ -1264,9 +1264,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", @@ -1278,6 +1278,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "smallvec", "tokio", ] @@ -1304,7 +1305,7 @@ dependencies = [ "futures-util", "http 1.0.0", "http-body 1.0.0", - "hyper 1.1.0", + "hyper 1.2.0", "pin-project-lite", "socket2", "tokio", @@ -1798,9 +1799,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.100" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae94056a791d0e1217d18b6cbdccb02c61e3054fc69893607f4067e3bb0b1fd1" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -3237,7 +3238,7 @@ dependencies = [ "futures", "hex", "http 1.0.0", - "hyper 1.1.0", + "hyper 1.2.0", "indexmap", "jsonwebtoken", "lazy_static", From b41488b194bba2f738ebdde7d60a7fee1b5b339a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 10:12:31 +0000 Subject: [PATCH 108/309] fix: [#488] torrent without tracker keys for open tracker Tracker HTTP URL: http://localhost:7070 - announce: "http://localhost:7070". - announce_list: "http://localhost:7070" and keeps the original URLs in the uploaded torrent. Tracker UDP URL: udp://localhost:6969 - announce: "udp://localhost:7070". - announce_list: "udp://localhost:7070" and keeps the original URLs in the uploaded torrent. Tracker HTTP URL: http://localhost:7070 - announce: "http://localhost:7070/**KEY**". - announce_list: "http://localhost:7070/**KEY**" and keeps the original URLs in the uploaded torrent. Tracker UDP URL: udp://localhost:6969 - announce: "udp://localhost:7070/**KEY**". - announce_list: "udp://localhost:7070/**KEY**" and keeps the original URLs in the uploaded torrent. It returns an 505 error if it can't get the user's tracker keys. TODO: - The application should not start with close tracker and UDP url in the configuration. - The API should return 503 instead of 500 when it can't connect to the tracker. --- src/config.rs | 7 ++ src/models/response.rs | 9 +++ src/models/torrent_file.rs | 34 +++++++++ src/services/torrent.rs | 74 +++++++++++-------- .../web/api/v1/contexts/torrent/contract.rs | 45 ----------- 5 files changed, 92 insertions(+), 77 deletions(-) diff --git a/src/config.rs b/src/config.rs index b0ad49fc..d655eaf1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -144,6 +144,13 @@ impl Default for TrackerMode { } } +impl TrackerMode { + #[must_use] + pub fn is_open(&self) -> bool { + matches!(self, TrackerMode::Public | TrackerMode::Whitelisted) + } +} + /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Tracker { diff --git a/src/models/response.rs b/src/models/response.rs index e82acb45..9932b09f 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -93,6 +93,15 @@ impl TorrentResponse { encoding: torrent_listing.encoding, } } + + /// It adds the tracker URL in the first position of the tracker list. + pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) { + // Remove any existing instances of tracker_url + self.trackers.retain(|tracker| tracker != tracker_url); + + // Insert tracker_url at the first position + self.trackers.insert(0, tracker_url.to_owned()); + } } #[allow(clippy::module_name_repetitions)] diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index ebbd1534..35bb3a6a 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -93,11 +93,45 @@ impl Torrent { } } + /// Includes the tracker URL a the main tracker in the torrent. + /// + /// It will be the URL in the `announce` field and also the first URL in the + /// `announce_list`. + pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) { + self.set_announce_to(tracker_url); + self.add_url_to_front_of_announce_list(tracker_url); + } + /// Sets the announce url to the tracker url. pub fn set_announce_to(&mut self, tracker_url: &str) { self.announce = Some(tracker_url.to_owned()); } + /// Adds a new tracker URL to the front of the `announce_list`, removes duplicates, + /// and cleans up any empty inner lists. + /// + /// In practice, it's common for the `announce_list` to include the URL from + /// the `announce` field as one of its entries, often in the first tier, + /// to ensure that this primary tracker is always used. However, this is not + /// a strict requirement of the `BitTorrent` protocol; it's more of a + /// convention followed by some torrent creators for redundancy and to + /// ensure better availability of trackers. + pub fn add_url_to_front_of_announce_list(&mut self, tracker_url: &str) { + if let Some(list) = &mut self.announce_list { + // Remove the tracker URL from existing lists + for inner_list in list.iter_mut() { + inner_list.retain(|url| url != tracker_url); + } + + // Prepend a new vector containing the tracker_url + let vec = vec![tracker_url.to_owned()]; + list.insert(0, vec); + + // Remove any empty inner lists + list.retain(|inner_list| !inner_list.is_empty()); + } + } + /// Removes all other trackers if the torrent is private. pub fn reset_announce_list_if_private(&mut self) { if self.is_private() { diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 73eef75d..73a6e38e 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -6,7 +6,7 @@ use serde_derive::{Deserialize, Serialize}; use super::category::DbCategoryRepository; use super::user::DbUserRepository; -use crate::config::Configuration; +use crate::config::{Configuration, TrackerMode}; use crate::databases::database::{Database, Error, Sorting}; use crate::errors::ServiceError; use crate::models::category::CategoryId; @@ -257,24 +257,18 @@ impl Index { let mut torrent = self.torrent_repository.get_by_info_hash(info_hash).await?; let tracker_url = self.get_tracker_url().await; + let tracker_mode = self.get_tracker_mode().await; - // Add personal tracker url or default tracker url - match opt_user_id { - Some(user_id) => { - let personal_announce_url = self - .tracker_service - .get_personal_announce_url(user_id) - .await - .unwrap_or(tracker_url); - torrent.announce = Some(personal_announce_url.clone()); - if let Some(list) = &mut torrent.announce_list { - let vec = vec![personal_announce_url]; - list.insert(0, vec); - } - } - None => { - torrent.announce = Some(tracker_url); - } + // code-review: should we remove all tracker URLs in the `announce_list` + // when the tracker is not open? + + if tracker_mode.is_open() { + torrent.include_url_as_main_tracker(&tracker_url); + } else if let Some(authenticated_user_id) = opt_user_id { + let personal_announce_url = self.tracker_service.get_personal_announce_url(authenticated_user_id).await?; + torrent.include_url_as_main_tracker(&personal_announce_url); + } else { + torrent.include_url_as_main_tracker(&tracker_url); } Ok(torrent) @@ -358,24 +352,35 @@ impl Index { // Add trackers + // code-review: duplicate logic. We have to check the same in the + // download torrent file endpoint. Here he have only one list of tracker + // like the `announce_list` in the torrent file. + torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?; let tracker_url = self.get_tracker_url().await; + let tracker_mode = self.get_tracker_mode().await; - // add tracker url - match opt_user_id { - Some(user_id) => { - // if no user owned tracker key can be found, use default tracker url - let personal_announce_url = self - .tracker_service - .get_personal_announce_url(user_id) - .await - .unwrap_or(tracker_url); - // add personal tracker url to front of vec - torrent_response.trackers.insert(0, personal_announce_url); - } - None => { - torrent_response.trackers.insert(0, tracker_url); + if tracker_mode.is_open() { + torrent_response.include_url_as_main_tracker(&tracker_url); + } else { + // Add main tracker URL + match opt_user_id { + Some(user_id) => { + // If no user owned tracker key can be found, use default tracker url + // code-review: for downloading the torrent file it returns an error + // instead of defaulting to the default tracker URL. + let personal_announce_url = self + .tracker_service + .get_personal_announce_url(user_id) + .await + .unwrap_or(tracker_url); + + torrent_response.include_url_as_main_tracker(&personal_announce_url); + } + None => { + torrent_response.include_url_as_main_tracker(&tracker_url); + } } } @@ -513,6 +518,11 @@ impl Index { let settings = self.configuration.settings.read().await; settings.tracker.url.clone() } + + async fn get_tracker_mode(&self) -> TrackerMode { + let settings = self.configuration.settings.read().await; + settings.tracker.mode.clone() + } } pub struct DbTorrentRepository { diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 4b590226..65b8de10 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -473,15 +473,6 @@ mod for_guests { mod for_authenticated_users { - use torrust_index::utils::parse_torrent::decode_torrent; - use torrust_index::web::api; - - use crate::common::client::Client; - use crate::e2e::environment::TestEnv; - use crate::e2e::web::api::v1::contexts::torrent::asserts::{build_announce_url, get_user_tracker_key}; - use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user; - mod uploading_a_torrent { use torrust_index::web::api; @@ -779,42 +770,6 @@ mod for_authenticated_users { } } - #[tokio::test] - async fn it_should_allow_authenticated_users_to_download_a_torrent_with_a_personal_announce_url() { - let mut env = TestEnv::new(); - env.start(api::Version::V1).await; - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - // Given a previously uploaded torrent - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await; - - // And a logged in user who is going to download the torrent - let downloader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &downloader.token); - - // When the user downloads the torrent - let response = client.download_torrent(&test_torrent.file_info_hash()).await; - - let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); - - // Then the torrent should have the personal announce URL - let tracker_key = get_user_tracker_key(&downloader, &env) - .await - .expect("uploader should have a valid tracker key"); - - let tracker_url = env.server_settings().unwrap().tracker.url; - - assert_eq!( - torrent.announce.unwrap(), - build_announce_url(&tracker_url, &Some(tracker_key)) - ); - } - mod and_non_admins { use torrust_index::web::api; From c1fd8666644cea151c55d80560078b2d00689710 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 16:35:20 +0000 Subject: [PATCH 109/309] feat: [#488] return an error in the torrent details endpoint when user tracker keys can't be generated --- src/services/torrent.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 73a6e38e..20f4a2a3 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -367,14 +367,7 @@ impl Index { // Add main tracker URL match opt_user_id { Some(user_id) => { - // If no user owned tracker key can be found, use default tracker url - // code-review: for downloading the torrent file it returns an error - // instead of defaulting to the default tracker URL. - let personal_announce_url = self - .tracker_service - .get_personal_announce_url(user_id) - .await - .unwrap_or(tracker_url); + let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?; torrent_response.include_url_as_main_tracker(&personal_announce_url); } From d100b5b9317875ca4a5a3a8f564aaffe40c9066d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 17:58:00 +0000 Subject: [PATCH 110/309] fix: [#488] broken test after removing duplicate tracker urls from magnet links --- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 65b8de10..37910582 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -201,13 +201,12 @@ mod for_guests { // it adds the tracker with the personal announce url, if the user // is logged in. If the user is not logged in, it adds the default // tracker again, and it ends up with two trackers. - trackers: vec![tracker_url.clone(), tracker_url.clone()], + trackers: vec![tracker_url.clone()], magnet_link: format!( // cspell:disable-next-line - "magnet:?xt=urn:btih:{}&dn={}&tr={}&tr={}", + "magnet:?xt=urn:btih:{}&dn={}&tr={}", test_torrent.file_info.info_hash.to_lowercase(), urlencoding::encode(&test_torrent.index_info.title), - encoded_tracker_url, encoded_tracker_url ), tags: vec![], From b8624a8143f61336bad1e149a74c63efb19acb34 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 18:02:58 +0000 Subject: [PATCH 111/309] feat!: [#488] return 503 instead of 500 when tracker is offline when the Index needs to make a request to the Tracker API and is not available. --- src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index b750a852..e6bc00c1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -298,7 +298,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::TrackerOffline => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::TrackerOffline => StatusCode::SERVICE_UNAVAILABLE, ServiceError::CategoryNameEmpty => StatusCode::BAD_REQUEST, ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::TagNameEmpty => StatusCode::BAD_REQUEST, From 36e46fe5610d2b9f33ab31bcd436f96ee4d8b434 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 18:04:21 +0000 Subject: [PATCH 112/309] chore(deps): add cargo dependency url to parse URLs --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 561fa1f9..053c3f86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3268,6 +3268,7 @@ dependencies = [ "tower-http", "trace", "tracing", + "url", "urlencoding", "uuid", "which", diff --git a/Cargo.toml b/Cargo.toml index d6ab88ee..ad1beb59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packa tower-http = { version = "0", features = ["compression-full", "cors", "trace", "propagate-header", "request-id"] } trace = "0.1.7" tracing = "0.1.40" +url = "2.5.0" urlencoding = "2" uuid = { version = "1", features = ["v4"] } From 9b7c5c8138580da2b8b1a772e586f2c0e66f5fa7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 23 Feb 2024 18:22:58 +0000 Subject: [PATCH 113/309] feat: [#488] panic starting the app when tracker config is invalid For the time being, it only checks that private tracker don't use UDP. --- src/app.rs | 2 ++ src/config.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/app.rs b/src/app.rs index bb71d5dc..9cdd0523 100644 --- a/src/app.rs +++ b/src/app.rs @@ -39,6 +39,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running logging::setup(&log_level); + configuration.validate().await.expect("invalid configuration"); + let configuration = Arc::new(configuration); // Get configuration settings needed to build the app dependencies and diff --git a/src/config.rs b/src/config.rs index d655eaf1..7335c1eb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; +use url::{ParseError, Url}; /// Information required for loading config #[derive(Debug, Default, Clone)] @@ -99,6 +100,17 @@ pub enum Error { Infallible, } +/// Errors that can occur validating the configuration. +#[derive(Error, Debug)] +pub enum ValidationError { + /// Unable to load the configuration from the configuration file. + #[error("Invalid tracker URL: {source}")] + InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, + + #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] + UdpTrackersInPrivateModeNotSupported, +} + impl From for Error { #[track_caller] fn from(err: ConfigError) -> Self { @@ -149,6 +161,11 @@ impl TrackerMode { pub fn is_open(&self) -> bool { matches!(self, TrackerMode::Public | TrackerMode::Whitelisted) } + + #[must_use] + pub fn is_close(&self) -> bool { + !self.is_open() + } } /// Configuration for the associated tracker. @@ -550,6 +567,42 @@ impl Configuration { settings_lock.net.base_url.clone() } + + /// # Errors + /// + /// Will return an error if the configuration is invalid. + pub async fn validate(&self) -> Result<(), ValidationError> { + self.validate_tracker_config().await + } + + /// # Errors + /// + /// Will return an error if the `tracker` configuration section is invalid. + pub async fn validate_tracker_config(&self) -> Result<(), ValidationError> { + let settings_lock = self.settings.read().await; + + let tracker_mode = settings_lock.tracker.mode.clone(); + let tracker_url = settings_lock.tracker.url.clone(); + + let tracker_url = match parse_url(&tracker_url) { + Ok(url) => url, + Err(err) => { + return Err(ValidationError::InvalidTrackerUrl { + source: Located(err).into(), + }) + } + }; + + if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { + return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); + } + + Ok(()) + } +} + +fn parse_url(url_str: &str) -> Result { + Url::parse(url_str) } /// The public index configuration. From b5e54cbb49de77659fc0022e66eec9ecb74824cf Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 27 Feb 2024 11:48:09 +0000 Subject: [PATCH 114/309] chore(deps): udpate cargo dependencies ```console cargo update Updating crates.io index Updating bstr v1.9.0 -> v1.9.1 Updating bumpalo v3.15.2 -> v3.15.3 Updating cc v1.0.86 -> v1.0.88 Updating hermit-abi v0.3.6 -> v0.3.8 Updating rustls-pki-types v1.3.0 -> v1.3.1 Updating socket2 v0.5.5 -> v0.5.6 Updating syn v2.0.50 -> v2.0.51 Updating tempfile v3.10.0 -> v3.10.1 Updating tower-http v0.5.1 -> v0.5.2 Updating windows-targets v0.52.0 -> v0.52.3 Updating windows_aarch64_gnullvm v0.52.0 -> v0.52.3 Updating windows_aarch64_msvc v0.52.0 -> v0.52.3 Updating windows_i686_gnu v0.52.0 -> v0.52.3 Updating windows_i686_msvc v0.52.0 -> v0.52.3 Updating windows_x86_64_gnu v0.52.0 -> v0.52.3 Updating windows_x86_64_gnullvm v0.52.0 -> v0.52.3 Updating windows_x86_64_msvc v0.52.0 -> v0.52.3 ``` --- Cargo.lock | 116 ++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 053c3f86..69b2752a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,7 +183,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.2" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b1be7772ee4501dba05acbe66bb1e8760f6a6c474a36035631638e4415f130" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "bytemuck" @@ -396,9 +396,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" dependencies = [ "libc", ] @@ -418,7 +418,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -462,7 +462,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -962,7 +962,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -1122,9 +1122,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "hex" @@ -1788,7 +1788,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -1933,7 +1933,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -1970,7 +1970,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -2338,9 +2338,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" +checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" [[package]] name = "rustls-webpki" @@ -2479,7 +2479,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -2626,12 +2626,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2930,9 +2930,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", @@ -2968,9 +2968,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", @@ -3038,7 +3038,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -3136,7 +3136,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -3300,9 +3300,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", "bitflags 2.4.2", @@ -3363,7 +3363,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] @@ -3644,7 +3644,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", "wasm-bindgen-shared", ] @@ -3678,7 +3678,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3764,7 +3764,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3782,7 +3782,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3802,17 +3802,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -3823,9 +3823,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -3835,9 +3835,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -3847,9 +3847,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -3859,9 +3859,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -3871,9 +3871,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -3883,9 +3883,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -3895,9 +3895,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winnow" @@ -3962,7 +3962,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.51", ] [[package]] From a511df37544e3f61cc51b8e5fe130fa1c4f1fa43 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 27 Feb 2024 11:50:00 +0000 Subject: [PATCH 115/309] chore(deps): bump EndBug/label-sync from 2.3.2 to 2.3.3 --- .github/workflows/labels.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yaml b/.github/workflows/labels.yaml index 97aaa030..bb8283f3 100644 --- a/.github/workflows/labels.yaml +++ b/.github/workflows/labels.yaml @@ -29,7 +29,7 @@ jobs: - id: sync name: Apply Labels from File - uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 + uses: EndBug/label-sync@v2 with: config-file: .github/labels.json delete-other-labels: true From 09e1620176fcde76a363497938f8f7301ca35de8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 27 Feb 2024 17:34:19 +0000 Subject: [PATCH 116/309] refactor: [#501] decouple database struct from API response for Category --- src/models/category.rs | 24 +++++++++++++++++++ src/models/response.rs | 7 +++--- .../client/v1/contexts/torrent/responses.rs | 4 +--- .../server/v1/contexts/category/handlers.rs | 7 ++++-- .../server/v1/contexts/category/responses.rs | 22 +++++++++++++++++ tests/common/contexts/torrent/asserts.rs | 5 +--- tests/common/contexts/torrent/responses.rs | 4 +--- .../web/api/v1/contexts/torrent/contract.rs | 2 +- 8 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/models/category.rs b/src/models/category.rs index 76b74f20..417ed57f 100644 --- a/src/models/category.rs +++ b/src/models/category.rs @@ -1,2 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use crate::databases::database::Category as DatabaseCategory; + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct Category { + pub id: i64, + // Deprecated. Use `id`. + pub category_id: i64, + pub name: String, + pub num_torrents: i64, +} + #[allow(clippy::module_name_repetitions)] pub type CategoryId = i64; + +impl From for Category { + fn from(db_category: DatabaseCategory) -> Self { + Category { + id: db_category.category_id, + category_id: db_category.category_id, + name: db_category.name, + num_torrents: db_category.num_torrents, + } + } +} diff --git a/src/models/response.rs b/src/models/response.rs index 9932b09f..dedc067f 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; +use super::category::Category; use super::torrent::TorrentId; -use crate::databases::database::Category; +use crate::databases::database::Category as DatabaseCategory; use crate::models::torrent::TorrentListing; use crate::models::torrent_file::TorrentFile; use crate::models::torrent_tag::TorrentTag; @@ -70,14 +71,14 @@ pub struct TorrentResponse { impl TorrentResponse { #[must_use] - pub fn from_listing(torrent_listing: TorrentListing, category: Option) -> TorrentResponse { + pub fn from_listing(torrent_listing: TorrentListing, category: Option) -> TorrentResponse { TorrentResponse { torrent_id: torrent_listing.torrent_id, uploader: torrent_listing.uploader, info_hash: torrent_listing.info_hash, title: torrent_listing.title, description: torrent_listing.description, - category, + category: category.map(std::convert::Into::into), upload_date: torrent_listing.date_uploaded, file_size: torrent_listing.file_size, seeders: torrent_listing.seeders, diff --git a/src/web/api/client/v1/contexts/torrent/responses.rs b/src/web/api/client/v1/contexts/torrent/responses.rs index 07355e99..43704149 100644 --- a/src/web/api/client/v1/contexts/torrent/responses.rs +++ b/src/web/api/client/v1/contexts/torrent/responses.rs @@ -75,11 +75,9 @@ pub struct TorrentDetails { pub encoding: Option, } -#[allow(unknown_lints)] -#[allow(clippy::struct_field_names)] #[derive(Deserialize, PartialEq, Debug)] pub struct Category { - pub category_id: CategoryId, // todo: rename to `id` + pub id: CategoryId, pub name: String, pub num_torrents: u64, } diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index 34046f6f..5e9d8c69 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -6,7 +6,7 @@ use axum::extract::{self, State}; use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddCategoryForm, DeleteCategoryForm}; -use super::responses::{added_category, deleted_category}; +use super::responses::{added_category, deleted_category, Category}; use crate::common::AppData; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; @@ -27,7 +27,10 @@ use crate::web::api::server::v1::responses::{self}; #[allow(clippy::unused_async)] pub async fn get_all_handler(State(app_data): State>) -> Response { match app_data.category_repository.get_all().await { - Ok(categories) => Json(responses::OkResponseData { data: categories }).into_response(), + Ok(categories) => { + let categories: Vec = categories.into_iter().map(Category::from).collect(); + Json(responses::OkResponseData { data: categories }).into_response() + } Err(error) => error.into_response(), } } diff --git a/src/web/api/server/v1/contexts/category/responses.rs b/src/web/api/server/v1/contexts/category/responses.rs index 2f761d2c..577b0882 100644 --- a/src/web/api/server/v1/contexts/category/responses.rs +++ b/src/web/api/server/v1/contexts/category/responses.rs @@ -1,9 +1,20 @@ //! API responses for the the [`category`](crate::web::api::server::v1::contexts::category) API //! context. use axum::Json; +use serde::{Deserialize, Serialize}; +use crate::databases::database::Category as DatabaseCategory; use crate::web::api::server::v1::responses::OkResponseData; +#[derive(Debug, Serialize, Deserialize)] +pub struct Category { + pub id: i64, + /// Deprecated. Use `id`. + pub category_id: i64, // todo: remove when the Index GUI uses the new `id` field. + pub name: String, + pub num_torrents: i64, +} + /// Response after successfully creating a new category. pub fn added_category(category_name: &str) -> Json> { Json(OkResponseData { @@ -17,3 +28,14 @@ pub fn deleted_category(category_name: &str) -> Json> { data: category_name.to_string(), }) } + +impl From for Category { + fn from(db_category: DatabaseCategory) -> Self { + Category { + id: db_category.category_id, + category_id: db_category.category_id, + name: db_category.name, + num_torrents: db_category.num_torrents, + } + } +} diff --git a/tests/common/contexts/torrent/asserts.rs b/tests/common/contexts/torrent/asserts.rs index d0f1a8cf..f612ba70 100644 --- a/tests/common/contexts/torrent/asserts.rs +++ b/tests/common/contexts/torrent/asserts.rs @@ -15,10 +15,7 @@ pub fn assert_expected_torrent_details(torrent: &TorrentDetails, expected_torren ("info_hash", torrent.info_hash == expected_torrent.info_hash), ("title", torrent.title == expected_torrent.title), ("description", torrent.description == expected_torrent.description), - ( - "category.category_id", - torrent.category.category_id == expected_torrent.category.category_id, - ), + ("category.id", torrent.category.id == expected_torrent.category.id), ("category.name", torrent.category.name == expected_torrent.category.name), ("file_size", torrent.file_size == expected_torrent.file_size), ("seeders", torrent.seeders == expected_torrent.seeders), diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index a776623f..b1ef0882 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -74,11 +74,9 @@ pub struct TorrentDetails { pub encoding: Option, } -#[allow(unknown_lints)] -#[allow(clippy::struct_field_names)] #[derive(Deserialize, PartialEq, Debug)] pub struct Category { - pub category_id: CategoryId, // todo: rename to `id` + pub id: CategoryId, pub name: String, pub num_torrents: u64, } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 37910582..6c057d67 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -182,7 +182,7 @@ mod for_guests { title: test_torrent.index_info.title.clone(), description: test_torrent.index_info.description, category: Category { - category_id: software_predefined_category_id(), + id: software_predefined_category_id(), name: test_torrent.index_info.category, num_torrents: 19, // Ignored in assertion }, From 553208eadf45e38de3a894c5c886ab6c88cdf7cd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 28 Feb 2024 16:34:11 +0000 Subject: [PATCH 117/309] feat: [#503] add more contrains for usernames Usernames will only allow the following characters: - Uppercase and lowercase letters (`A-Z`, `a-z`) - Digits (`0-9`) - The simple dash (`-`) - The underscore (`_`) Additionally, we'll enforce a maximum length of 20 characters for the usernames. We exclude emojis, blank spaces, non-valid UTF-8 characters, and non-ASCII characters ```regex ^[A-Za-z0-9-_]{1,20}$ ``` Explanation: - `^` asserts the start of the string. - `[A-Za-z0-9-]` is a character class that matches uppercase letters (`A-Z`), lowercase letters (`a-z`), digits (`0-9`), and the simple dash (`-`) or underscore (`_`). - `{1,20}` quantifier makes sure that the preceding character class matches between 1 and 20 times, inclusive, which enforces your maximum length requirement. - `$` asserts the end of the string. This regular expression ensures that usernames consist only of the specified characters and do not exceed 20 characters in length. It effectively excludes emojis, blank spaces, non-valid UTF-8 characters, and non-ASCII characters, as they are not included in the specified character set. The API endpoint `/register` for registration should return a Bad Request when the username does to match this regexp. --- src/errors.rs | 2 +- src/models/user.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++ src/services/user.rs | 12 +++---- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index e6bc00c1..92d5319c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -62,7 +62,7 @@ pub enum ServiceError { #[display(fmt = "Username not available")] UsernameTaken, - #[display(fmt = "Username contains illegal characters")] + #[display(fmt = "Invalid username. Usernames must consist of 1-20 alphanumeric characters, dashes, or underscore")] UsernameInvalid, /// email is already taken diff --git a/src/models/user.rs b/src/models/user.rs index b115e10c..8347357c 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,3 +1,7 @@ +use std::fmt; +use std::str::FromStr; + +use regex::Regex; use serde::{Deserialize, Serialize}; #[allow(clippy::module_name_repetitions)] @@ -57,3 +61,82 @@ pub struct UserClaims { pub user: UserCompact, pub exp: u64, // epoch in seconds } + +const MAX_USERNAME_LENGTH: usize = 20; +const USERNAME_VALIDATION_ERROR_MSG: &str = "Usernames must consist of 1-20 alphanumeric characters, dashes, or underscore"; + +#[derive(Debug, Clone)] +pub struct UsernameParseError { + message: String, +} + +// Implement std::fmt::Display for UsernameParseError +impl fmt::Display for UsernameParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "UsernameParseError: {}", self.message) + } +} + +// Implement std::error::Error for UsernameParseError +impl std::error::Error for UsernameParseError {} + +pub struct Username(String); + +impl fmt::Display for Username { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +// Implement the parsing logic +impl FromStr for Username { + type Err = UsernameParseError; + + fn from_str(s: &str) -> Result { + if s.len() > MAX_USERNAME_LENGTH { + return Err(UsernameParseError { + message: format!("username '{s}' is too long. {USERNAME_VALIDATION_ERROR_MSG}."), + }); + } + + let pattern = format!(r"^[A-Za-z0-9-_]{{1,{MAX_USERNAME_LENGTH}}}$"); + let re = Regex::new(&pattern).expect("username regexp should be valid"); + + if re.is_match(s) { + Ok(Username(s.to_string())) + } else { + Err(UsernameParseError { + message: format!("'{s}' is not a valid username. {USERNAME_VALIDATION_ERROR_MSG}."), + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn username_must_consist_of_1_to_20_alphanumeric_characters_or_dashes() { + let username_str = "validUsername123"; + assert!(username_str.parse::().is_ok()); + } + + #[test] + fn username_should_be_shorter_then_21_chars() { + let username_str = "a".repeat(MAX_USERNAME_LENGTH + 1); + assert!(username_str.parse::().is_err()); + } + + #[test] + fn username_should_not_allow_invalid_characters() { + let username_str = "invalid*Username"; + assert!(username_str.parse::().is_err()); + } + + #[test] + fn username_should_be_displayed() { + let username = Username("FirstLast-01".to_string()); + assert_eq!(username.to_string(), "FirstLast-01"); + } +} diff --git a/src/services/user.rs b/src/services/user.rs index cd4e5937..05f9ecfc 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -12,7 +12,7 @@ use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::mailer; use crate::mailer::VerifyClaims; -use crate::models::user::{UserCompact, UserId, UserProfile}; +use crate::models::user::{UserCompact, UserId, UserProfile, Username}; use crate::utils::validation::validate_email_address; use crate::web::api::server::v1::contexts::user::forms::RegistrationForm; @@ -68,6 +68,10 @@ impl RegistrationService { pub async fn register_user(&self, registration_form: &RegistrationForm, api_base_url: &str) -> Result { info!("registering user: {}", registration_form.username); + let Ok(username) = registration_form.username.parse::() else { + return Err(ServiceError::UsernameInvalid); + }; + let settings = self.configuration.settings.read().await; let opt_email = match settings.auth.email_on_signup { @@ -111,14 +115,10 @@ impl RegistrationService { .hash_password(registration_form.password.as_bytes(), &salt)? .to_string(); - if registration_form.username.contains('@') { - return Err(ServiceError::UsernameInvalid); - } - let user_id = self .user_repository .add( - ®istration_form.username, + &username.to_string(), &opt_email.clone().unwrap_or(no_email()), &password_hash, ) From c92e41c5b2c014f9ec499daa43a84d508fe47054 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 29 Feb 2024 17:30:24 +0000 Subject: [PATCH 118/309] feat: add a new console command to create random test torrents You can execute it with: ``` cargo run --bin create_test_torrent ./output/test/torrents ``` Folders must be created. --- .gitignore | 5 ++- src/bin/create_test_torrent.rs | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/bin/create_test_torrent.rs diff --git a/.gitignore b/.gitignore index c7d2c3f0..281cc1ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ /.coverage/ /.env +/.idea/ /config.toml /data_v2.db* /data.db* +/output/ /storage/ /target -/uploads/ -/.idea/ +/uploads/ \ No newline at end of file diff --git a/src/bin/create_test_torrent.rs b/src/bin/create_test_torrent.rs new file mode 100644 index 00000000..9495c21c --- /dev/null +++ b/src/bin/create_test_torrent.rs @@ -0,0 +1,74 @@ +//! Command line tool to create a test torrent file. +//! +//! It's only used for debugging purposes. +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use torrust_index::models::torrent_file::{Torrent, TorrentFile, TorrentInfoDictionary}; +use torrust_index::services::hasher::sha1; // DevSkim: ignore DS126858 +use torrust_index::utils::parse_torrent; +use uuid::Uuid; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() != 2 { + eprintln!("Usage: cargo run --bin create_test_torrent "); + eprintln!("Example: cargo run --bin create_test_torrent ./output/test/torrents"); + std::process::exit(1); + } + + let destination_folder = &args[1]; + + let id = Uuid::new_v4(); + + // Content of the file from which the torrent will be generated. + // We use the UUID as the content of the file. + let file_contents = format!("{id}\n"); + let file_name = format!("file-{id}.txt"); + + let torrent = Torrent { + info: TorrentInfoDictionary::with( + &file_name, + 16384, + None, + 0, + &sha1(&file_contents), // DevSkim: ignore DS126858 + &[TorrentFile { + path: vec![file_name.clone()], // Adjusted to include the actual file name + length: i64::try_from(file_contents.len()).expect("file contents size in bytes cannot exceed i64::MAX"), + md5sum: None, // DevSkim: ignore DS126858 + }], + ), + announce: None, + nodes: Some(vec![("99.236.6.144".to_string(), 6881), ("91.109.195.156".to_string(), 1996)]), + encoding: None, + httpseeds: Some(vec!["https://seeder.torrust-demo.com/seed".to_string()]), + announce_list: Some(vec![vec!["https://tracker.torrust-demo.com/announce".to_string()]]), + creation_date: None, + comment: None, + created_by: None, + }; + + match parse_torrent::encode_torrent(&torrent) { + Ok(bytes) => { + // Construct the path where the torrent file will be saved + let file_path = Path::new(destination_folder).join(format!("{file_name}.torrent")); + + // Attempt to create and write to the file + let mut file = match File::create(&file_path) { + Ok(file) => file, + Err(e) => panic!("Failed to create file {file_path:?}: {e}"), + }; + + if let Err(e) = file.write_all(&bytes) { + panic!("Failed to write to file {file_path:?}: {e}"); + } + + println!("File successfully written to {file_path:?}"); + } + Err(e) => panic!("Error encoding torrent: {e}"), + }; +} From d98c61a3ee4b11f6aa295dfb4de69f95cfe0dce2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 4 Mar 2024 13:44:36 +0000 Subject: [PATCH 119/309] feat: [#438] persist metainfo field httpseeds. BEP 17 The field `httpseeds` was included in the `Torrent` struct but not persisted into or loaded from database. --- ...4106_torrust_add_http_seeds_to_torrent.sql | 6 +++ ...4106_torrust_add_http_seeds_to_torrent.sql | 6 +++ src/databases/database.rs | 21 +++++++++- src/databases/mysql.rs | 37 ++++++++++++++++- src/databases/sqlite.rs | 41 ++++++++++++++++++- src/models/torrent_file.rs | 18 +++++++- 6 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql create mode 100644 migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql diff --git a/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql b/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql new file mode 100644 index 00000000..696f5ad6 --- /dev/null +++ b/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds ( + http_seed_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + torrent_id INTEGER NOT NULL, + seed_url VARCHAR(256) NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql b/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql new file mode 100644 index 00000000..04fa7713 --- /dev/null +++ b/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds ( + http_seed_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + torrent_id INTEGER NOT NULL, + seed_url TEXT NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/src/databases/database.rs b/src/databases/database.rs index 18b2a56a..010d208a 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -206,7 +206,14 @@ pub trait Database: Sync + Send { let torrent_announce_urls = self.get_torrent_announce_urls_from_id(db_torrent.torrent_id).await?; - Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls)) + let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + + Ok(Torrent::from_database( + &db_torrent, + &torrent_files, + torrent_announce_urls, + torrent_http_seed_urls, + )) } /// Get `Torrent` from `torrent_id`. @@ -217,7 +224,14 @@ pub trait Database: Sync + Send { let torrent_announce_urls = self.get_torrent_announce_urls_from_id(torrent_id).await?; - Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls)) + let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + + Ok(Torrent::from_database( + &db_torrent, + &torrent_files, + torrent_announce_urls, + torrent_http_seed_urls, + )) } /// It returns the list of all infohashes producing the same canonical @@ -257,6 +271,9 @@ pub trait Database: Sync + Send { /// Get all torrent's announce urls as `Vec>` from `torrent_id`. async fn get_torrent_announce_urls_from_id(&self, torrent_id: i64) -> Result>, Error>; + /// Get all torrent's HTTP seed urls as `Vec>` from `torrent_id`. + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get `TorrentListing` from `torrent_id`. async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index bb1a0fb4..87f48e26 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -13,7 +13,7 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile}; +use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -582,7 +582,31 @@ impl Database for Mysql { return Err(e); } - // Insert tags + // add HTTP seeds + + let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds { + for seed_url in http_seeds { + let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)") + .bind(torrent_id) + .bind(seed_url) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_http_seeds_result { + drop(tx.rollback().await); + return Err(e); + } + + // add tags for tag_id in &metadata.tags { let insert_torrent_tag_result = query("INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)") @@ -740,6 +764,15 @@ impl Database for Mysql { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| a.seed_url.to_string()).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 4b47dd10..5fe1d398 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -13,7 +13,7 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile}; +use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -504,6 +504,8 @@ impl Database for Sqlite { return Err(e); } + // add torrent files + let insert_torrent_files_result = if let Some(length) = torrent.info.length { query("INSERT INTO torrust_torrent_files (md5sum, torrent_id, length) VALUES (?, ?, ?)") .bind(torrent.info.md5sum.clone()) @@ -538,6 +540,8 @@ impl Database for Sqlite { return Err(e); } + // add announce URLs + let insert_torrent_announce_urls_result: Result<(), database::Error> = if let Some(announce_urls) = &torrent.announce_list { // flatten the nested vec (this will however remove the) @@ -572,7 +576,31 @@ impl Database for Sqlite { return Err(e); } - // Insert tags + // add HTTP seeds + + let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds { + for seed_url in http_seeds { + let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)") + .bind(torrent_id) + .bind(seed_url) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_http_seeds_result { + drop(tx.rollback().await); + return Err(e); + } + + // add tags for tag_id in &metadata.tags { let insert_torrent_tag_result = query("INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)") @@ -730,6 +758,15 @@ impl Database for Sqlite { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| a.seed_url.to_string()).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 35bb3a6a..8c0503d9 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -70,7 +70,12 @@ impl Torrent { /// This function will panic if the `torrent_info.pieces` is not a valid /// hex string. #[must_use] - pub fn from_database(db_torrent: &DbTorrent, torrent_files: &[TorrentFile], torrent_announce_urls: Vec>) -> Self { + pub fn from_database( + db_torrent: &DbTorrent, + torrent_files: &[TorrentFile], + torrent_announce_urls: Vec>, + torrent_http_seed_urls: Vec, + ) -> Self { let info_dict = TorrentInfoDictionary::with( &db_torrent.name, db_torrent.piece_length, @@ -85,7 +90,11 @@ impl Torrent { announce: None, nodes: None, encoding: db_torrent.encoding.clone(), - httpseeds: None, + httpseeds: if torrent_http_seed_urls.is_empty() { + None + } else { + Some(torrent_http_seed_urls) + }, announce_list: Some(torrent_announce_urls), creation_date: db_torrent.creation_date, comment: db_torrent.comment.clone(), @@ -345,6 +354,11 @@ pub struct DbTorrentAnnounceUrl { pub tracker_url: String, } +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct DbTorrentHttpSeedUrl { + pub seed_url: String, +} + #[cfg(test)] mod tests { From f64cb296936a98e2dfb5d3efda8a14f2547a3855 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 4 Mar 2024 16:34:07 +0000 Subject: [PATCH 120/309] test: add announce field to test torrent --- src/bin/create_test_torrent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/create_test_torrent.rs b/src/bin/create_test_torrent.rs index 9495c21c..494cbcd3 100644 --- a/src/bin/create_test_torrent.rs +++ b/src/bin/create_test_torrent.rs @@ -42,7 +42,7 @@ fn main() { md5sum: None, // DevSkim: ignore DS126858 }], ), - announce: None, + announce: Some("https://tracker.torrust-demo.com/announce".to_string()), nodes: Some(vec![("99.236.6.144".to_string(), 6881), ("91.109.195.156".to_string(), 1996)]), encoding: None, httpseeds: Some(vec!["https://seeder.torrust-demo.com/seed".to_string()]), From 7b9d5a475c25db634d0aa77cef2a8b405bff1b22 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 4 Mar 2024 17:23:17 +0000 Subject: [PATCH 121/309] feat: [#438] persist metainfo field nodes. BEP 5 --- ...304165035_torrust_add_nodes_to_torrent.sql | 7 ++++ ...304165035_torrust_add_nodes_to_torrent.sql | 7 ++++ .../config/index.development.sqlite3.toml | 2 +- src/databases/database.rs | 9 +++++ src/databases/mysql.rs | 38 ++++++++++++++++++- src/databases/sqlite.rs | 38 ++++++++++++++++++- src/models/torrent_file.rs | 9 ++++- 7 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql create mode 100644 migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql diff --git a/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql b/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql new file mode 100644 index 00000000..66e7a7df --- /dev/null +++ b/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_nodes ( + node_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + torrent_id INTEGER NOT NULL, + node_ip VARCHAR(256) NOT NULL, + node_port INTEGER NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql b/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql new file mode 100644 index 00000000..4efc0966 --- /dev/null +++ b/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_nodes ( + node_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + torrent_id INTEGER NOT NULL, + node_ip TEXT NOT NULL, + node_port INTEGER NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) \ No newline at end of file diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 669979af..5a4461e0 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,4 +1,4 @@ -log_level = "info" +log_level = "debug" [website] name = "Torrust" diff --git a/src/databases/database.rs b/src/databases/database.rs index 010d208a..2d56e22f 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -208,11 +208,14 @@ pub trait Database: Sync + Send { let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + let torrent_nodes = self.get_torrent_nodes_from_id(db_torrent.torrent_id).await?; + Ok(Torrent::from_database( &db_torrent, &torrent_files, torrent_announce_urls, torrent_http_seed_urls, + torrent_nodes, )) } @@ -226,11 +229,14 @@ pub trait Database: Sync + Send { let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + let torrent_nodes = self.get_torrent_nodes_from_id(db_torrent.torrent_id).await?; + Ok(Torrent::from_database( &db_torrent, &torrent_files, torrent_announce_urls, torrent_http_seed_urls, + torrent_nodes, )) } @@ -274,6 +280,9 @@ pub trait Database: Sync + Send { /// Get all torrent's HTTP seed urls as `Vec>` from `torrent_id`. async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get all torrent's nodes as `Vec<(String, i64)>` from `torrent_id`. + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get `TorrentListing` from `torrent_id`. async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 87f48e26..07ae8839 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -13,7 +13,9 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; +use crate::models::torrent_file::{ + DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, DbTorrentNode, Torrent, TorrentFile, +}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -606,6 +608,31 @@ impl Database for Mysql { return Err(e); } + // add nodes + + let insert_torrent_nodes_result: Result<(), database::Error> = if let Some(nodes) = &torrent.nodes { + for node in nodes { + let () = query("INSERT INTO torrust_torrent_nodes (torrent_id, node_ip, node_port) VALUES (?, ?, ?)") + .bind(torrent_id) + .bind(node.0.clone()) + .bind(node.1) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_nodes_result { + drop(tx.rollback().await); + return Err(e); + } + // add tags for tag_id in &metadata.tags { @@ -773,6 +800,15 @@ impl Database for Mysql { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentNode>("SELECT node_ip, node_port FROM torrust_torrent_nodes WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| (a.node_ip.to_string(), a.node_port)).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 5fe1d398..e6fb01f5 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -13,7 +13,9 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; +use crate::models::torrent_file::{ + DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, DbTorrentNode, Torrent, TorrentFile, +}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -600,6 +602,31 @@ impl Database for Sqlite { return Err(e); } + // add nodes + + let insert_torrent_nodes_result: Result<(), database::Error> = if let Some(nodes) = &torrent.nodes { + for node in nodes { + let () = query("INSERT INTO torrust_torrent_nodes (torrent_id, node_ip, node_port) VALUES (?, ?, ?)") + .bind(torrent_id) + .bind(node.0.clone()) + .bind(node.1) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_nodes_result { + drop(tx.rollback().await); + return Err(e); + } + // add tags for tag_id in &metadata.tags { @@ -767,6 +794,15 @@ impl Database for Sqlite { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentNode>("SELECT node_ip, node_port FROM torrust_torrent_nodes WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| (a.node_ip.to_string(), a.node_port)).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 8c0503d9..64fa5e89 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -75,6 +75,7 @@ impl Torrent { torrent_files: &[TorrentFile], torrent_announce_urls: Vec>, torrent_http_seed_urls: Vec, + torrent_nodes: Vec<(String, i64)>, ) -> Self { let info_dict = TorrentInfoDictionary::with( &db_torrent.name, @@ -88,7 +89,7 @@ impl Torrent { Self { info: info_dict, announce: None, - nodes: None, + nodes: if torrent_nodes.is_empty() { None } else { Some(torrent_nodes) }, encoding: db_torrent.encoding.clone(), httpseeds: if torrent_http_seed_urls.is_empty() { None @@ -359,6 +360,12 @@ pub struct DbTorrentHttpSeedUrl { pub seed_url: String, } +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct DbTorrentNode { + pub node_ip: String, + pub node_port: i64, +} + #[cfg(test)] mod tests { From 4d7091d7e47e5ad92389572b34600825eb43e448 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 11:39:34 +0000 Subject: [PATCH 122/309] refactor: [#301] rename table field root_hash to is_bep_30 --- ...10750_torrust_bep_rename_root_hash_to_is_bep_30.sql | 1 + ...10750_torrust_bep_rename_root_hash_to_is_bep_30.sql | 1 + src/databases/mysql.rs | 9 +++++---- src/databases/sqlite.rs | 6 +++--- src/models/torrent_file.rs | 10 +++++----- src/services/torrent_file.rs | 7 ++++--- .../from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs | 8 ++++---- .../transferrer_testers/torrent_transferrer_tester.rs | 2 +- 8 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 migrations/mysql/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql create mode 100644 migrations/sqlite3/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql diff --git a/migrations/mysql/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql b/migrations/mysql/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql new file mode 100644 index 00000000..f50075bc --- /dev/null +++ b/migrations/mysql/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql @@ -0,0 +1 @@ +ALTER TABLE torrust_torrents CHANGE COLUMN root_hash is_bep_30 BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/migrations/sqlite3/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql b/migrations/sqlite3/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql new file mode 100644 index 00000000..0a33c94b --- /dev/null +++ b/migrations/sqlite3/20240305110750_torrust_bep_rename_root_hash_to_is_bep_30.sql @@ -0,0 +1 @@ +ALTER TABLE torrust_torrents RENAME COLUMN root_hash TO is_bep_30; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 07ae8839..660ead9e 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -438,8 +438,9 @@ impl Database for Mysql { // start db transaction let mut tx = conn.begin().await.map_err(|_| database::Error::Error)?; - // torrent file can only hold a pieces key or a root hash key: http://www.bittorrent.org/beps/bep_0030.html - let (pieces, root_hash): (String, bool) = if let Some(pieces) = &torrent.info.pieces { + // torrent file can only hold a `pieces` key or a `root hash` key + // BEP 30: http://www.bittorrent.org/beps/bep_0030.html + let (pieces, is_bep_30): (String, bool) = if let Some(pieces) = &torrent.info.pieces { (from_bytes(pieces.as_ref()), false) } else { let root_hash = torrent.info.root_hash.as_ref().ok_or(database::Error::Error)?; @@ -457,7 +458,7 @@ impl Database for Mysql { pieces, piece_length, private, - root_hash, + is_bep_30, `source`, comment, date_uploaded, @@ -474,7 +475,7 @@ impl Database for Mysql { .bind(pieces) .bind(torrent.info.piece_length) .bind(torrent.info.private) - .bind(root_hash) + .bind(is_bep_30) .bind(torrent.info.source.clone()) .bind(torrent.comment.clone()) .bind(torrent.creation_date) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index e6fb01f5..a31f16d4 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -429,7 +429,7 @@ impl Database for Sqlite { let mut tx = conn.begin().await.map_err(|_| database::Error::Error)?; // torrent file can only hold a pieces key or a root hash key: http://www.bittorrent.org/beps/bep_0030.html - let (pieces, root_hash): (String, bool) = if let Some(pieces) = &torrent.info.pieces { + let (pieces, is_bep_30): (String, bool) = if let Some(pieces) = &torrent.info.pieces { (from_bytes(pieces.as_ref()), false) } else { let root_hash = torrent.info.root_hash.as_ref().ok_or(database::Error::Error)?; @@ -447,7 +447,7 @@ impl Database for Sqlite { pieces, piece_length, private, - root_hash, + is_bep_30, `source`, comment, date_uploaded, @@ -464,7 +464,7 @@ impl Database for Sqlite { .bind(pieces) .bind(torrent.info.piece_length) .bind(torrent.info.private) - .bind(root_hash) + .bind(is_bep_30) .bind(torrent.info.source.clone()) .bind(torrent.comment.clone()) .bind(torrent.creation_date) diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 64fa5e89..01c97af0 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -81,7 +81,7 @@ impl Torrent { &db_torrent.name, db_torrent.piece_length, db_torrent.private, - db_torrent.root_hash, + db_torrent.is_bep_30, &db_torrent.pieces, torrent_files, ); @@ -235,7 +235,7 @@ impl TorrentInfoDictionary { /// - The `pieces` field is not a valid hex string. /// - For single files torrents the `TorrentFile` path is empty. #[must_use] - pub fn with(name: &str, piece_length: i64, private: Option, root_hash: i64, pieces: &str, files: &[TorrentFile]) -> Self { + pub fn with(name: &str, piece_length: i64, private: Option, is_bep_30: i64, pieces: &str, files: &[TorrentFile]) -> Self { let mut info_dict = Self { name: name.to_string(), pieces: None, @@ -250,8 +250,8 @@ impl TorrentInfoDictionary { }; // a torrent file has a root hash or a pieces key, but not both. - if root_hash > 0 { - // If `root_hash` is true the `pieces` field contains the `root hash` + if is_bep_30 > 0 { + // If `is_bep_30` is true the `pieces` field contains the `root hash` info_dict.root_hash = Some(pieces.to_owned()); } else { let buffer = into_bytes(pieces).expect("variable `torrent_info.pieces` is not a valid hex string"); @@ -334,7 +334,7 @@ pub struct DbTorrent { pub piece_length: i64, #[serde(default)] pub private: Option, - pub root_hash: i64, + pub is_bep_30: i64, pub comment: Option, pub creation_date: Option, pub created_by: Option, diff --git a/src/services/torrent_file.rs b/src/services/torrent_file.rs index 3824ea9e..6326e4a4 100644 --- a/src/services/torrent_file.rs +++ b/src/services/torrent_file.rs @@ -14,7 +14,8 @@ pub struct CreateTorrentRequest { pub pieces: String, pub piece_length: i64, pub private: Option, - pub root_hash: i64, // True (1) if it's a BEP 30 torrent. + /// True (1) if it's a BEP 30 torrent. + pub is_bep_30: i64, pub files: Vec, // Other fields of the root level metainfo dictionary pub announce_urls: Vec>, @@ -58,7 +59,7 @@ impl CreateTorrentRequest { &self.name, self.piece_length, self.private, - self.root_hash, + self.is_bep_30, &self.pieces, &self.files, ) @@ -92,7 +93,7 @@ pub fn generate_random_torrent(id: Uuid) -> Torrent { pieces: sha1(&file_contents), piece_length: 16384, private: None, - root_hash: 0, + is_bep_30: 0, files: torrent_files, announce_urls: torrent_announce_urls, comment: None, diff --git a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs index cbaed29f..1508c6f1 100644 --- a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs +++ b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs @@ -26,7 +26,7 @@ pub struct TorrentRecordV2 { pub pieces: String, pub piece_length: i64, pub private: Option, - pub root_hash: i64, + pub is_bep_30: i64, pub date_uploaded: String, } @@ -43,7 +43,7 @@ impl TorrentRecordV2 { pieces: torrent_info.get_pieces_as_string(), piece_length: torrent_info.piece_length, private: torrent_info.private, - root_hash: torrent_info.get_root_hash_as_i64(), + is_bep_30: torrent_info.get_root_hash_as_i64(), date_uploaded: convert_timestamp_to_datetime(torrent.upload_date), } } @@ -196,7 +196,7 @@ impl SqliteDatabaseV2_0_0 { pieces, piece_length, private, - root_hash, + is_bep_30, date_uploaded ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ) @@ -209,7 +209,7 @@ impl SqliteDatabaseV2_0_0 { .bind(torrent.pieces.clone()) .bind(torrent.piece_length) .bind(torrent.private.unwrap_or(0)) - .bind(torrent.root_hash) + .bind(torrent.is_bep_30) .bind(torrent.date_uploaded.clone()) .execute(&self.pool) .await diff --git a/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs b/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs index 6677b04b..768e8a50 100644 --- a/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs +++ b/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs @@ -119,7 +119,7 @@ impl TorrentTester { } else { assert_eq!(imported_torrent.private, torrent_file.info.private); } - assert_eq!(imported_torrent.root_hash, torrent_file.info.get_root_hash_as_i64()); + assert_eq!(imported_torrent.is_bep_30, torrent_file.info.get_root_hash_as_i64()); assert_eq!( imported_torrent.date_uploaded, convert_timestamp_to_datetime(torrent.upload_date) From 7390616dbfca88ef15dca3973c5e61bcc4749644 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 13:37:33 +0000 Subject: [PATCH 123/309] refactor: [#301] add independent field root_hash in DB for BEP-30 torrents instead of reusing the `pieces` field in `torrust_torrents` table. That decouples BEP-30 implementation. In the future we migth want to remove support for BEP 30 since it's decrecated and not supported by clients or libs. --- ...orrust_add_independent_root_hash_field.sql | 4 + ...orrust_add_independent_root_hash_field.sql | 77 +++++++++++++++++++ src/databases/mysql.rs | 23 +++--- src/databases/sqlite.rs | 22 ++++-- src/models/torrent_file.rs | 68 +++++++++++----- src/services/torrent_file.rs | 6 +- .../databases/sqlite_v2_0_0.rs | 8 +- .../torrent_transferrer_tester.rs | 5 +- 8 files changed, 168 insertions(+), 45 deletions(-) create mode 100644 migrations/mysql/20240305120015_torrust_add_independent_root_hash_field.sql create mode 100644 migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql diff --git a/migrations/mysql/20240305120015_torrust_add_independent_root_hash_field.sql b/migrations/mysql/20240305120015_torrust_add_independent_root_hash_field.sql new file mode 100644 index 00000000..efde400c --- /dev/null +++ b/migrations/mysql/20240305120015_torrust_add_independent_root_hash_field.sql @@ -0,0 +1,4 @@ +ALTER TABLE torrust_torrents ADD COLUMN root_hash LONGTEXT; + +-- Make `pieces` nullable. BEP 30 torrents does have this field. +ALTER TABLE torrust_torrents MODIFY COLUMN pieces LONGTEXT; diff --git a/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql b/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql new file mode 100644 index 00000000..0bec861f --- /dev/null +++ b/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql @@ -0,0 +1,77 @@ +-- add field `root_hash` and make `pieces` nullable +CREATE TABLE + "torrust_torrents_new" ( + "torrent_id" INTEGER NOT NULL, + "uploader_id" INTEGER NOT NULL, + "category_id" INTEGER, + "info_hash" TEXT NOT NULL UNIQUE, + "size" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "pieces" TEXT, + "root_hash" TEXT, + "piece_length" INTEGER NOT NULL, + "private" BOOLEAN DEFAULT NULL, + "is_bep_30" INT NOT NULL DEFAULT 0, + "date_uploaded" TEXT NOT NULL, + "source" TEXT DEFAULT NULL, + "comment" TEXT, + "creation_date" BIGINT, + "created_by" TEXT, + "encoding" TEXT, + FOREIGN KEY ("uploader_id") REFERENCES "torrust_users" ("user_id") ON DELETE CASCADE, + FOREIGN KEY ("category_id") REFERENCES "torrust_categories" ("category_id") ON DELETE SET NULL, + PRIMARY KEY ("torrent_id" AUTOINCREMENT) + ); + +-- Step 2: Copy data from the old table to the new table +INSERT INTO + torrust_torrents_new ( + torrent_id, + uploader_id, + category_id, + info_hash, + size, + name, + pieces, + piece_length, + private, + root_hash, + date_uploaded, + source, + comment, + creation_date, + created_by, + encoding + ) +SELECT + torrent_id, + uploader_id, + category_id, + info_hash, + size, + name, + CASE + WHEN is_bep_30 = 0 THEN pieces + ELSE NULL + END, + piece_length, + private, + CASE + WHEN is_bep_30 = 1 THEN pieces + ELSE NULL + END, + date_uploaded, + source, + comment, + creation_date, + created_by, + encoding +FROM + torrust_torrents; + +-- Step 3: Drop the old table +DROP TABLE torrust_torrents; + +-- Step 4: Rename the new table to the original name +ALTER TABLE torrust_torrents_new +RENAME TO torrust_torrents; \ No newline at end of file diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 660ead9e..b964a1e8 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -438,14 +438,17 @@ impl Database for Mysql { // start db transaction let mut tx = conn.begin().await.map_err(|_| database::Error::Error)?; - // torrent file can only hold a `pieces` key or a `root hash` key - // BEP 30: http://www.bittorrent.org/beps/bep_0030.html - let (pieces, is_bep_30): (String, bool) = if let Some(pieces) = &torrent.info.pieces { - (from_bytes(pieces.as_ref()), false) - } else { - let root_hash = torrent.info.root_hash.as_ref().ok_or(database::Error::Error)?; - (root_hash.to_string(), true) - }; + // BEP 30: . + // Torrent file can only hold a `pieces` key or a `root hash` key + let is_bep_30 = !matches!(&torrent.info.pieces, Some(_pieces)); + + let pieces = torrent.info.pieces.as_ref().map(|pieces| from_bytes(pieces.as_ref())); + + let root_hash = torrent + .info + .root_hash + .as_ref() + .map(|root_hash| from_bytes(root_hash.as_ref())); // add torrent let torrent_id = query( @@ -456,6 +459,7 @@ impl Database for Mysql { size, name, pieces, + root_hash, piece_length, private, is_bep_30, @@ -465,7 +469,7 @@ impl Database for Mysql { creation_date, created_by, `encoding` - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(), ?, ?, ?)", + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(), ?, ?, ?)", ) .bind(uploader_id) .bind(metadata.category_id) @@ -473,6 +477,7 @@ impl Database for Mysql { .bind(torrent.file_size()) .bind(torrent.info.name.to_string()) .bind(pieces) + .bind(root_hash) .bind(torrent.info.piece_length) .bind(torrent.info.private) .bind(is_bep_30) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index a31f16d4..18b799f3 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -428,13 +428,17 @@ impl Database for Sqlite { // start db transaction let mut tx = conn.begin().await.map_err(|_| database::Error::Error)?; - // torrent file can only hold a pieces key or a root hash key: http://www.bittorrent.org/beps/bep_0030.html - let (pieces, is_bep_30): (String, bool) = if let Some(pieces) = &torrent.info.pieces { - (from_bytes(pieces.as_ref()), false) - } else { - let root_hash = torrent.info.root_hash.as_ref().ok_or(database::Error::Error)?; - (root_hash.to_string(), true) - }; + // BEP 30: . + // Torrent file can only hold a `pieces` key or a `root hash` key + let is_bep_30 = !matches!(&torrent.info.pieces, Some(_pieces)); + + let pieces = torrent.info.pieces.as_ref().map(|pieces| from_bytes(pieces.as_ref())); + + let root_hash = torrent + .info + .root_hash + .as_ref() + .map(|root_hash| from_bytes(root_hash.as_ref())); // add torrent let torrent_id = query( @@ -445,6 +449,7 @@ impl Database for Sqlite { size, name, pieces, + root_hash, piece_length, private, is_bep_30, @@ -454,7 +459,7 @@ impl Database for Sqlite { creation_date, created_by, `encoding` - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')), ?, ?, ?)", + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')), ?, ?, ?)", ) .bind(uploader_id) .bind(metadata.category_id) @@ -462,6 +467,7 @@ impl Database for Sqlite { .bind(torrent.file_size()) .bind(torrent.info.name.to_string()) .bind(pieces) + .bind(root_hash) .bind(torrent.info.piece_length) .bind(torrent.info.private) .bind(is_bep_30) diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 01c97af0..e0d44b7c 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -1,3 +1,4 @@ +use log::error; use serde::{Deserialize, Serialize}; use serde_bencode::ser; use serde_bytes::ByteBuf; @@ -77,12 +78,31 @@ impl Torrent { torrent_http_seed_urls: Vec, torrent_nodes: Vec<(String, i64)>, ) -> Self { + let pieces_or_root_hash = if db_torrent.is_bep_30 == 0 { + match &db_torrent.pieces { + Some(pieces) => pieces.clone(), + None => { + error!("Invalid torrent #{}. Null `pieces` in database", db_torrent.torrent_id); + String::new() + } + } + } else { + // A BEP-30 torrent + match &db_torrent.root_hash { + Some(root_hash) => root_hash.clone(), + None => { + error!("Invalid torrent #{}. Null `root_hash` in database", db_torrent.torrent_id); + String::new() + } + } + }; + let info_dict = TorrentInfoDictionary::with( &db_torrent.name, db_torrent.piece_length, db_torrent.private, db_torrent.is_bep_30, - &db_torrent.pieces, + &pieces_or_root_hash, torrent_files, ); @@ -235,7 +255,14 @@ impl TorrentInfoDictionary { /// - The `pieces` field is not a valid hex string. /// - For single files torrents the `TorrentFile` path is empty. #[must_use] - pub fn with(name: &str, piece_length: i64, private: Option, is_bep_30: i64, pieces: &str, files: &[TorrentFile]) -> Self { + pub fn with( + name: &str, + piece_length: i64, + private: Option, + is_bep_30: i64, + pieces_or_root_hash: &str, + files: &[TorrentFile], + ) -> Self { let mut info_dict = Self { name: name.to_string(), pieces: None, @@ -249,13 +276,13 @@ impl TorrentInfoDictionary { source: None, }; - // a torrent file has a root hash or a pieces key, but not both. - if is_bep_30 > 0 { - // If `is_bep_30` is true the `pieces` field contains the `root hash` - info_dict.root_hash = Some(pieces.to_owned()); - } else { - let buffer = into_bytes(pieces).expect("variable `torrent_info.pieces` is not a valid hex string"); + // BEP 30: . + // Torrent file can only hold a `pieces` key or a `root hash` key + if is_bep_30 == 0 { + let buffer = into_bytes(pieces_or_root_hash).expect("variable `torrent_info.pieces` is not a valid hex string"); info_dict.pieces = Some(ByteBuf::from(buffer)); + } else { + info_dict.root_hash = Some(pieces_or_root_hash.to_owned()); } // either set the single file or the multiple files information @@ -298,22 +325,22 @@ impl TorrentInfoDictionary { } } - /// It returns the root hash as a `i64` value. - /// - /// # Panics - /// - /// This function will panic if the root hash cannot be converted into a - /// `i64` value. + /// torrent file can only hold a pieces key or a root hash key: + /// [BEP 39](http://www.bittorrent.org/beps/bep_0030.html) #[must_use] - pub fn get_root_hash_as_i64(&self) -> i64 { + pub fn get_root_hash_as_string(&self) -> String { match &self.root_hash { - None => 0i64, - Some(root_hash) => root_hash - .parse::() - .expect("variable `root_hash` cannot be converted into a `i64`"), + None => String::new(), + Some(root_hash) => root_hash.clone(), } } + /// It returns true if the torrent is a BEP-30 torrent. + #[must_use] + pub fn is_bep_30(&self) -> bool { + self.root_hash.is_some() + } + #[must_use] pub fn is_a_single_file_torrent(&self) -> bool { self.length.is_some() @@ -330,7 +357,8 @@ pub struct DbTorrent { pub torrent_id: i64, pub info_hash: String, pub name: String, - pub pieces: String, + pub pieces: Option, + pub root_hash: Option, pub piece_length: i64, #[serde(default)] pub private: Option, diff --git a/src/services/torrent_file.rs b/src/services/torrent_file.rs index 6326e4a4..a60999dd 100644 --- a/src/services/torrent_file.rs +++ b/src/services/torrent_file.rs @@ -11,7 +11,7 @@ use crate::services::hasher::sha1; pub struct CreateTorrentRequest { // The `info` dictionary fields pub name: String, - pub pieces: String, + pub pieces_or_root_hash: String, pub piece_length: i64, pub private: Option, /// True (1) if it's a BEP 30 torrent. @@ -60,7 +60,7 @@ impl CreateTorrentRequest { self.piece_length, self.private, self.is_bep_30, - &self.pieces, + &self.pieces_or_root_hash, &self.files, ) } @@ -90,7 +90,7 @@ pub fn generate_random_torrent(id: Uuid) -> Torrent { let create_torrent_req = CreateTorrentRequest { name: format!("file-{id}.txt"), - pieces: sha1(&file_contents), + pieces_or_root_hash: sha1(&file_contents), piece_length: 16384, private: None, is_bep_30: 0, diff --git a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs index 1508c6f1..f902c859 100644 --- a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs +++ b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs @@ -23,7 +23,8 @@ pub struct TorrentRecordV2 { pub info_hash: String, pub size: i64, pub name: String, - pub pieces: String, + pub pieces: Option, + pub root_hash: Option, pub piece_length: i64, pub private: Option, pub is_bep_30: i64, @@ -40,10 +41,11 @@ impl TorrentRecordV2 { info_hash: torrent.info_hash.clone(), size: torrent.file_size, name: torrent_info.name.clone(), - pieces: torrent_info.get_pieces_as_string(), + pieces: Some(torrent_info.get_pieces_as_string()), + root_hash: Some(torrent_info.get_root_hash_as_string()), piece_length: torrent_info.piece_length, private: torrent_info.private, - is_bep_30: torrent_info.get_root_hash_as_i64(), + is_bep_30: i64::from(torrent_info.is_bep_30()), date_uploaded: convert_timestamp_to_datetime(torrent.upload_date), } } diff --git a/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs b/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs index 768e8a50..078f5630 100644 --- a/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs +++ b/tests/upgrades/from_v1_0_0_to_v2_0_0/transferrer_testers/torrent_transferrer_tester.rs @@ -112,14 +112,15 @@ impl TorrentTester { assert_eq!(imported_torrent.info_hash, torrent.info_hash); assert_eq!(imported_torrent.size, torrent.file_size); assert_eq!(imported_torrent.name, torrent_file.info.name); - assert_eq!(imported_torrent.pieces, torrent_file.info.get_pieces_as_string()); + assert_eq!(imported_torrent.pieces, Some(torrent_file.info.get_pieces_as_string())); + assert_eq!(imported_torrent.root_hash, None); assert_eq!(imported_torrent.piece_length, torrent_file.info.piece_length); if torrent_file.info.private.is_none() { assert_eq!(imported_torrent.private, Some(0)); } else { assert_eq!(imported_torrent.private, torrent_file.info.private); } - assert_eq!(imported_torrent.is_bep_30, torrent_file.info.get_root_hash_as_i64()); + assert_eq!(imported_torrent.is_bep_30, i64::from(torrent_file.info.is_bep_30())); assert_eq!( imported_torrent.date_uploaded, convert_timestamp_to_datetime(torrent.upload_date) From 601c358d0ff55965a3d172d6370c26aa623c5df7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 16:09:30 +0000 Subject: [PATCH 124/309] feat: [#294] add canonical info-hash group top torrent details API response ```json { "data": { "torrent_id": 2, "uploader": "admin", "info_hash": "0c90fbf036e28370c1ec773401bc7620146b1d48", "title": "Test 01", "description": "Test 01", "category": { "id": 5, "category_id": 5, "name": "software", "num_torrents": 1 }, "upload_date": "2024-03-05 16:05:00", "file_size": 602515, "seeders": 0, "leechers": 0, "files": [ { "path": [ "mandelbrot_set_01" ], "length": 602515, "md5sum": null } ], "trackers": [ "udp://localhost:6969" ], "magnet_link": "magnet:?xt=urn:btih:0c90fbf036e28370c1ec773401bc7620146b1d48&dn=Test%2001&tr=udp%3A%2F%2Flocalhost%3A6969", "tags": [], "name": "mandelbrot_set_01", "comment": "Mandelbrot Set 01", "creation_date": 1687937540, "created_by": "Transmission/3.00 (bb6b5a062e)", "encoding": "UTF-8", "canonical_info_hash_group": [ "d5eaff5bc75ed274da7c5294de3f6641dc0a90ce", "e126f473a9dee89217d7ae5982f9b21490ed2c3f" ] } } ``` Notice the new field: `canonical_info_hash_group` at the end of the JSON. --- src/models/response.rs | 13 +- src/services/torrent.rs | 190 ++++++++++-------- tests/common/contexts/torrent/responses.rs | 1 + .../web/api/v1/contexts/torrent/contract.rs | 3 +- 4 files changed, 123 insertions(+), 84 deletions(-) diff --git a/src/models/response.rs b/src/models/response.rs index dedc067f..020eb9be 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -6,6 +6,7 @@ use crate::databases::database::Category as DatabaseCategory; use crate::models::torrent::TorrentListing; use crate::models::torrent_file::TorrentFile; use crate::models::torrent_tag::TorrentTag; +use crate::services::torrent::CanonicalInfoHashGroup; pub enum OkResponses { TokenResponse(TokenResponse), @@ -67,11 +68,16 @@ pub struct TorrentResponse { pub creation_date: Option, pub created_by: Option, pub encoding: Option, + pub canonical_info_hash_group: Vec, } impl TorrentResponse { #[must_use] - pub fn from_listing(torrent_listing: TorrentListing, category: Option) -> TorrentResponse { + pub fn from_listing( + torrent_listing: TorrentListing, + category: Option, + canonical_info_hash_group: &CanonicalInfoHashGroup, + ) -> TorrentResponse { TorrentResponse { torrent_id: torrent_listing.torrent_id, uploader: torrent_listing.uploader, @@ -92,6 +98,11 @@ impl TorrentResponse { creation_date: torrent_listing.creation_date, created_by: torrent_listing.created_by, encoding: torrent_listing.encoding, + canonical_info_hash_group: canonical_info_hash_group + .original_info_hashes + .iter() + .map(super::info_hash::InfoHash::to_hex_string) + .collect(), } } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 20f4a2a3..bc197881 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -328,82 +328,9 @@ impl Index { ) -> Result { let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?; - let torrent_id = torrent_listing.torrent_id; - - let category = match torrent_listing.category_id { - Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?), - None => None, - }; - - let mut torrent_response = TorrentResponse::from_listing(torrent_listing, category); - - // Add files - - torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?; - - if torrent_response.files.len() == 1 { - let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?; - - torrent_response - .files - .iter_mut() - .for_each(|v| v.path = vec![torrent_info.name.to_string()]); - } - - // Add trackers - - // code-review: duplicate logic. We have to check the same in the - // download torrent file endpoint. Here he have only one list of tracker - // like the `announce_list` in the torrent file. - - torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?; - - let tracker_url = self.get_tracker_url().await; - let tracker_mode = self.get_tracker_mode().await; - - if tracker_mode.is_open() { - torrent_response.include_url_as_main_tracker(&tracker_url); - } else { - // Add main tracker URL - match opt_user_id { - Some(user_id) => { - let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?; - - torrent_response.include_url_as_main_tracker(&personal_announce_url); - } - None => { - torrent_response.include_url_as_main_tracker(&tracker_url); - } - } - } - - // Add magnet link - - // todo: extract a struct or function to build the magnet links - let mut magnet = format!( - "magnet:?xt=urn:btih:{}&dn={}", - torrent_response.info_hash, - urlencoding::encode(&torrent_response.title) - ); - - // Add trackers from torrent file to magnet link - for tracker in &torrent_response.trackers { - magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker))); - } - - torrent_response.magnet_link = magnet; - - // Get realtime seeders and leechers - if let Ok(torrent_info) = self - .tracker_statistics_importer - .import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash) - .await - { - torrent_response.seeders = torrent_info.seeders; - torrent_response.leechers = torrent_info.leechers; - } - - torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?; + let torrent_response = self + .build_full_torrent_response(torrent_listing, info_hash, opt_user_id) + .await?; Ok(torrent_response) } @@ -497,12 +424,7 @@ impl Index { .one_torrent_by_torrent_id(&torrent_listing.torrent_id) .await?; - let category = match torrent_listing.category_id { - Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?), - None => None, - }; - - let torrent_response = TorrentResponse::from_listing(torrent_listing, category); + let torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?; Ok(torrent_response) } @@ -516,6 +438,109 @@ impl Index { let settings = self.configuration.settings.read().await; settings.tracker.mode.clone() } + + async fn build_short_torrent_response( + &self, + torrent_listing: TorrentListing, + info_hash: &InfoHash, + ) -> Result { + let category = match torrent_listing.category_id { + Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?), + None => None, + }; + + let canonical_info_hash_group = self + .torrent_info_hash_repository + .get_canonical_info_hash_group(info_hash) + .await?; + + Ok(TorrentResponse::from_listing( + torrent_listing, + category, + &canonical_info_hash_group, + )) + } + + async fn build_full_torrent_response( + &self, + torrent_listing: TorrentListing, + info_hash: &InfoHash, + opt_user_id: Option, + ) -> Result { + let torrent_id: i64 = torrent_listing.torrent_id; + + let mut torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?; + + // Add files + + torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?; + + if torrent_response.files.len() == 1 { + let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?; + + torrent_response + .files + .iter_mut() + .for_each(|v| v.path = vec![torrent_info.name.to_string()]); + } + + // Add trackers + + // code-review: duplicate logic. We have to check the same in the + // download torrent file endpoint. Here he have only one list of tracker + // like the `announce_list` in the torrent file. + + torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?; + + let tracker_url = self.get_tracker_url().await; + let tracker_mode = self.get_tracker_mode().await; + + if tracker_mode.is_open() { + torrent_response.include_url_as_main_tracker(&tracker_url); + } else { + // Add main tracker URL + match opt_user_id { + Some(user_id) => { + let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?; + + torrent_response.include_url_as_main_tracker(&personal_announce_url); + } + None => { + torrent_response.include_url_as_main_tracker(&tracker_url); + } + } + } + + // Add magnet link + + // todo: extract a struct or function to build the magnet links + let mut magnet = format!( + "magnet:?xt=urn:btih:{}&dn={}", + torrent_response.info_hash, + urlencoding::encode(&torrent_response.title) + ); + + // Add trackers from torrent file to magnet link + for tracker in &torrent_response.trackers { + magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker))); + } + + torrent_response.magnet_link = magnet; + + // Get realtime seeders and leechers + if let Ok(torrent_info) = self + .tracker_statistics_importer + .import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash) + .await + { + torrent_response.seeders = torrent_info.seeders; + torrent_response.leechers = torrent_info.leechers; + } + + torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?; + + Ok(torrent_response) + } } pub struct DbTorrentRepository { @@ -579,6 +604,7 @@ pub struct DbTorrentInfoHash { /// This function returns the original infohashes of a canonical infohash. /// /// The relationship is 1 canonical infohash -> N original infohashes. +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct CanonicalInfoHashGroup { pub canonical_info_hash: InfoHash, /// The list of original infohashes associated to the canonical one. diff --git a/tests/common/contexts/torrent/responses.rs b/tests/common/contexts/torrent/responses.rs index b1ef0882..55857a5b 100644 --- a/tests/common/contexts/torrent/responses.rs +++ b/tests/common/contexts/torrent/responses.rs @@ -72,6 +72,7 @@ pub struct TorrentDetails { pub creation_date: Option, pub created_by: Option, pub encoding: Option, + pub canonical_info_hash_group: Vec, } #[derive(Deserialize, PartialEq, Debug)] diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 6c057d67..1d207b56 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -197,7 +197,7 @@ mod for_guests { md5sum: None, }], // code-review: why is this duplicated? It seems that is adding the - // same tracker twice because first ti adds all trackers and then + // same tracker twice because first it adds all trackers and then // it adds the tracker with the personal announce url, if the user // is logged in. If the user is not logged in, it adds the default // tracker again, and it ends up with two trackers. @@ -215,6 +215,7 @@ mod for_guests { creation_date: test_torrent.file_info.creation_date, created_by: test_torrent.file_info.created_by.clone(), encoding: test_torrent.file_info.encoding.clone(), + canonical_info_hash_group: vec![test_torrent.file_info.info_hash.to_lowercase()], }; assert_expected_torrent_details(&torrent_details_response.data, &expected_torrent); From 3c719b1c7ca127282dee2375990eb4b90a9786e9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 16:30:40 +0000 Subject: [PATCH 125/309] chore: remove deprecated comment That was fixed. --- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 1d207b56..4c947924 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -196,11 +196,6 @@ mod for_guests { length: test_torrent.file_info.content_size, md5sum: None, }], - // code-review: why is this duplicated? It seems that is adding the - // same tracker twice because first it adds all trackers and then - // it adds the tracker with the personal announce url, if the user - // is logged in. If the user is not logged in, it adds the default - // tracker again, and it ends up with two trackers. trackers: vec![tracker_url.clone()], magnet_link: format!( // cspell:disable-next-line From 2abb62a4a6e6c807b71aa5e52d142c226eef5763 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 16:31:34 +0000 Subject: [PATCH 126/309] chore: reset log level to info for developmentWq --- share/default/config/index.development.sqlite3.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 5a4461e0..669979af 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,4 +1,4 @@ -log_level = "debug" +log_level = "info" [website] name = "Torrust" From d50063462efdbbf3811396fc9af7df39e2c5ea63 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 17:25:41 +0000 Subject: [PATCH 127/309] refactor: [#290] specific error for suplidatetorrent with a original infohashes --- src/errors.rs | 4 ++++ src/services/torrent.rs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index 92d5319c..448243c0 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -115,6 +115,9 @@ pub enum ServiceError { #[display(fmt = "A torrent with the same canonical infohash already exists in our database.")] CanonicalInfoHashAlreadyExists, + #[display(fmt = "A torrent with the same original infohash already exists in our database.")] + OriginalInfoHashAlreadyExists, + #[display(fmt = "This torrent title has already been used.")] TorrentTitleAlreadyExists, @@ -297,6 +300,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::Unauthorized => StatusCode::FORBIDDEN, ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::OriginalInfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::TrackerOffline => StatusCode::SERVICE_UNAVAILABLE, ServiceError::CategoryNameEmpty => StatusCode::BAD_REQUEST, diff --git a/src/services/torrent.rs b/src/services/torrent.rs index bc197881..788eec19 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -209,6 +209,8 @@ impl Index { .await?; if !original_info_hashes.is_empty() { + // A previous torrent with the same canonical infohash has been uploaded before + // Torrent with the same canonical infohash was already uploaded debug!("Canonical infohash found: {:?}", canonical_info_hash.to_hex_string()); @@ -216,7 +218,7 @@ impl Index { // The exact original infohash was already uploaded debug!("Original infohash found: {:?}", original_info_hash.to_hex_string()); - return Err(ServiceError::InfoHashAlreadyExists); + return Err(ServiceError::OriginalInfoHashAlreadyExists); } // A new original infohash is being uploaded with a canonical infohash that already exists. @@ -229,6 +231,7 @@ impl Index { return Err(ServiceError::CanonicalInfoHashAlreadyExists); } + // No other torrent with the same canonical infohash has been uploaded before Ok(()) } From 414d468df24998c42c4bbdee3500e34813ce223a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 5 Mar 2024 17:33:17 +0000 Subject: [PATCH 128/309] refactor: [#290] return 4009 intead of 400 trying to upload duplicate torrents Torrents with the same original or canonical infohash. --- src/errors.rs | 4 ++-- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 448243c0..71ff3ab3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -299,8 +299,8 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::InvalidTag => StatusCode::BAD_REQUEST, ServiceError::Unauthorized => StatusCode::FORBIDDEN, ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::OriginalInfoHashAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::CONFLICT, + ServiceError::OriginalInfoHashAlreadyExists => StatusCode::CONFLICT, ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::TrackerOffline => StatusCode::SERVICE_UNAVAILABLE, ServiceError::CategoryNameEmpty => StatusCode::BAD_REQUEST, diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 4c947924..60614deb 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -728,7 +728,7 @@ mod for_authenticated_users { let form: UploadTorrentMultipartForm = first_torrent_clone.index_info.into(); let response = client.upload_torrent(form.into()).await; - assert_eq!(response.status, 400); + assert_eq!(response.status, 409); } #[tokio::test] @@ -761,7 +761,7 @@ mod for_authenticated_users { let form: UploadTorrentMultipartForm = torrent_with_the_same_canonical_info_hash.index_info.into(); let response = client.upload_torrent(form.into()).await; - assert_eq!(response.status, 400); + assert_eq!(response.status, 409); } } From a7f43dc19504f3efb0b3f61cd2350fe34e97e34c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 6 Mar 2024 14:39:33 +0000 Subject: [PATCH 129/309] refactor: rename fields in upload torrent response to match latest changes in the database. From: ```json { "data": { "torrent_id": 12, "info_hash": "c01f910ff0cc2a1b8c7a1f6ab948377604d8c464", "original_info_hash": "dc0c6be689b28696319263ef27f9df4da8ded0a2" } } ``` To: ``` { "data": { "torrent_id": 12, "canonical_info_hash": "c01f910ff0cc2a1b8c7a1f6ab948377604d8c464", "info_hash": "dc0c6be689b28696319263ef27f9df4da8ded0a2" } } ``` --- src/services/torrent.rs | 6 +++--- src/web/api/server/v1/contexts/torrent/responses.rs | 4 ++-- tests/common/contexts/torrent/responses.rs | 1 + tests/e2e/web/api/v1/contexts/torrent/contract.rs | 2 +- tests/e2e/web/api/v1/contexts/torrent/steps.rs | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 788eec19..c3c7e7f0 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -45,8 +45,8 @@ pub struct AddTorrentRequest { pub struct AddTorrentResponse { pub torrent_id: TorrentId, + pub canonical_info_hash: String, pub info_hash: String, - pub original_info_hash: String, } /// User request to generate a torrent listing. @@ -172,8 +172,8 @@ impl Index { Ok(AddTorrentResponse { torrent_id, - info_hash: torrent.canonical_info_hash_hex(), - original_info_hash: original_info_hash.to_string(), + canonical_info_hash: torrent.canonical_info_hash_hex(), + info_hash: original_info_hash.to_string(), }) } diff --git a/src/web/api/server/v1/contexts/torrent/responses.rs b/src/web/api/server/v1/contexts/torrent/responses.rs index d928f675..103e3a85 100644 --- a/src/web/api/server/v1/contexts/torrent/responses.rs +++ b/src/web/api/server/v1/contexts/torrent/responses.rs @@ -11,8 +11,8 @@ use crate::web::api::server::v1::responses::OkResponseData; #[derive(Serialize, Deserialize, Debug)] pub struct NewTorrentResponseData { pub torrent_id: TorrentId, + pub canonical_info_hash: String, pub info_hash: String, - pub original_info_hash: String, } /// Response after successfully uploading a new torrent. @@ -20,8 +20,8 @@ pub fn new_torrent_response(add_torrent_response: &AddTorrentResponse) -> Json } let uploaded_torrent_response: UploadedTorrentResponse = serde_json::from_str(&response.body).unwrap(); - let canonical_info_hash_hex = uploaded_torrent_response.data.info_hash.to_lowercase(); + let canonical_info_hash_hex = uploaded_torrent_response.data.canonical_info_hash.to_lowercase(); let canonical_info_hash = InfoHash::from_str(&canonical_info_hash_hex) .unwrap_or_else(|_| panic!("Invalid info-hash in database: {canonical_info_hash_hex}")); From bd935b337c2e7a583cda626840e5bdb2e2bd3e86 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 8 Mar 2024 15:03:22 +0000 Subject: [PATCH 130/309] fix: [#526] DB migration for SQLite. Backup secondary tables The table `torrust_torrents` has 8 secondary tables that reference it with a foreign key: ```sql FOREIGN KEY("torrent_id") REFERENCES "torrust_torrents"("torrent_id") ON DELETE CASCADE, ``` The migration fixed in this commit creates a new table in order to alter it and drops the old one. However, when you drop the old table, SQLite uses an implicit DELETE query for the `torrust_torrents` table which triggers a DELETE ON CASCADE, deleting all the related records in secondary tables. The secondary tables are: - torrust_torrent_files - torrust_torrent_announce_urls - torrust_torrent_info - torrust_torrent_tracker_stats - torrust_torrent_tag_links - torrust_torrent_info_hashes - torrust_torrent_http_seeds - torrust_torrent_nodes These tables store the torrent file fiel together with the master `torrust_torrents`. --- ...orrust_add_independent_root_hash_field.sql | 97 +++++++++++++++++-- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql b/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql index 0bec861f..b792d0f4 100644 --- a/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql +++ b/migrations/sqlite3/20240305120015_torrust_add_independent_root_hash_field.sql @@ -1,6 +1,68 @@ --- add field `root_hash` and make `pieces` nullable +PRAGMA foreign_keys = off; + +-- Step 1: backup secondary tables. They will be truncated because of the DELETE ON CASCADE +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_files_backup" ( + "file_id" INTEGER NOT NULL, + "torrent_id" INTEGER NOT NULL, + "md5sum" TEXT DEFAULT NULL, + "length" BIGINT NOT NULL, + "path" TEXT DEFAULT NULL +); +INSERT INTO torrust_torrent_files_backup SELECT * FROM torrust_torrent_files; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_announce_urls_backup" ( + "announce_url_id" INTEGER NOT NULL, + "torrent_id" INTEGER NOT NULL, + "tracker_url" TEXT NOT NULL +); +INSERT INTO torrust_torrent_announce_urls_backup SELECT * FROM torrust_torrent_announce_urls; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_info_backup" ( + "torrent_id" INTEGER NOT NULL, + "title" VARCHAR(256) NOT NULL UNIQUE, + "description" TEXT DEFAULT NULL +); +INSERT INTO torrust_torrent_info_backup SELECT * FROM torrust_torrent_info; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_tracker_stats_backup" ( + "torrent_id" INTEGER NOT NULL, + "tracker_url" VARCHAR(256) NOT NULL, + "seeders" INTEGER NOT NULL DEFAULT 0, + "leechers" INTEGER NOT NULL DEFAULT 0 +); +INSERT INTO torrust_torrent_tracker_stats_backup SELECT * FROM torrust_torrent_tracker_stats; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_tag_links_backup" ( + "torrent_id" INTEGER NOT NULL, + "tag_id" INTEGER NOT NULL +); +INSERT INTO torrust_torrent_tag_links_backup SELECT * FROM torrust_torrent_tag_links; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_info_hashes_backup" ( + "info_hash" TEXT NOT NULL, + "canonical_info_hash" TEXT NOT NULL, + "original_is_known" BOOLEAN NOT NULL +); +INSERT INTO torrust_torrent_info_hashes_backup SELECT * FROM torrust_torrent_info_hashes; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_http_seeds_backup" ( + "http_seed_id" INTEGER NOT NULL, + "torrent_id" INTEGER NOT NULL, + "seed_url" TEXT NOT NULL +); +INSERT INTO torrust_torrent_http_seeds_backup SELECT * FROM torrust_torrent_http_seeds; + +CREATE TEMPORARY TABLE IF NOT EXISTS "torrust_torrent_nodes_backup" ( + "node_id" INTEGER NOT NULL, + "torrent_id" INTEGER NOT NULL, + "node_ip" TEXT NOT NULL, + "node_port" INTEGER NOT NULL +); +INSERT INTO torrust_torrent_nodes_backup SELECT * FROM torrust_torrent_nodes; + +-- Step 2: Add field `root_hash` and make `pieces` nullable CREATE TABLE - "torrust_torrents_new" ( + IF NOT EXISTS "torrust_torrents_new" ( "torrent_id" INTEGER NOT NULL, "uploader_id" INTEGER NOT NULL, "category_id" INTEGER, @@ -23,7 +85,7 @@ CREATE TABLE PRIMARY KEY ("torrent_id" AUTOINCREMENT) ); --- Step 2: Copy data from the old table to the new table +-- Step 3: Copy data from the old table to the new table INSERT INTO torrust_torrents_new ( torrent_id, @@ -69,9 +131,30 @@ SELECT FROM torrust_torrents; --- Step 3: Drop the old table +-- Step 4: Drop the old table DROP TABLE torrust_torrents; --- Step 4: Rename the new table to the original name -ALTER TABLE torrust_torrents_new -RENAME TO torrust_torrents; \ No newline at end of file +-- Step 5: Rename the new table to the original name +ALTER TABLE torrust_torrents_new RENAME TO torrust_torrents; + +-- Step 6: Repopulate secondary tables from backup tables +INSERT INTO torrust_torrent_files SELECT * FROM torrust_torrent_files_backup; +INSERT INTO torrust_torrent_announce_urls SELECT * FROM torrust_torrent_announce_urls_backup; +INSERT INTO torrust_torrent_info SELECT * FROM torrust_torrent_info_backup; +INSERT INTO torrust_torrent_tracker_stats SELECT * FROM torrust_torrent_tracker_stats_backup; +INSERT INTO torrust_torrent_tag_links SELECT * FROM torrust_torrent_tag_links_backup; +INSERT INTO torrust_torrent_info_hashes SELECT * FROM torrust_torrent_info_hashes_backup; +INSERT INTO torrust_torrent_http_seeds SELECT * FROM torrust_torrent_http_seeds_backup; +INSERT INTO torrust_torrent_nodes SELECT * FROM torrust_torrent_nodes_backup; + +-- Step 7: Drop temporary secondary table backups +DROP TABLE torrust_torrent_files_backup; +DROP TABLE torrust_torrent_announce_urls_backup; +DROP TABLE torrust_torrent_info_backup; +DROP TABLE torrust_torrent_tracker_stats_backup; +DROP TABLE torrust_torrent_tag_links_backup; +DROP TABLE torrust_torrent_info_hashes_backup; +DROP TABLE torrust_torrent_http_seeds_backup; +DROP TABLE torrust_torrent_nodes_backup; + +PRAGMA foreign_keys = on; \ No newline at end of file From 094ab8a54303e2b4825a7ae3629c70380bde5bb1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 8 Mar 2024 15:39:58 +0000 Subject: [PATCH 131/309] fix: linter errors --- src/config.rs | 8 ++++---- src/models/torrent_file.rs | 2 +- tests/e2e/environment.rs | 8 ++++---- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7335c1eb..7c8092c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -430,10 +430,10 @@ impl TorrustIndex { } pub fn remove_secrets(&mut self) { - self.tracker.token = "***".to_owned(); - self.database.connect_url = "***".to_owned(); - self.mail.password = "***".to_owned(); - self.auth.secret_key = "***".to_owned(); + "***".clone_into(&mut self.tracker.token); + "***".clone_into(&mut self.database.connect_url); + "***".clone_into(&mut self.mail.password); + "***".clone_into(&mut self.auth.secret_key); } } diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index e0d44b7c..1bbd7157 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -291,7 +291,7 @@ impl TorrentInfoDictionary { .first() .expect("vector `torrent_files` should have at least one element"); - info_dict.md5sum = torrent_file.md5sum.clone(); + info_dict.md5sum.clone_from(&torrent_file.md5sum); // DevSkim: ignore DS126858 info_dict.length = Some(torrent_file.length); diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 87797a08..40840953 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -90,10 +90,10 @@ impl TestEnv { pub fn server_settings_masking_secrets(&self) -> Option { match self.starting_settings.clone() { Some(mut settings) => { - settings.tracker.token = "***".to_owned(); - settings.database.connect_url = "***".to_owned(); - settings.mail.password = "***".to_owned(); - settings.auth.secret_key = "***".to_owned(); + "***".clone_into(&mut settings.tracker.token); + "***".clone_into(&mut settings.database.connect_url); + "***".clone_into(&mut settings.mail.password); + "***".clone_into(&mut settings.auth.secret_key); Some(settings) } None => None, diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 92083076..03ef94fb 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -238,7 +238,7 @@ mod for_guests { // Upload the first torrent let mut first_torrent = TestTorrent::with_custom_info_dict_field(id, &file_contents, "custom 01"); - first_torrent.index_info.title = title.clone(); + first_torrent.index_info.title.clone_from(&title); let first_torrent_canonical_info_hash = upload_test_torrent(&client, &first_torrent) .await @@ -376,7 +376,7 @@ mod for_guests { // Upload the first torrent let mut first_torrent = TestTorrent::with_custom_info_dict_field(id, &file_contents, "custom 01"); - first_torrent.index_info.title = title.clone(); + first_torrent.index_info.title.clone_from(&title); let first_torrent_canonical_info_hash = upload_test_torrent(&client, &first_torrent) .await From 0df72de470e0758fdabc0b4c4d419c348eebfce5 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 12 Mar 2024 15:47:21 +0100 Subject: [PATCH 132/309] chore(deps): bump clap from 4.5.1 to 4.5.2 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69b2752a..eac6dbb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,9 +433,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -443,9 +443,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index ad1beb59..bb25a919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ axum = { version = "0", features = ["multipart"] } binascii = "0" bytes = "1" chrono = { version = "0", default-features = false, features = ["clock"] } -clap = { version = "4.4.18", features = ["derive", "env"]} +clap = { version = "4.5.2", features = ["derive", "env"]} config = "0" derive_more = "0" email_address = "0" From 7183953c734c24c10cfc0ce3b921350d67f4193d Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 12 Mar 2024 16:20:31 +0100 Subject: [PATCH 133/309] chore: bump http from 1.0.0 to 1.1.0 --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eac6dbb3..e0393830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.2.0", @@ -255,7 +255,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "mime", @@ -1078,7 +1078,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 1.0.0", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -1183,9 +1183,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1210,7 +1210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.0.0", + "http 1.1.0", ] [[package]] @@ -1221,7 +1221,7 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" dependencies = [ "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "pin-project-lite", ] @@ -1272,7 +1272,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.2", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "httparse", "httpdate", @@ -1303,7 +1303,7 @@ checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "hyper 1.2.0", "pin-project-lite", @@ -1628,7 +1628,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.0.0", + "http 1.1.0", "httparse", "log", "memchr", @@ -3237,7 +3237,7 @@ dependencies = [ "fern", "futures", "hex", - "http 1.0.0", + "http 1.1.0", "hyper 1.2.0", "indexmap", "jsonwebtoken", @@ -3308,7 +3308,7 @@ dependencies = [ "bitflags 2.4.2", "bytes", "futures-core", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index bb25a919..37e794b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ email_address = "0" fern = "0" futures = "0" hex = "0" -http = "1.0.0" +http = "1.1.0" hyper = "1" indexmap = "2" jsonwebtoken = "9" From 7148cf11f64a63322acdece4a86ca02a03a2df08 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 12 Mar 2024 16:41:20 +0100 Subject: [PATCH 134/309] chore: bump indexmap from 2.2.3 to 2.2.5 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0393830..5dbe1758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1362,9 +1362,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown 0.14.3", From d7e32871f1b72375622989d8a12fc5182d48e2a8 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 12 Mar 2024 17:46:02 +0100 Subject: [PATCH 135/309] chore: bump log from 0.4.20 to 0.4.21 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5dbe1758..98645a4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,9 +1529,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "match_cfg" From 67f82b06b6ab119ce488863cb540302cb9a7e2a3 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 12 Mar 2024 18:06:54 +0100 Subject: [PATCH 136/309] chore: bump which from 5.0.0 to 6.0.0 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98645a4d..a1cb6877 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3710,15 +3710,15 @@ dependencies = [ [[package]] name = "which" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" dependencies = [ "either", "home", "once_cell", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 37e794b0..0a86c23c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,4 +85,4 @@ uuid = { version = "1", features = ["v4"] } [dev-dependencies] tempfile = "3" uuid = { version = "1", features = ["v4"] } -which = "5" +which = "6" From feffd09a03c80620cd78cff6f0f6d6deba0bc700 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 12 Mar 2024 15:59:59 +0000 Subject: [PATCH 137/309] feat: [#469] add update datetime for tracker stasts importation Add a new column to the table `torrust_torrent_tracker_stats` with the datetime of the update date, which means the date when the stats (leechers and seeders) were synchronized from the tracker API. --- ...130530_torrust_add_update_data_to_tracker_stats.sql | 4 ++++ ...130530_torrust_add_update_data_to_tracker_stats.sql | 2 ++ src/databases/mysql.rs | 5 +++-- src/databases/sqlite.rs | 5 +++-- src/utils/clock.rs | 10 ++++++++++ 5 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 migrations/mysql/20240312130530_torrust_add_update_data_to_tracker_stats.sql create mode 100644 migrations/sqlite3/20240312130530_torrust_add_update_data_to_tracker_stats.sql diff --git a/migrations/mysql/20240312130530_torrust_add_update_data_to_tracker_stats.sql b/migrations/mysql/20240312130530_torrust_add_update_data_to_tracker_stats.sql new file mode 100644 index 00000000..76b306de --- /dev/null +++ b/migrations/mysql/20240312130530_torrust_add_update_data_to_tracker_stats.sql @@ -0,0 +1,4 @@ +-- New field to track when stats were updated from the tracker +ALTER TABLE torrust_torrent_tracker_stats ADD COLUMN updated_at DATETIME DEFAULT NULL; +UPDATE torrust_torrent_tracker_stats SET updated_at = '1000-01-01 00:00:00'; +ALTER TABLE torrust_torrent_tracker_stats MODIFY COLUMN updated_at DATETIME NOT NULL; \ No newline at end of file diff --git a/migrations/sqlite3/20240312130530_torrust_add_update_data_to_tracker_stats.sql b/migrations/sqlite3/20240312130530_torrust_add_update_data_to_tracker_stats.sql new file mode 100644 index 00000000..b376d945 --- /dev/null +++ b/migrations/sqlite3/20240312130530_torrust_add_update_data_to_tracker_stats.sql @@ -0,0 +1,2 @@ +-- New field to track when stats were updated from the tracker +ALTER TABLE torrust_torrent_tracker_stats ADD COLUMN updated_at TEXT DEFAULT "1000-01-01 00:00:00"; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index b964a1e8..5ee723cd 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -20,7 +20,7 @@ use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; -use crate::utils::clock; +use crate::utils::clock::{self, datetime_now}; use crate::utils::hex::from_bytes; pub struct Mysql { @@ -1055,11 +1055,12 @@ impl Database for Mysql { seeders: i64, leechers: i64, ) -> Result<(), database::Error> { - query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers) VALUES (?, ?, ?, ?)") + query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES (?, ?, ?, ?, ?)") .bind(torrent_id) .bind(tracker_url) .bind(seeders) .bind(leechers) + .bind(datetime_now()) .execute(&self.pool) .await .map(|_| ()) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 18b799f3..26d90eea 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -20,7 +20,7 @@ use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; -use crate::utils::clock; +use crate::utils::clock::{self, datetime_now}; use crate::utils::hex::from_bytes; pub struct Sqlite { @@ -1047,11 +1047,12 @@ impl Database for Sqlite { seeders: i64, leechers: i64, ) -> Result<(), database::Error> { - query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers) VALUES ($1, $2, $3, $4)") + query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(torrent_id) .bind(tracker_url) .bind(seeders) .bind(leechers) + .bind(datetime_now()) .execute(&self.pool) .await .map(|_| ()) diff --git a/src/utils/clock.rs b/src/utils/clock.rs index b17ee48b..338a5a61 100644 --- a/src/utils/clock.rs +++ b/src/utils/clock.rs @@ -1,3 +1,5 @@ +use chrono::Utc; + /// Returns the current timestamp in seconds. /// /// # Panics @@ -8,3 +10,11 @@ pub fn now() -> u64 { u64::try_from(chrono::prelude::Utc::now().timestamp()).expect("timestamp should be positive") } + +/// Returns the current time in database format. +/// +/// For example: `2024-03-12 15:56:24`. +#[must_use] +pub fn datetime_now() -> String { + Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() +} From 16cbea819e6099e4767b9cc01261a31155f28eb3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 12 Mar 2024 19:58:39 +0000 Subject: [PATCH 138/309] feat: [#469] import torrent statistics in batches Instead of importing all torrents statistics from the tracker every hour, this change imports 50 torrents every 100 milliseconds. Batches only include torrents that have not been updated in the last hour (or whatever is the `torrent_info_update_interval` value in the configuration). This change avoid loading the whole set of torrents in memory every time the importation starts. In the future, It could also allow to handle the nunber of request per second to the tracker (statically, from config value or dinamically, depending on the tracler load). Althought it saves memory, it runs more SQL queries to get the list of torrents pending to update. --- .../cronjobs/tracker_statistics_importer.rs | 48 +++++++++++---- src/databases/database.rs | 9 ++- src/databases/mysql.rs | 25 +++++++- src/databases/sqlite.rs | 25 +++++++- src/tracker/statistics_importer.rs | 58 ++++++++++++++++++- src/utils/clock.rs | 12 +++- 6 files changed, 156 insertions(+), 21 deletions(-) diff --git a/src/console/cronjobs/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs index 0d32ba34..52ab20bd 100644 --- a/src/console/cronjobs/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -17,12 +17,14 @@ use axum::extract::State; use axum::routing::{get, post}; use axum::{Json, Router}; use chrono::{DateTime, Utc}; -use log::{error, info}; +use log::{debug, error, info}; use serde_json::{json, Value}; +use text_colorizer::Colorize; use tokio::net::TcpListener; use tokio::task::JoinHandle; use crate::tracker::statistics_importer::StatisticsImporter; +use crate::utils::clock::seconds_ago_utc; const IMPORTER_API_IP: &str = "127.0.0.1"; @@ -41,7 +43,7 @@ struct ImporterState { #[must_use] pub fn start( importer_port: u16, - torrent_info_update_interval: u64, + torrent_stats_update_interval: u64, tracker_statistics_importer: &Arc, ) -> JoinHandle<()> { let weak_tracker_statistics_importer = Arc::downgrade(tracker_statistics_importer); @@ -54,7 +56,7 @@ pub fn start( let _importer_api_handle = tokio::spawn(async move { let import_state = Arc::new(ImporterState { last_heartbeat: Arc::new(Mutex::new(Utc::now())), - torrent_info_update_interval, + torrent_info_update_interval: torrent_stats_update_interval, }); let app = Router::new() @@ -81,25 +83,47 @@ pub fn start( info!("Tracker statistics importer cronjob starting ..."); - let interval = std::time::Duration::from_secs(torrent_info_update_interval); - let mut interval = tokio::time::interval(interval); + let execution_interval_in_milliseconds = 100; + let execution_interval_duration = std::time::Duration::from_millis(execution_interval_in_milliseconds); + let mut execution_interval = tokio::time::interval(execution_interval_duration); - interval.tick().await; // first tick is immediate... + execution_interval.tick().await; // first tick is immediate... - loop { - interval.tick().await; - - info!("Running tracker statistics importer ..."); + info!("Running tracker statistics importer every {execution_interval_in_milliseconds} milliseconds ..."); + loop { if let Err(e) = send_heartbeat(importer_port).await { error!("Failed to send heartbeat from importer cronjob: {}", e); } - if let Some(tracker) = weak_tracker_statistics_importer.upgrade() { - drop(tracker.import_all_torrents_statistics().await); + if let Some(statistics_importer) = weak_tracker_statistics_importer.upgrade() { + let one_interval_ago = seconds_ago_utc( + torrent_stats_update_interval + .try_into() + .expect("update interval should be a positive integer"), + ); + let limit = 50; + + debug!( + "Importing torrents statistics not updated since {} limited to a maximum of {} torrents ...", + one_interval_ago.to_string().yellow(), + limit.to_string().yellow() + ); + + match statistics_importer + .import_torrents_statistics_not_updated_since(one_interval_ago, limit) + .await + { + Ok(()) => {} + Err(e) => error!("Failed to import statistics: {:?}", e), + } + + drop(statistics_importer); } else { break; } + + execution_interval.tick().await; } }) } diff --git a/src/databases/database.rs b/src/databases/database.rs index 2d56e22f..a19970be 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::databases::mysql::Mysql; @@ -292,6 +292,13 @@ pub trait Database: Sync + Send { /// Get all torrents as `Vec`. async fn get_all_torrents_compact(&self) -> Result, Error>; + /// Get torrents whose stats have not been imported from the tracker at least since a given datetime. + async fn get_torrents_with_stats_not_updated_since( + &self, + datetime: DateTime, + limit: i64, + ) -> Result, Error>; + /// Update a torrent's title with `torrent_id` and `title`. async fn update_torrent_title(&self, torrent_id: i64, title: &str) -> Result<(), Error>; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 5ee723cd..d28f1ae7 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::time::Duration; use async_trait::async_trait; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool}; @@ -20,7 +20,7 @@ use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; -use crate::utils::clock::{self, datetime_now}; +use crate::utils::clock::{self, datetime_now, DATETIME_FORMAT}; use crate::utils::hex::from_bytes; pub struct Mysql { @@ -884,6 +884,27 @@ impl Database for Mysql { .map_err(|_| database::Error::Error) } + async fn get_torrents_with_stats_not_updated_since( + &self, + datetime: DateTime, + limit: i64, + ) -> Result, database::Error> { + query_as::<_, TorrentCompact>( + "SELECT tt.torrent_id, tt.info_hash + FROM torrust_torrents tt + LEFT JOIN torrust_torrent_tracker_stats tts ON tt.torrent_id = tts.torrent_id + WHERE tts.updated_at < ? OR tts.updated_at IS NULL + ORDER BY tts.updated_at ASC + LIMIT ? + ", + ) + .bind(datetime.format(DATETIME_FORMAT).to_string()) + .bind(limit) + .fetch_all(&self.pool) + .await + .map_err(|_| database::Error::Error) + } + async fn update_torrent_title(&self, torrent_id: i64, title: &str) -> Result<(), database::Error> { query("UPDATE torrust_torrent_info SET title = ? WHERE torrent_id = ?") .bind(title) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 26d90eea..421292d6 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::time::Duration; use async_trait::async_trait; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool}; @@ -20,7 +20,7 @@ use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; -use crate::utils::clock::{self, datetime_now}; +use crate::utils::clock::{self, datetime_now, DATETIME_FORMAT}; use crate::utils::hex::from_bytes; pub struct Sqlite { @@ -876,6 +876,27 @@ impl Database for Sqlite { .map_err(|_| database::Error::Error) } + async fn get_torrents_with_stats_not_updated_since( + &self, + datetime: DateTime, + limit: i64, + ) -> Result, database::Error> { + query_as::<_, TorrentCompact>( + "SELECT tt.torrent_id, tt.info_hash + FROM torrust_torrents tt + LEFT JOIN torrust_torrent_tracker_stats tts ON tt.torrent_id = tts.torrent_id + WHERE tts.updated_at < ? OR tts.updated_at IS NULL + ORDER BY tts.updated_at ASC + LIMIT ? + ", + ) + .bind(datetime.format(DATETIME_FORMAT).to_string()) + .bind(limit) + .fetch_all(&self.pool) + .await + .map_err(|_| database::Error::Error) + } + async fn update_torrent_title(&self, torrent_id: i64, title: &str) -> Result<(), database::Error> { query("UPDATE torrust_torrent_info SET title = $1 WHERE torrent_id = $2") .bind(title) diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index 996008f3..d9030e39 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use std::time::Instant; -use log::{error, info}; +use chrono::{DateTime, Utc}; +use log::{debug, error, info}; use text_colorizer::Colorize; use super::service::{Service, TorrentInfo, TrackerAPIError}; @@ -36,13 +37,17 @@ impl StatisticsImporter { pub async fn import_all_torrents_statistics(&self) -> Result<(), database::Error> { let torrents = self.database.get_all_torrents_compact().await?; + if torrents.is_empty() { + return Ok(()); + } + info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); // Start the timer before the loop let start_time = Instant::now(); for torrent in torrents { - info!(target: LOG_TARGET, "Importing torrent #{} ...", torrent.torrent_id.to_string().yellow()); + info!(target: LOG_TARGET, "Importing torrent #{} statistics ...", torrent.torrent_id.to_string().yellow()); let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; @@ -64,6 +69,55 @@ impl StatisticsImporter { Ok(()) } + /// Import torrents statistics not updated recently.. + /// + /// # Errors + /// + /// Will return an error if the database query failed. + pub async fn import_torrents_statistics_not_updated_since( + &self, + datetime: DateTime, + limit: i64, + ) -> Result<(), database::Error> { + debug!(target: LOG_TARGET, "Importing torrents statistics not updated since {} limited to a maximum of {} torrents ...", datetime.to_string().yellow(), limit.to_string().yellow()); + + let torrents = self + .database + .get_torrents_with_stats_not_updated_since(datetime, limit) + .await?; + + if torrents.is_empty() { + return Ok(()); + } + + info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); + + // Start the timer before the loop + let start_time = Instant::now(); + + for torrent in torrents { + info!(target: LOG_TARGET, "Importing torrent #{} statistics ...", torrent.torrent_id.to_string().yellow()); + + let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; + + if let Some(err) = ret.err() { + if err != TrackerAPIError::TorrentNotFound { + let message = format!( + "Error updating torrent tracker stats for torrent. Torrent: id {}; infohash {}. Error: {:?}", + torrent.torrent_id, torrent.info_hash, err + ); + error!(target: LOG_TARGET, "{}", message); + } + } + } + + let elapsed_time = start_time.elapsed(); + + info!(target: LOG_TARGET, "Statistics import completed in {:.2?}", elapsed_time); + + Ok(()) + } + /// Import torrent statistics from tracker and update them in database. /// /// # Errors diff --git a/src/utils/clock.rs b/src/utils/clock.rs index 338a5a61..42269eeb 100644 --- a/src/utils/clock.rs +++ b/src/utils/clock.rs @@ -1,4 +1,6 @@ -use chrono::Utc; +use chrono::{DateTime, Duration, Utc}; + +pub const DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; /// Returns the current timestamp in seconds. /// @@ -11,10 +13,16 @@ pub fn now() -> u64 { u64::try_from(chrono::prelude::Utc::now().timestamp()).expect("timestamp should be positive") } +/// Returns the datetime some seconds ago. +#[must_use] +pub fn seconds_ago_utc(seconds: i64) -> DateTime { + Utc::now() - Duration::seconds(seconds) +} + /// Returns the current time in database format. /// /// For example: `2024-03-12 15:56:24`. #[must_use] pub fn datetime_now() -> String { - Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + Utc::now().format(DATETIME_FORMAT).to_string() } From af7150b6543832e7a10582d9a1f9d85db222e7d5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 13 Mar 2024 16:36:22 +0000 Subject: [PATCH 139/309] feat: [#469] import torrent stats using multiple torrents tracker API endpoint There is a new feature inthe tracker API where you can get stats for a list of infohashes: This way you can get many torrents stasts in one request. This commit replaces the statistics importer to use this new endpoint feature. --- .../cronjobs/tracker_statistics_importer.rs | 9 +++ src/tracker/api.rs | 25 ++++++++- src/tracker/service.rs | 56 +++++++++++++++++++ src/tracker/statistics_importer.rs | 50 ++++++++++++----- 4 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/console/cronjobs/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs index 52ab20bd..970fd7ca 100644 --- a/src/console/cronjobs/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -83,6 +83,15 @@ pub fn start( info!("Tracker statistics importer cronjob starting ..."); + // code-review: we set an execution interval to avoid intense polling to + // the database. If we remove the interval we would be constantly + // queering if there are torrent stats pending to update, unless there + // are torrents to update. Maybe we should only sleep for 100 milliseconds + // if we did not update any torrents in the latest execution. + // With this current limit we can only import 50 torrent stats every 100 + // milliseconds which is 500 torrents per second (1800000 torrents per hour). + // If the tracker can handle a request in 100 milliseconds. + let execution_interval_in_milliseconds = 100; let execution_interval_duration = std::time::Duration::from_millis(execution_interval_in_milliseconds); let mut execution_interval = tokio::time::interval(execution_interval_duration); diff --git a/src/tracker/api.rs b/src/tracker/api.rs index d3c00188..c81a745e 100644 --- a/src/tracker/api.rs +++ b/src/tracker/api.rs @@ -15,6 +15,8 @@ impl ConnectionInfo { } } +const TOKEN_PARAM_NAME: &str = "token"; + pub struct Client { pub connection_info: ConnectionInfo, api_base_url: String, @@ -29,7 +31,7 @@ impl Client { pub fn new(connection_info: ConnectionInfo) -> Result { let base_url = format!("{}/api/v1", connection_info.url); let client = reqwest::Client::builder().timeout(Duration::from_secs(5)).build()?; - let token_param = [("token".to_string(), connection_info.token.to_string())]; + let token_param = [(TOKEN_PARAM_NAME.to_string(), connection_info.token.to_string())]; Ok(Self { connection_info, @@ -72,7 +74,7 @@ impl Client { self.client.post(request_url).query(&self.token_param).send().await } - /// Retrieve the info for a torrent. + /// Retrieve the info for one torrent. /// /// # Errors /// @@ -82,4 +84,23 @@ impl Client { self.client.get(request_url).query(&self.token_param).send().await } + + /// Retrieve the info for multiple torrents at the same time. + /// + /// # Errors + /// + /// Will return an error if the HTTP request fails. + pub async fn get_torrents_info(&self, info_hashes: &[String]) -> Result { + let request_url = format!("{}/torrents", self.api_base_url); + + let mut query_params: Vec<(String, String)> = Vec::with_capacity(info_hashes.len() + 1); + + query_params.push((TOKEN_PARAM_NAME.to_string(), self.connection_info.token.clone())); + + for info_hash in info_hashes { + query_params.push(("info_hash".to_string(), info_hash.clone())); + } + + self.client.get(request_url).query(&query_params).send().await + } } diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 598e35fd..3036ce89 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -48,6 +48,14 @@ pub struct TorrentInfo { pub peers: Vec, } +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct TorrentBasicInfo { + pub info_hash: String, + pub seeders: i64, + pub completed: i64, + pub leechers: i64, +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Peer { pub peer_id: Option, @@ -259,6 +267,54 @@ impl Service { } } + /// Get torrent info from tracker in batches. + /// + /// # Errors + /// + /// Will return an error if the HTTP request to get torrent info fails or + /// if the response cannot be parsed. + pub async fn get_torrents_info(&self, info_hashes: &[String]) -> Result, TrackerAPIError> { + debug!(target: "tracker-service", "get torrents info"); + + let maybe_response = self.api_client.get_torrents_info(info_hashes).await; + + debug!(target: "tracker-service", "get torrents info response result: {:?}", maybe_response); + + match maybe_response { + Ok(response) => { + let status: StatusCode = map_status_code(response.status()); + + let body = response.text().await.map_err(|_| { + error!(target: "tracker-service", "response without body"); + TrackerAPIError::MissingResponseBody + })?; + + match status { + StatusCode::OK => serde_json::from_str(&body).map_err(|e| { + error!( + target: "tracker-service", "Failed to parse torrents info from tracker response. Body: {}, Error: {}", + body, e + ); + TrackerAPIError::FailedToParseTrackerResponse { body } + }), + StatusCode::INTERNAL_SERVER_ERROR => { + if body == Self::invalid_token_body() { + Err(TrackerAPIError::InvalidToken) + } else { + error!(target: "tracker-service", "get torrents info 500 response: status {status}, body: {body}"); + Err(TrackerAPIError::InternalServerError) + } + } + _ => { + error!(target: "tracker-service", "get torrents info unhandled response: status {status}, body: {body}"); + Err(TrackerAPIError::UnexpectedResponseStatus) + } + } + } + Err(_) => Err(TrackerAPIError::TrackerOffline), + } + } + /// Issue a new tracker key from tracker. async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result { debug!(target: "tracker-service", "retrieve key: {user_id}"); diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index d9030e39..b9842855 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -58,6 +58,7 @@ impl StatisticsImporter { torrent.torrent_id, torrent.info_hash, err ); error!(target: "statistics_importer", "{}", message); + // todo: return a service error that can be a tracker API error or a database error. } } } @@ -92,29 +93,48 @@ impl StatisticsImporter { info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); - // Start the timer before the loop - let start_time = Instant::now(); + // Import stats for all torrents in one request - for torrent in torrents { - info!(target: LOG_TARGET, "Importing torrent #{} statistics ...", torrent.torrent_id.to_string().yellow()); + let info_hashes: Vec = torrents.iter().map(|t| t.info_hash.clone()).collect(); - let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await; + let torrent_info_vec = match self.tracker_service.get_torrents_info(&info_hashes).await { + Ok(torrents_info) => torrents_info, + Err(err) => { + let message = format!("Error getting torrents tracker stats. Error: {err:?}"); + error!(target: LOG_TARGET, "{}", message); + // todo: return a service error that can be a tracker API error or a database error. + return Ok(()); + } + }; - if let Some(err) = ret.err() { - if err != TrackerAPIError::TorrentNotFound { - let message = format!( - "Error updating torrent tracker stats for torrent. Torrent: id {}; infohash {}. Error: {:?}", - torrent.torrent_id, torrent.info_hash, err + // Update stats for all torrents + + for torrent in torrents { + match torrent_info_vec.iter().find(|t| t.info_hash == torrent.info_hash) { + None => { + // No stats for this torrent in the tracker + drop( + self.database + .update_tracker_info(torrent.torrent_id, &self.tracker_url, 0, 0) + .await, + ); + } + Some(torrent_info) => { + // Update torrent stats for this tracker + drop( + self.database + .update_tracker_info( + torrent.torrent_id, + &self.tracker_url, + torrent_info.seeders, + torrent_info.leechers, + ) + .await, ); - error!(target: LOG_TARGET, "{}", message); } } } - let elapsed_time = start_time.elapsed(); - - info!(target: LOG_TARGET, "Statistics import completed in {:.2?}", elapsed_time); - Ok(()) } From 6e6f4f7f228463b5b35a9a32414519a4fd65d839 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 13 Mar 2024 15:16:26 +0100 Subject: [PATCH 140/309] chore: bump mio from 0.8.10 to 0.8.11 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1cb6877..7ff340cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1610,9 +1610,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", From a011158bfed63e724cdf5f6821f6ab17965405b8 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 13 Mar 2024 17:25:50 +0100 Subject: [PATCH 141/309] chore: bump chrono from 0.4.34 to 0.4.35 Minor refactoring to update deprecated functions calls --- Cargo.lock | 4 ++-- .../from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ff340cb..819206df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,9 +411,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs index f902c859..13e7d023 100644 --- a/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs +++ b/src/upgrades/from_v1_0_0_to_v2_0_0/databases/sqlite_v2_0_0.rs @@ -1,6 +1,6 @@ #![allow(clippy::missing_errors_doc)] -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::DateTime; use serde::{Deserialize, Serialize}; use sqlx::sqlite::{SqlitePoolOptions, SqliteQueryResult}; use sqlx::{query, query_as, SqlitePool}; @@ -61,11 +61,10 @@ pub fn convert_timestamp_to_datetime(timestamp: i64) -> String { // The expected format in database is: 2022-11-04 09:53:57 // MySQL uses a DATETIME column and SQLite uses a TEXT column. - let naive_datetime = NaiveDateTime::from_timestamp_opt(timestamp, 0).expect("Overflow of i64 seconds, very future!"); - let datetime_again: DateTime = DateTime::from_naive_utc_and_offset(naive_datetime, Utc); + let datetime = DateTime::from_timestamp(timestamp, 0).expect("Overflow of i64 seconds, very future!"); // Format without timezone - datetime_again.format("%Y-%m-%d %H:%M:%S").to_string() + datetime.format("%Y-%m-%d %H:%M:%S").to_string() } pub struct SqliteDatabaseV2_0_0 { From c7ada2e66d2734155b51f3ec922a68c12201b25e Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 13 Mar 2024 17:58:54 +0100 Subject: [PATCH 142/309] chore: bump reqwest from 0.11.24 to 0.11.26 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 819206df..2e55503a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2146,9 +2146,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" dependencies = [ "base64 0.21.7", "bytes", From 2b6654f006c113b4c0d61fcffb16b34e09b4d6b1 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 14 Mar 2024 00:37:45 +0100 Subject: [PATCH 143/309] chore: bump sqlx from 0.7.3 to 0.7.4 --- Cargo.lock | 52 ++++++++++++++-------------------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e55503a..d0cbf1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,16 +195,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic-write-file" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix", - "rand", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -1655,17 +1645,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -2672,9 +2651,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2685,9 +2664,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", @@ -2695,7 +2674,6 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -2727,9 +2705,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -2740,11 +2718,10 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ - "atomic-write-file", "dotenvy", "either", "heck", @@ -2767,9 +2744,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", @@ -2810,9 +2787,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", @@ -2837,7 +2814,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -2850,9 +2826,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "flume", From dcb0f59727f991524338319a442daf2d66860ee3 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 14 Mar 2024 00:47:15 +0100 Subject: [PATCH 144/309] chore: bump anyhow from 1.0.80 to 1.0.81 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0cbf1fe..6e10e7c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "argon2" diff --git a/Cargo.toml b/Cargo.toml index 0a86c23c..97f5b133 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ version = "3.0.0-alpha.3-develop" opt-level = 3 [dependencies] -anyhow = "1.0.79" +anyhow = "1.0.81" argon2 = "0" async-trait = "0" axum = { version = "0", features = ["multipart"] } From 4167b86edc417ee07d27f7d32b648360d89c5439 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 14 Mar 2024 17:53:54 +0100 Subject: [PATCH 145/309] chore: bump thiserror from 1.0.57 to 1.0.58 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e10e7c0..c920db9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2999,18 +2999,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", From d83f8c60ef75f0718b96da1ff2e3bea06ad9b5ae Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 15 Mar 2024 21:56:44 +0100 Subject: [PATCH 146/309] refactor: deprecated function updated and unused import deleted --- src/utils/clock.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/clock.rs b/src/utils/clock.rs index 42269eeb..5ce8c40e 100644 --- a/src/utils/clock.rs +++ b/src/utils/clock.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, TimeDelta, Utc}; pub const DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; @@ -14,9 +14,15 @@ pub fn now() -> u64 { } /// Returns the datetime some seconds ago. +/// +/// # Panics +/// +/// The function panics if the number of seconds passed as a parameter +/// are more than `i64::MAX` / `1_000` or less than `-i64::MAX` / `1_000`. #[must_use] pub fn seconds_ago_utc(seconds: i64) -> DateTime { - Utc::now() - Duration::seconds(seconds) + Utc::now() + - TimeDelta::try_seconds(seconds).expect("seconds should be more than i64::MAX / 1_000 or less than -i64::MAX / 1_000") } /// Returns the current time in database format. From 3321418eab069c64a17b35d8a0a05e05967dc014 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 1 Apr 2024 16:19:48 +0200 Subject: [PATCH 147/309] chore: update deps Updating ahash v0.8.9 -> v0.8.11 Updating aho-corasick v1.1.2 -> v1.1.3 Updating anstream v0.6.12 -> v0.6.13 Updating async-compression v0.4.6 -> v0.4.7 Updating async-trait v0.1.77 -> v0.1.79 Updating autocfg v1.1.0 -> v1.2.0 Updating axum v0.7.4 -> v0.7.5 Updating backtrace v0.3.69 -> v0.3.71 Adding base64 v0.22.0 Updating bitflags v2.4.2 -> v2.5.0 Updating brotli v3.4.0 -> v3.5.0 Updating bumpalo v3.15.3 -> v3.15.4 Updating bytemuck v1.14.3 -> v1.15.0 Updating bytes v1.5.0 -> v1.6.0 Updating cc v1.0.88 -> v1.0.90 Updating chrono v0.4.35 -> v0.4.37 Updating clap v4.5.2 -> v4.5.4 Updating clap_derive v4.5.0 -> v4.5.4 Updating const-random v0.1.17 -> v0.1.18 Updating email-encoding v0.2.0 -> v0.3.0 Updating fastrand v2.0.1 -> v2.0.2 Removing h2 v0.3.24 Removing h2 v0.4.2 Adding h2 v0.4.3 Adding heck v0.5.0 Updating hermit-abi v0.3.8 -> v0.3.9 Removing http v0.2.11 Removing http-body v0.4.6 Updating http-body-util v0.1.0 -> v0.1.1 Removing hyper v0.14.28 Updating hyper-tls v0.5.0 -> v0.6.0 Updating indexmap v2.2.5 -> v2.2.6 Updating itoa v1.0.10 -> v1.0.11 Adding jobserver v0.1.28 Updating js-sys v0.3.68 -> v0.3.69 Updating jsonwebtoken v9.2.0 -> v9.3.0 Updating lettre v0.11.4 -> v0.11.6 Updating memchr v2.7.1 -> v2.7.2 Updating openssl-sys v0.9.101 -> v0.9.102 Updating pest v2.7.7 -> v2.7.8 Updating pest_derive v2.7.7 -> v2.7.8 Updating pest_generator v2.7.7 -> v2.7.8 Updating pest_meta v2.7.7 -> v2.7.8 Updating pin-project v1.1.4 -> v1.1.5 Updating pin-project-internal v1.1.4 -> v1.1.5 Updating pin-project-lite v0.2.13 -> v0.2.14 Updating proc-macro2 v1.0.78 -> v1.0.79 Updating regex v1.10.3 -> v1.10.4 Updating regex-automata v0.4.5 -> v0.4.6 Updating regex-syntax v0.8.2 -> v0.8.3 Updating reqwest v0.11.26 -> v0.12.2 Updating rustix v0.38.31 -> v0.38.32 Updating rustls v0.22.2 -> v0.23.4 Updating rustls-pemfile v2.1.0 -> v2.1.1 Updating rustls-pki-types v1.3.1 -> v1.4.1 Updating security-framework v2.9.2 -> v2.10.0 Updating security-framework-sys v2.9.1 -> v2.10.0 Updating serde_json v1.0.114 -> v1.0.115 Updating serde_path_to_error v0.1.15 -> v0.1.16 Updating smallvec v1.13.1 -> v1.13.2 Updating syn v2.0.51 -> v2.0.57 Adding sync_wrapper v1.0.0 Updating tokio v1.36.0 -> v1.37.0 Updating tokio-rustls v0.25.0 -> v0.26.0 Updating tokio-stream v0.1.14 -> v0.1.15 Updating toml v0.8.10 -> v0.8.12 Updating toml_edit v0.22.6 -> v0.22.9 Updating uuid v1.7.0 -> v1.8.0 Updating walkdir v2.4.0 -> v2.5.0 Adding wasite v0.1.0 Updating wasm-bindgen v0.2.91 -> v0.2.92 Updating wasm-bindgen-backend v0.2.91 -> v0.2.92 Updating wasm-bindgen-futures v0.4.41 -> v0.4.42 Updating wasm-bindgen-macro v0.2.91 -> v0.2.92 Updating wasm-bindgen-macro-support v0.2.91 -> v0.2.92 Updating wasm-bindgen-shared v0.2.91 -> v0.2.92 Updating web-sys v0.3.68 -> v0.3.69 Updating which v6.0.0 -> v6.0.1 Updating whoami v1.4.1 -> v1.5.1 Updating windows-targets v0.52.3 -> v0.52.4 Updating windows_aarch64_gnullvm v0.52.3 -> v0.52.4 Updating windows_aarch64_msvc v0.52.3 -> v0.52.4 Updating windows_i686_gnu v0.52.3 -> v0.52.4 Updating windows_i686_msvc v0.52.3 -> v0.52.4 Updating windows_x86_64_gnu v0.52.3 -> v0.52.4 Updating windows_x86_64_gnullvm v0.52.3 -> v0.52.4 Updating windows_x86_64_msvc v0.52.3 -> v0.52.4 Updating winnow v0.6.2 -> v0.6.5 Adding winsafe v0.0.19 (latest: v0.0.20) Updating xml-rs v0.8.19 -> v0.8.20 Updating zstd v0.13.0 -> v0.13.1 Updating zstd-safe v7.0.0 -> v7.1.0 Updating zstd-sys v2.0.9+zstd.1.5.5 -> v2.0.10+zstd.1.5.6 --- Cargo.lock | 577 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 284 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c920db9f..56cc34ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -32,9 +32,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -161,9 +161,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +checksum = "86a9249d1447a85f95810c620abea82e001fe58a31713fcce614caf52499f905" dependencies = [ "brotli", "flate2", @@ -177,13 +177,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -197,24 +197,24 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", - "hyper 1.2.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -228,7 +228,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.0", "tokio", "tower", "tower-layer", @@ -245,13 +245,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", "tracing", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -284,6 +284,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -304,9 +310,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -331,9 +337,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -362,15 +368,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -380,16 +386,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.88" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" dependencies = [ + "jobserver", "libc", ] @@ -401,14 +408,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -423,9 +430,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -445,14 +452,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -505,9 +512,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] @@ -728,11 +735,11 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "memchr", ] @@ -789,9 +796,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdeflate" @@ -952,7 +959,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -1040,35 +1047,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.11", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -1110,11 +1098,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1160,17 +1154,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "http" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1182,17 +1165,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.11", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -1200,19 +1172,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] name = "http-body-util" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "futures-core", + "http", + "http-body", "pin-project-lite", ] @@ -1228,30 +1200,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.24", - "http 0.2.11", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.2.0" @@ -1261,28 +1209,32 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.2", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.28", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -1292,13 +1244,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1352,9 +1308,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1377,9 +1333,18 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] [[package]] name = "jpeg-decoder" @@ -1389,9 +1354,9 @@ checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1409,9 +1374,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.2.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.7", "js-sys", @@ -1442,12 +1407,12 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357ff5edb6d8326473a64c82cf41ddf78ab116f89668c50c4fac1b321e5e80f4" +checksum = "47460276655930189e0919e4fbf46e46476b14f934f18a63dd726a5fb7b60e2e" dependencies = [ "async-trait", - "base64 0.21.7", + "base64 0.22.0", "chumsky", "email-encoding", "email_address", @@ -1463,7 +1428,7 @@ dependencies = [ "percent-encoding", "quoted_printable", "rustls", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.1", "socket2", "tokio", "tokio-native-tls", @@ -1553,9 +1518,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -1618,7 +1583,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.1.0", + "http", "httparse", "log", "memchr", @@ -1750,7 +1715,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -1767,7 +1732,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -1778,9 +1743,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1883,9 +1848,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", @@ -1894,9 +1859,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" dependencies = [ "pest", "pest_generator", @@ -1904,22 +1869,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" dependencies = [ "once_cell", "pest", @@ -1934,29 +1899,29 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2018,9 +1983,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -2096,9 +2061,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2108,9 +2073,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2119,26 +2084,28 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.26" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" +checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.3.24", - "http 0.2.11", - "http-body 0.4.6", - "hyper 0.14.28", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -2152,7 +2119,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -2210,7 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -2271,11 +2238,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2284,11 +2251,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -2307,9 +2275,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" dependencies = [ "base64 0.21.7", "rustls-pki-types", @@ -2317,9 +2285,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -2395,9 +2363,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2408,9 +2376,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -2458,14 +2426,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -2474,9 +2442,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -2599,9 +2567,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -2724,7 +2692,7 @@ checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.4.1", "hex", "once_cell", "proc-macro2", @@ -2750,7 +2718,7 @@ checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "bytes", "crc", @@ -2793,7 +2761,7 @@ checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "crc", "dotenvy", @@ -2906,9 +2874,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.51" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -2921,6 +2889,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" + [[package]] name = "system-configuration" version = "0.5.1" @@ -3014,7 +2988,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -3088,9 +3062,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -3112,7 +3086,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -3127,9 +3101,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", @@ -3138,9 +3112,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3163,9 +3137,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -3184,9 +3158,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", @@ -3213,8 +3187,8 @@ dependencies = [ "fern", "futures", "hex", - "http 1.1.0", - "hyper 1.2.0", + "http", + "hyper", "indexmap", "jsonwebtoken", "lazy_static", @@ -3281,11 +3255,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", "pin-project-lite", "tokio", @@ -3339,7 +3313,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -3555,9 +3529,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] @@ -3576,9 +3550,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3599,11 +3573,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3611,24 +3591,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3638,9 +3618,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3648,28 +3628,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3686,22 +3666,25 @@ dependencies = [ [[package]] name = "which" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.52.0", + "winsafe", ] [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall", + "wasite", +] [[package]] name = "winapi" @@ -3740,7 +3723,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -3758,7 +3741,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -3778,17 +3761,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -3799,9 +3782,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -3811,9 +3794,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -3823,9 +3806,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -3835,9 +3818,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -3847,9 +3830,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -3859,9 +3842,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -3871,15 +3854,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -3894,11 +3877,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmlparser" @@ -3938,7 +3927,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn 2.0.57", ] [[package]] @@ -3949,27 +3938,27 @@ checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 97f5b133..22f8f9f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ axum = { version = "0", features = ["multipart"] } binascii = "0" bytes = "1" chrono = { version = "0", default-features = false, features = ["clock"] } -clap = { version = "4.5.2", features = ["derive", "env"]} +clap = { version = "4.5.4", features = ["derive", "env"]} config = "0" derive_more = "0" email_address = "0" From 09b030d3eb231b0a9beb7f77ae73c85e7a5f29af Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 8 Apr 2024 23:50:50 +0200 Subject: [PATCH 148/309] build: [#556] fix project not building due to unused code --- tests/common/contexts/torrent/file.rs | 1 + tests/common/contexts/torrent/fixtures.rs | 1 + tests/common/contexts/user/responses.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/tests/common/contexts/torrent/file.rs b/tests/common/contexts/torrent/file.rs index 5d8a541a..d9d3783f 100644 --- a/tests/common/contexts/torrent/file.rs +++ b/tests/common/contexts/torrent/file.rs @@ -8,6 +8,7 @@ use serde::Deserialize; use which::which; /// Attributes parsed from a torrent file. +#[allow(dead_code)] #[derive(Deserialize, Clone, Debug)] pub struct TorrentFileInfo { pub name: String, diff --git a/tests/common/contexts/torrent/fixtures.rs b/tests/common/contexts/torrent/fixtures.rs index e60b6089..b085ddec 100644 --- a/tests/common/contexts/torrent/fixtures.rs +++ b/tests/common/contexts/torrent/fixtures.rs @@ -40,6 +40,7 @@ impl From for UploadTorrentMultipartForm { } /// Torrent that has been added to the index. +#[allow(dead_code)] pub struct TorrentListedInIndex { pub torrent_id: Id, pub title: String, diff --git a/tests/common/contexts/user/responses.rs b/tests/common/contexts/user/responses.rs index 1a9a3837..f917be18 100644 --- a/tests/common/contexts/user/responses.rs +++ b/tests/common/contexts/user/responses.rs @@ -1,10 +1,12 @@ use serde::Deserialize; +#[allow(dead_code)] #[derive(Deserialize, Debug)] pub struct AddedUserResponse { pub data: NewUserData, } +#[allow(dead_code)] #[derive(Deserialize, Debug)] pub struct NewUserData { pub user_id: i64, From b2c2ce7612543c9a38afd6fa7cc1fdfb923bd2dc Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 24 Apr 2024 16:35:28 +0100 Subject: [PATCH 149/309] feat: increase the tracker stast importer exec interval We are having problems with the live demo server: https://github.com/torrust/torrust-demo/issues/1 Due to a high CPU and memory usage. --- .../cronjobs/tracker_statistics_importer.rs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/console/cronjobs/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs index 970fd7ca..7f618e61 100644 --- a/src/console/cronjobs/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -83,16 +83,28 @@ pub fn start( info!("Tracker statistics importer cronjob starting ..."); - // code-review: we set an execution interval to avoid intense polling to - // the database. If we remove the interval we would be constantly - // queering if there are torrent stats pending to update, unless there - // are torrents to update. Maybe we should only sleep for 100 milliseconds - // if we did not update any torrents in the latest execution. - // With this current limit we can only import 50 torrent stats every 100 - // milliseconds which is 500 torrents per second (1800000 torrents per hour). - // If the tracker can handle a request in 100 milliseconds. - - let execution_interval_in_milliseconds = 100; + // code-review: + // + // We set an execution interval to avoid intense polling to the + // database. If we remove the interval we would be constantly queering + // if there are torrent stats pending to update, unless there are + // torrents to update. Maybe we should only sleep for 100 milliseconds + // if we did not update any torrents in the latest execution. With this + // current limit we can only import 50 torrent stats every 2000 seconds, + // which is 500 torrents per second (1800000 torrents per hour). + // + // | Interval (secs) | Number of torrents imported per hour | + // ------------------|--------------------------------------| + // | 1 sec | 50 * (3600/1) = 180000 | + // | 2 sec | 50 * (3600/2) = 90000 | + // | 3 sec | 50 * (3600/3) = 60000 | + // | 4 sec | 50 * (3600/4) = 45000 | + // | 5 sec | 50 * (3600/5) = 36000 | + // + // The `execution_interval_in_milliseconds` could be a config option in + // the future. + + let execution_interval_in_milliseconds = 2000; let execution_interval_duration = std::time::Duration::from_millis(execution_interval_in_milliseconds); let mut execution_interval = tokio::time::interval(execution_interval_duration); From a6d3b0d799e489dfe5f8efc1231a168578790252 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 24 Apr 2024 18:24:07 +0100 Subject: [PATCH 150/309] chore(deps): udpate dependencies ```output Updating crates.io index Locking 59 packages to latest compatible versions Updating allocator-api2 v0.2.16 -> v0.2.18 Updating anyhow v1.0.81 -> v1.0.82 Updating async-compression v0.4.7 -> v0.4.8 Updating async-trait v0.1.79 -> v0.1.80 Updating brotli v3.5.0 -> v4.0.0 (latest: v5.0.0) Updating brotli-decompressor v2.5.1 -> v3.0.0 (latest: v4.0.0) Updating bumpalo v3.15.4 -> v3.16.0 Updating cc v1.0.90 -> v1.0.95 Updating chrono v0.4.37 -> v0.4.38 Updating crc v3.0.1 -> v3.2.1 Updating der v0.7.8 -> v0.7.9 Updating either v1.10.0 -> v1.11.0 Updating encoding_rs v0.8.33 -> v0.8.34 Updating getrandom v0.2.12 -> v0.2.14 Updating h2 v0.4.3 -> v0.4.4 Updating hostname v0.3.1 -> v0.4.0 Updating hyper v1.2.0 -> v1.3.1 Updating jobserver v0.1.28 -> v0.1.31 Updating lettre v0.11.6 -> v0.11.7 Removing match_cfg v0.1.0 Updating pem v3.0.3 -> v3.0.4 Updating pest v2.7.8 -> v2.7.9 Updating pest_derive v2.7.8 -> v2.7.9 Updating pest_generator v2.7.8 -> v2.7.9 Updating pest_meta v2.7.8 -> v2.7.9 Updating proc-macro2 v1.0.79 -> v1.0.81 Updating quote v1.0.35 -> v1.0.36 Updating reqwest v0.12.2 -> v0.12.4 Updating rustix v0.38.32 -> v0.38.34 Updating rustls v0.23.4 -> v0.23.5 Removing rustls-pemfile v1.0.4 Removing rustls-pemfile v2.1.1 Adding rustls-pemfile v2.1.2 Updating rustls-pki-types v1.4.1 -> v1.5.0 Updating rustls-webpki v0.102.2 -> v0.102.3 Updating rustversion v1.0.14 -> v1.0.15 Updating serde v1.0.197 -> v1.0.198 Updating serde_derive v1.0.197 -> v1.0.198 Updating serde_json v1.0.115 -> v1.0.116 Updating signal-hook-registry v1.4.1 -> v1.4.2 Updating strsim v0.11.0 -> v0.11.1 Updating syn v2.0.57 -> v2.0.60 Updating sync_wrapper v1.0.0 -> v1.0.1 Updating thiserror v1.0.58 -> v1.0.59 Updating thiserror-impl v1.0.58 -> v1.0.59 Updating time v0.3.34 -> v0.3.36 Updating time-macros v0.2.17 -> v0.2.18 Updating toml_edit v0.22.9 -> v0.22.12 Updating winapi-util v0.1.6 -> v0.1.7 Adding windows v0.52.0 (latest: v0.56.0) Updating windows-targets v0.52.4 -> v0.52.5 Updating windows_aarch64_gnullvm v0.52.4 -> v0.52.5 Updating windows_aarch64_msvc v0.52.4 -> v0.52.5 Updating windows_i686_gnu v0.52.4 -> v0.52.5 Adding windows_i686_gnullvm v0.52.5 Updating windows_i686_msvc v0.52.4 -> v0.52.5 Updating windows_x86_64_gnu v0.52.4 -> v0.52.5 Updating windows_x86_64_gnullvm v0.52.4 -> v0.52.5 Updating windows_x86_64_msvc v0.52.4 -> v0.52.5 Updating winnow v0.6.5 -> v0.6.6 Updating winreg v0.50.0 -> v0.52.0 Updating zeroize v1.7.0 -> v1.8.0 ``` --- Cargo.lock | 325 +++++++++++++++++++++++++++-------------------------- 1 file changed, 164 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56cc34ae..6e50e352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "argon2" @@ -161,9 +161,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86a9249d1447a85f95810c620abea82e001fe58a31713fcce614caf52499f905" +checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" dependencies = [ "brotli", "flate2", @@ -177,13 +177,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -228,7 +228,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.0", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.5.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -368,9 +368,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" @@ -392,12 +392,13 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -408,14 +409,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -459,7 +460,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -572,9 +573,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -655,9 +656,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -726,9 +727,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" dependencies = [ "serde", ] @@ -754,9 +755,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -959,7 +960,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -1004,9 +1005,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -1047,9 +1048,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", @@ -1145,13 +1146,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ + "cfg-if", "libc", - "match_cfg", - "winapi", + "windows", ] [[package]] @@ -1202,9 +1203,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -1339,9 +1340,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -1407,9 +1408,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47460276655930189e0919e4fbf46e46476b14f934f18a63dd726a5fb7b60e2e" +checksum = "1a62049a808f1c4e2356a2a380bd5f2aca3b011b0b482cf3b914ba1731426969" dependencies = [ "async-trait", "base64 0.22.0", @@ -1428,7 +1429,7 @@ dependencies = [ "percent-encoding", "quoted_printable", "rustls", - "rustls-pemfile 2.1.1", + "rustls-pemfile", "socket2", "tokio", "tokio-native-tls", @@ -1488,12 +1489,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.10" @@ -1732,7 +1727,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -1823,11 +1818,11 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "serde", ] @@ -1848,9 +1843,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ "memchr", "thiserror", @@ -1859,9 +1854,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -1869,22 +1864,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] name = "pest_meta" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", @@ -1914,7 +1909,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -1983,9 +1978,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -2001,9 +1996,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2090,11 +2085,11 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bytes", "encoding_rs", "futures-core", @@ -2115,7 +2110,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2238,9 +2233,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -2251,9 +2246,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.4" +version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" +checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" dependencies = [ "log", "once_cell", @@ -2266,34 +2261,25 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" -dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", "rustls-pki-types", @@ -2302,9 +2288,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rustybuzz" @@ -2392,9 +2378,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -2420,20 +2406,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -2506,9 +2492,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2842,9 +2828,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -2874,9 +2860,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.57" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -2891,9 +2877,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "system-configuration" @@ -2973,29 +2959,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -3014,9 +3000,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -3086,7 +3072,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -3158,9 +3144,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", @@ -3313,7 +3299,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] @@ -3600,7 +3586,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -3634,7 +3620,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3704,11 +3690,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3717,13 +3703,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3741,7 +3737,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3761,17 +3757,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -3782,9 +3779,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3794,9 +3791,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3806,9 +3803,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -3818,9 +3821,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3830,9 +3833,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3842,9 +3845,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3854,24 +3857,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", @@ -3927,14 +3930,14 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.60", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" [[package]] name = "zstd" From 470bfb2d3ec5ba5bdcf0e21a955b3417410259aa Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 May 2024 16:31:32 +0100 Subject: [PATCH 151/309] refactor: [#576] rename tracker env vars ``` TORRUST_TRACKER_BACK_ -> TORRUST_TRACKER_ TORRUST_TRACKER_DATABASE_DRIVER -> TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER TORRUST_TRACKER_API_ADMIN_TOKEN -> TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN TORRUST_TRACKER_CONFIG -> TORRUST_TRACKER_CONFIG_TOML TORRUST_TRACKER_PATH_CONFIG -> TORRUST_TRACKER_CONFIG_TOML_PATH ``` To follow changes in: https://github.com/torrust/torrust-tracker/issues/851 --- .env.local | 6 +++--- Containerfile | 2 +- compose.yaml | 6 +++--- contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh | 2 +- contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 6 +++--- contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh | 2 +- contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh | 6 +++--- share/default/config/tracker.container.mysql.toml | 4 ---- share/default/config/tracker.container.sqlite3.toml | 4 ---- share/default/config/tracker.e2e.container.sqlite3.toml | 5 ----- src/config.rs | 2 +- 11 files changed, 16 insertions(+), 29 deletions(-) diff --git a/.env.local b/.env.local index 2cb1998c..c970e247 100644 --- a/.env.local +++ b/.env.local @@ -2,6 +2,6 @@ DATABASE_URL=sqlite://storage/database/data.db?mode=rwc TORRUST_INDEX_CONFIG= TORRUST_INDEX_AUTH_SECRET_KEY=MaxVerstappenWC2021 USER_ID=1000 -TORRUST_TRACKER_CONFIG= -TORRUST_TRACKER_DATABASE_DRIVER=sqlite3 -TORRUST_TRACKER_API_ADMIN_TOKEN=MyAccessToken +TORRUST_TRACKER_CONFIG_TOML= +TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER=Sqlite3 +TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN=MyAccessToken diff --git a/Containerfile b/Containerfile index fe7961da..c1b89791 100644 --- a/Containerfile +++ b/Containerfile @@ -98,7 +98,7 @@ RUN ["/busybox/cp", "-sp", "/busybox/sh","/busybox/cat","/busybox/ls","/busybox/ COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec ARG TORRUST_INDEX_PATH_CONFIG="/etc/torrust/index/index.toml" -ARG TORRUST_INDEX_DATABASE_DRIVER="sqlite3" +ARG TORRUST_INDEX_DATABASE_DRIVER="Sqlite3" ARG USER_ID=1000 ARG API_PORT=3001 ARG IMPORTER_API_PORT=3002 diff --git a/compose.yaml b/compose.yaml index 946f13c1..ecac7447 100644 --- a/compose.yaml +++ b/compose.yaml @@ -32,10 +32,10 @@ services: tty: true environment: - USER_ID=${USER_ID} - - TORRUST_TRACKER_CONFIG=${TORRUST_TRACKER_CONFIG} + - TORRUST_TRACKER_CONFIG_TOML=${TORRUST_TRACKER_CONFIG_TOML} - TORRUST_TRACKER_DATABASE=${TORRUST_TRACKER_DATABASE:-e2e_testing_sqlite3} - - TORRUST_TRACKER_DATABASE_DRIVER=${TORRUST_TRACKER_DATABASE_DRIVER:-sqlite3} - - TORRUST_TRACKER_API_ADMIN_TOKEN=${TORRUST_TRACKER_API_ADMIN_TOKEN:-MyAccessToken} + - TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER=${TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER:-Sqlite3} + - TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN=${TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN:-MyAccessToken} networks: - server_side ports: diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh index d21e953b..a867b233 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index cb941681..f1d6f82c 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -9,8 +9,8 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ - TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh index 74ed5e77..be61fa4d 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index 519d6018..abd6a6a6 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -9,8 +9,8 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ - TORRUST_TRACKER_CONFIG=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_DATABASE_DRIVER="sqlite3" \ - TORRUST_TRACKER_API_ADMIN_TOKEN="MyAccessToken" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/share/default/config/tracker.container.mysql.toml b/share/default/config/tracker.container.mysql.toml index e7714c22..49fa7c16 100644 --- a/share/default/config/tracker.container.mysql.toml +++ b/share/default/config/tracker.container.mysql.toml @@ -30,10 +30,6 @@ ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" ssl_enabled = false ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" -# Please override the admin token setting the -# `TORRUST_TRACKER_API_ADMIN_TOKEN` -# environmental variable! - [http_api.access_tokens] admin = "MyAccessToken" diff --git a/share/default/config/tracker.container.sqlite3.toml b/share/default/config/tracker.container.sqlite3.toml index 4ec055c5..7649f778 100644 --- a/share/default/config/tracker.container.sqlite3.toml +++ b/share/default/config/tracker.container.sqlite3.toml @@ -30,10 +30,6 @@ ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" ssl_enabled = false ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" -# Please override the admin token setting the -# `TORRUST_TRACKER_API_ADMIN_TOKEN` -# environmental variable! - [http_api.access_tokens] admin = "MyAccessToken" diff --git a/share/default/config/tracker.e2e.container.sqlite3.toml b/share/default/config/tracker.e2e.container.sqlite3.toml index 316c6f1d..df840dfb 100644 --- a/share/default/config/tracker.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.e2e.container.sqlite3.toml @@ -30,13 +30,8 @@ ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" ssl_enabled = false ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" -# Please override the admin token setting the -# `TORRUST_TRACKER_API_ADMIN_TOKEN` -# environmental variable! - [http_api.access_tokens] admin = "MyAccessToken" [health_check_api] bind_address = "127.0.0.1:1313" - diff --git a/src/config.rs b/src/config.rs index 7c8092c5..6df6e7fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,7 +81,7 @@ impl Info { pub enum Error { /// Unable to load the configuration from the environment variable. /// This error only occurs if there is no configuration file and the - /// `TORRUST_TRACKER_CONFIG` environment variable is not set. + /// `TORRUST_TRACKER_CONFIG_TOML` environment variable is not set. #[error("Unable to load from Environmental Variable: {source}")] UnableToLoadFromEnvironmentVariable { source: LocatedError<'static, dyn std::error::Error + Send + Sync>, From 033513474b150f227410a5b773cf427f95a0a60d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 May 2024 18:55:58 +0100 Subject: [PATCH 152/309] chore(deps): update dependencies ```output Updating crates.io index Locking 55 packages to latest compatible versions Updating anstream v0.6.13 -> v0.6.14 Updating anstyle v1.0.6 -> v1.0.7 Updating anstyle-parse v0.2.3 -> v0.2.4 Updating anstyle-query v1.0.2 -> v1.0.3 Updating anstyle-wincon v3.0.2 -> v3.0.3 Updating anyhow v1.0.82 -> v1.0.83 Updating async-compression v0.4.8 -> v0.4.10 Updating autocfg v1.2.0 -> v1.3.0 Updating base64 v0.22.0 -> v0.22.1 Updating brotli v4.0.0 -> v6.0.0 Updating brotli-decompressor v3.0.0 -> v4.0.0 Updating bytemuck v1.15.0 -> v1.16.0 Updating cc v1.0.95 -> v1.0.97 Updating colorchoice v1.0.0 -> v1.0.1 Updating errno v0.3.8 -> v0.3.9 Updating fastrand v2.0.2 -> v2.1.0 Updating flate2 v1.0.28 -> v1.0.30 Updating getrandom v0.2.14 -> v0.2.15 Updating hashbrown v0.14.3 -> v0.14.5 Adding is_terminal_polyfill v1.70.0 Updating libc v0.2.153 -> v0.2.154 Updating lock_api v0.4.11 -> v0.4.12 Updating multer v3.0.0 -> v3.1.0 Updating num-bigint v0.4.4 -> v0.4.5 Updating num-iter v0.1.44 -> v0.1.45 Updating num-traits v0.2.18 -> v0.2.19 Updating parking_lot v0.12.1 -> v0.12.2 Updating parking_lot_core v0.9.9 -> v0.9.10 Updating paste v1.0.14 -> v1.0.15 Updating pest v2.7.9 -> v2.7.10 Updating pest_derive v2.7.9 -> v2.7.10 Updating pest_generator v2.7.9 -> v2.7.10 Updating pest_meta v2.7.9 -> v2.7.10 Updating proc-macro2 v1.0.81 -> v1.0.82 Adding redox_syscall v0.5.1 Updating rustc-demangle v0.1.23 -> v0.1.24 Updating rustls-pki-types v1.5.0 -> v1.7.0 Updating rustversion v1.0.15 -> v1.0.16 Updating ryu v1.0.17 -> v1.0.18 Updating security-framework v2.10.0 -> v2.11.0 Updating security-framework-sys v2.10.0 -> v2.11.0 Updating semver v1.0.22 -> v1.0.23 Updating serde v1.0.198 -> v1.0.201 Updating serde_derive v1.0.198 -> v1.0.201 Updating serde_json v1.0.116 -> v1.0.117 Updating socket2 v0.5.6 -> v0.5.7 Updating syn v2.0.60 -> v2.0.63 Updating thiserror v1.0.59 -> v1.0.60 Updating thiserror-impl v1.0.59 -> v1.0.60 Updating tokio-util v0.7.10 -> v0.7.11 Updating winapi-util v0.1.7 -> v0.1.8 Updating winnow v0.6.6 -> v0.6.8 Updating zerocopy v0.7.32 -> v0.7.34 Updating zerocopy-derive v0.7.32 -> v0.7.34 Downgrading zeroize v1.8.0 -> v1.7.0 note: pass `--verbose` to see 60 unchanged dependencies behind latest ``` --- Cargo.lock | 281 ++++++++++++++++++++++++++++------------------------- 1 file changed, 147 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e50e352..ba81e0ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,47 +77,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -125,9 +126,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "argon2" @@ -161,9 +162,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" +checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" dependencies = [ "brotli", "flate2", @@ -183,7 +184,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -197,9 +198,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -286,9 +287,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -337,9 +338,9 @@ dependencies = [ [[package]] name = "brotli" -version = "4.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -348,9 +349,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525" +checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -374,9 +375,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -392,9 +393,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", @@ -425,7 +426,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", "stacker", ] @@ -460,7 +461,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -471,9 +472,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "colored" @@ -740,7 +741,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "memchr", ] @@ -770,9 +771,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -797,9 +798,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -827,9 +828,9 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -960,7 +961,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -1005,9 +1006,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1073,9 +1074,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -1087,7 +1088,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1314,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1323,6 +1324,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.12.1" @@ -1413,7 +1420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a62049a808f1c4e2356a2a380bd5f2aca3b011b0b482cf3b914ba1731426969" dependencies = [ "async-trait", - "base64 0.22.0", + "base64 0.22.1", "chumsky", "email-encoding", "email_address", @@ -1440,9 +1447,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -1475,9 +1482,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1571,16 +1578,15 @@ dependencies = [ [[package]] name = "multer" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", - "log", "memchr", "mime", "spin 0.9.8", @@ -1617,11 +1623,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1660,9 +1665,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1671,9 +1676,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -1727,7 +1732,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -1760,9 +1765,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1770,15 +1775,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1794,9 +1799,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" @@ -1822,7 +1827,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "serde", ] @@ -1843,9 +1848,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -1854,9 +1859,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -1864,22 +1869,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -1909,7 +1914,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -1978,9 +1983,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -2054,6 +2059,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" version = "1.10.4" @@ -2089,7 +2103,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -2218,9 +2232,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -2265,15 +2279,15 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -2288,9 +2302,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" [[package]] name = "rustybuzz" @@ -2310,9 +2324,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe_arch" @@ -2349,11 +2363,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -2362,9 +2376,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2372,15 +2386,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -2406,20 +2420,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2559,9 +2573,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2860,9 +2874,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -2959,22 +2973,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -3072,7 +3086,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -3109,16 +3123,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -3299,7 +3312,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -3586,7 +3599,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", "wasm-bindgen-shared", ] @@ -3620,7 +3633,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3668,7 +3681,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "redox_syscall", + "redox_syscall 0.4.1", "wasite", ] @@ -3690,9 +3703,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] @@ -3863,9 +3876,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -3915,29 +3928,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] name = "zeroize" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zstd" From ae061700dbfe97bf94ab5733129ebcf338b64117 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 14 May 2024 20:23:06 +0100 Subject: [PATCH 153/309] fix: errors in scripts - Wrong filepaths - Format scripts --- contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh | 2 +- .../dev-tools/container/e2e/mysql/run-e2e-tests.sh | 11 +++++++---- .../dev-tools/container/e2e/sqlite/e2e-env-down.sh | 2 +- contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 11 +++++++---- contrib/dev-tools/init/install-local.sh | 6 +++--- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh index a867b233..54123056 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ - TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite.toml) \ +TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 476d2a04..cbbc0530 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -17,7 +17,7 @@ export TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 -# Install app (no docker) that will run the test suite against the E2E testing +# Install app (no docker) that will run the test suite against the E2E testing # environment (in docker). cp .env.local .env || exit 1 @@ -30,7 +30,7 @@ echo "Running E2E tests using MySQL ..." # Wait for conatiners to be healthy ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1 -./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 # Just to make sure that everything is up and running docker ps @@ -42,8 +42,11 @@ docker ps TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ - cargo test \ - || { ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh; exit 1; } + cargo test || + { + ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh + exit 1 + } # Stop E2E testing environment ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh index be61fa4d..3dfc14e3 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ - TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ +TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index abd6a6a6..e9a424f5 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -13,4 +13,4 @@ USER_ID=${USER_ID:-1000} \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ - docker compose up --detach --pull always --remove-orphans + docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index af22e085..5842723c 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -17,7 +17,7 @@ export TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" # It's needed by some tests to generate and parse test torrent files. cargo install imdl || exit 1 -# Install app (no docker) that will run the test suite against the E2E testing +# Install app (no docker) that will run the test suite against the E2E testing # environment (in docker). cp .env.local .env || exit 1 ./contrib/dev-tools/container/e2e/sqlite/install.sh || exit 1 @@ -31,7 +31,7 @@ echo "Running E2E tests using SQLite ..." # Wait for conatiners to be healthy ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1 -./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 # Just to make sure that everything is up and running docker ps @@ -40,8 +40,11 @@ docker ps TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ - cargo test \ - || { ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh; exit 1; } + cargo test || + { + ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh + exit 1 + } # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 diff --git a/contrib/dev-tools/init/install-local.sh b/contrib/dev-tools/init/install-local.sh index e7b76d49..caeb27ef 100644 --- a/contrib/dev-tools/init/install-local.sh +++ b/contrib/dev-tools/init/install-local.sh @@ -5,7 +5,7 @@ # Generate the Index sqlite database directory and file if it does not exist mkdir -p ./storage/index/lib/database -if ! [ -f "./storage/index/lib/database/sqlite3.db.db" ]; then - echo "Creating index database 'sqlite3.db.db'" - sqlite3 "./storage/index/lib/database/sqlite3.db.db" "VACUUM;" +if ! [ -f "./storage/index/lib/database/sqlite3.db" ]; then + echo "Creating index database 'sqlite3.db'" + sqlite3 "./storage/index/lib/database/sqlite3.db" "VACUUM;" fi From bcebcab898410bf113ca6c0f0cb245c7bdec79ba Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 16:21:14 +0100 Subject: [PATCH 154/309] chore: format Cargo.toml --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22f8f9f4..7bf8639a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ repository = "https://github.com/torrust/torrust-tracker" rust-version = "1.72" version = "3.0.0-alpha.3-develop" - [profile.dev.package.sqlx-macros] opt-level = 3 @@ -41,7 +40,7 @@ axum = { version = "0", features = ["multipart"] } binascii = "0" bytes = "1" chrono = { version = "0", default-features = false, features = ["clock"] } -clap = { version = "4.5.4", features = ["derive", "env"]} +clap = { version = "4.5.4", features = ["derive", "env"] } config = "0" derive_more = "0" email_address = "0" @@ -75,7 +74,7 @@ thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } -tower-http = { version = "0", features = ["compression-full", "cors", "trace", "propagate-header", "request-id"] } +tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } trace = "0.1.7" tracing = "0.1.40" url = "2.5.0" From b9485732b49eb2387d19a91854806bc15b87e3ee Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 16:51:09 +0100 Subject: [PATCH 155/309] chore(deps): [#426] add cargo dependencies to use a custom axum server with timeouts Two levels of wrappers for the Axum server: Custom (with timeouts) -> axum-server -> axum --- Cargo.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 6 ++++ project-words.txt | 3 ++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba81e0ce..0b8b8edd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "argon2" version = "0.5.3" @@ -258,6 +264,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-server" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad46c3ec4e12f4a4b6835e173ba21c25e484c9d02b49770bf006ce5367c036" +dependencies = [ + "arc-swap", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "tokio", + "tokio-rustls 0.24.1", + "tower", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -1435,12 +1464,12 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls", + "rustls 0.23.5", "rustls-pemfile", "socket2", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.0", "url", "webpki-roots", ] @@ -2258,6 +2287,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.5" @@ -2268,7 +2309,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.3", "subtle", "zeroize", ] @@ -2289,6 +2330,16 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.3" @@ -2361,6 +2412,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.11.0" @@ -3099,13 +3160,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.5", "rustls-pki-types", "tokio", ] @@ -3176,6 +3247,7 @@ dependencies = [ "argon2", "async-trait", "axum", + "axum-server", "binascii", "bytes", "chrono", @@ -3185,15 +3257,19 @@ dependencies = [ "email_address", "fern", "futures", + "futures-util", "hex", "http", + "http-body", "hyper", + "hyper-util", "indexmap", "jsonwebtoken", "lazy_static", "lettre", "log", "pbkdf2", + "pin-project-lite", "rand", "rand_core", "regex", @@ -3214,6 +3290,7 @@ dependencies = [ "tokio", "toml", "torrust-index-located-error", + "tower", "tower-http", "trace", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 7bf8639a..4f3d6b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ anyhow = "1.0.81" argon2 = "0" async-trait = "0" axum = { version = "0", features = ["multipart"] } +axum-server = { version = "0", features = ["tls-rustls"] } binascii = "0" bytes = "1" chrono = { version = "0", default-features = false, features = ["clock"] } @@ -46,15 +47,19 @@ derive_more = "0" email_address = "0" fern = "0" futures = "0" +futures-util = "0.3.30" hex = "0" http = "1.1.0" +http-body = "1.0.0" hyper = "1" +hyper-util = { version = "0.1.3", features = ["http1", "http2", "tokio"] } indexmap = "2" jsonwebtoken = "9" lazy_static = "1.4.0" lettre = { version = "0", features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls", "tokio1-rustls-tls"] } log = "0" pbkdf2 = { version = "0", features = ["simple"] } +pin-project-lite = "0.2" rand = "0" rand_core = { version = "0", features = ["std"] } regex = "1" @@ -74,6 +79,7 @@ thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } +tower = { version = "0.4", features = ["timeout"] } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } trace = "0.1.7" tracing = "0.1.40" diff --git a/project-words.txt b/project-words.txt index c6f4a670..d02ab0ca 100644 --- a/project-words.txt +++ b/project-words.txt @@ -25,6 +25,7 @@ Dont dotless dtolnay elif +Eray grcov Grünwald hasher @@ -39,6 +40,7 @@ indexmap infohash Intermodal jsonwebtoken +Karatay leechers Leechers LEECHERS @@ -59,6 +61,7 @@ oneshot openbittorrent opentrackr ppassword +programatik proxied rapppid reqwest From e29eb012fa2e2172fe90a094677d15b406397842 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 16:56:21 +0100 Subject: [PATCH 156/309] feat: use custom axum-server wrapper with timeouts - Use axum-server isntead of directly the axum crate (like in the tracker). - Add wrapper to axum-server to enable timeouts. --- src/web/api/mod.rs | 6 - src/web/api/server/custom_axum.rs | 275 ++++++++++++++++++++++++++++++ src/web/api/server/mod.rs | 53 +++--- src/web/api/server/signals.rs | 88 ++++++++++ 4 files changed, 396 insertions(+), 26 deletions(-) create mode 100644 src/web/api/server/custom_axum.rs create mode 100644 src/web/api/server/signals.rs diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index f2d5b996..0a4e108e 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -30,12 +30,6 @@ pub struct Running { pub api_server: Option>>, } -#[must_use] -#[derive(Debug)] -pub struct ServerStartedMessage { - pub socket_addr: SocketAddr, -} - /// Starts the API server. #[must_use] pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, implementation: &Version) -> api::Running { diff --git a/src/web/api/server/custom_axum.rs b/src/web/api/server/custom_axum.rs new file mode 100644 index 00000000..5705ef24 --- /dev/null +++ b/src/web/api/server/custom_axum.rs @@ -0,0 +1,275 @@ +//! Wrapper for Axum server to add timeouts. +//! +//! Copyright (c) Eray Karatay ([@programatik29](https://github.com/programatik29)). +//! +//! See: . +//! +//! If a client opens a HTTP connection and it does not send any requests, the +//! connection is closed after a timeout. You can test it with: +//! +//! ```text +//! telnet 127.0.0.1 1212 +//! Trying 127.0.0.1... +//! Connected to 127.0.0.1. +//! Escape character is '^]'. +//! Connection closed by foreign host. +//! ``` +//! +//! If you want to know more about Axum and timeouts see . +use std::future::Ready; +use std::io::ErrorKind; +use std::net::TcpListener; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +use axum_server::accept::Accept; +use axum_server::tls_rustls::{RustlsAcceptor, RustlsConfig}; +use axum_server::Server; +use futures_util::{ready, Future}; +use http_body::{Body, Frame}; +use hyper::Response; +use hyper_util::rt::TokioTimer; +use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::time::{Instant, Sleep}; +use tower::Service; + +const HTTP1_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(5); +const HTTP2_KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(5); +const HTTP2_KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(5); + +#[must_use] +pub fn from_tcp_with_timeouts(socket: TcpListener) -> Server { + add_timeouts(axum_server::from_tcp(socket)) +} + +#[must_use] +pub fn from_tcp_rustls_with_timeouts(socket: TcpListener, tls: RustlsConfig) -> Server { + add_timeouts(axum_server::from_tcp_rustls(socket, tls)) +} + +fn add_timeouts(mut server: Server) -> Server { + server.http_builder().http1().timer(TokioTimer::new()); + server.http_builder().http2().timer(TokioTimer::new()); + + server.http_builder().http1().header_read_timeout(HTTP1_HEADER_READ_TIMEOUT); + server + .http_builder() + .http2() + .keep_alive_timeout(HTTP2_KEEP_ALIVE_TIMEOUT) + .keep_alive_interval(HTTP2_KEEP_ALIVE_INTERVAL); + + server +} + +#[derive(Clone)] +pub struct TimeoutAcceptor; + +impl Accept for TimeoutAcceptor { + type Stream = TimeoutStream; + type Service = TimeoutService; + type Future = Ready>; + + fn accept(&self, stream: I, service: S) -> Self::Future { + let (tx, rx) = mpsc::unbounded_channel(); + + let stream = TimeoutStream::new(stream, HTTP1_HEADER_READ_TIMEOUT, rx); + let service = TimeoutService::new(service, tx); + + std::future::ready(Ok((stream, service))) + } +} + +#[derive(Clone)] +pub struct TimeoutService { + inner: S, + sender: UnboundedSender, +} + +impl TimeoutService { + fn new(inner: S, sender: UnboundedSender) -> Self { + Self { inner, sender } + } +} + +impl Service for TimeoutService +where + S: Service>, +{ + type Response = Response>; + type Error = S::Error; + type Future = TimeoutServiceFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + // send timer wait signal + let _ = self.sender.send(TimerSignal::Wait); + + TimeoutServiceFuture::new(self.inner.call(req), self.sender.clone()) + } +} + +pin_project! { + pub struct TimeoutServiceFuture { + #[pin] + inner: F, + sender: Option>, + } +} + +impl TimeoutServiceFuture { + fn new(inner: F, sender: UnboundedSender) -> Self { + Self { + inner, + sender: Some(sender), + } + } +} + +impl Future for TimeoutServiceFuture +where + F: Future, E>>, +{ + type Output = Result>, E>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + this.inner.poll(cx).map(|result| { + result.map(|response| { + response.map(|body| TimeoutBody::new(body, this.sender.take().expect("future polled after ready"))) + }) + }) + } +} + +enum TimerSignal { + Wait, + Reset, +} + +pin_project! { + pub struct TimeoutBody { + #[pin] + inner: B, + sender: UnboundedSender, + } +} + +impl TimeoutBody { + fn new(inner: B, sender: UnboundedSender) -> Self { + Self { inner, sender } + } +} + +impl Body for TimeoutBody { + type Data = B::Data; + type Error = B::Error; + + fn poll_frame(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>>> { + let this = self.project(); + let option = ready!(this.inner.poll_frame(cx)); + + if option.is_none() { + let _ = this.sender.send(TimerSignal::Reset); + } + + Poll::Ready(option) + } + + fn is_end_stream(&self) -> bool { + let is_end_stream = self.inner.is_end_stream(); + + if is_end_stream { + let _ = self.sender.send(TimerSignal::Reset); + } + + is_end_stream + } + + fn size_hint(&self) -> http_body::SizeHint { + self.inner.size_hint() + } +} + +pub struct TimeoutStream { + inner: IO, + // hyper requires unpin + sleep: Pin>, + duration: Duration, + waiting: bool, + receiver: UnboundedReceiver, + finished: bool, +} + +impl TimeoutStream { + fn new(inner: IO, duration: Duration, receiver: UnboundedReceiver) -> Self { + Self { + inner, + sleep: Box::pin(tokio::time::sleep(duration)), + duration, + waiting: false, + receiver, + finished: false, + } + } +} + +impl AsyncRead for TimeoutStream { + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + if !self.finished { + match Pin::new(&mut self.receiver).poll_recv(cx) { + // reset the timer + Poll::Ready(Some(TimerSignal::Reset)) => { + self.waiting = false; + + let deadline = Instant::now() + self.duration; + self.sleep.as_mut().reset(deadline); + } + // enter waiting mode (for response body last chunk) + Poll::Ready(Some(TimerSignal::Wait)) => self.waiting = true, + Poll::Ready(None) => self.finished = true, + Poll::Pending => (), + } + } + + if !self.waiting { + // return error if timer is elapsed + if let Poll::Ready(()) = self.sleep.as_mut().poll(cx) { + return Poll::Ready(Err(std::io::Error::new(ErrorKind::TimedOut, "request header read timed out"))); + } + } + + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for TimeoutStream { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } +} diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index e6943e87..a327361e 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -1,15 +1,20 @@ +pub mod custom_axum; +pub mod signals; pub mod v1; use std::net::SocketAddr; use std::sync::Arc; +use axum_server::Handle; use log::info; -use tokio::net::TcpListener; -use tokio::sync::oneshot::{self, Sender}; +use tokio::sync::oneshot::{Receiver, Sender}; use v1::routes::router; -use super::{Running, ServerStartedMessage}; +use self::signals::{Halted, Started}; +use super::Running; use crate::common::AppData; +use crate::web::api::server::custom_axum::TimeoutAcceptor; +use crate::web::api::server::signals::graceful_shutdown; /// Starts the API server. /// @@ -21,13 +26,14 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni .parse() .expect("API server socket address to be valid."); - let (tx, rx) = oneshot::channel::(); + let (tx_start, rx) = tokio::sync::oneshot::channel::(); + let (_tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); // Run the API server let join_handle = tokio::spawn(async move { info!("Starting API server with net config: {} ...", config_socket_addr); - start_server(config_socket_addr, app_data.clone(), tx).await; + start_server(config_socket_addr, app_data.clone(), tx_start, rx_halt).await; info!("API server stopped"); @@ -46,27 +52,34 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni } } -async fn start_server(config_socket_addr: SocketAddr, app_data: Arc, tx: Sender) { - let tcp_listener = TcpListener::bind(config_socket_addr) - .await - .expect("tcp listener to bind to a socket address"); +async fn start_server( + config_socket_addr: SocketAddr, + app_data: Arc, + tx_start: Sender, + rx_halt: Receiver, +) { + let router = router(app_data); + let socket = std::net::TcpListener::bind(config_socket_addr).expect("Could not bind tcp_listener to address."); + let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); - let bound_addr = tcp_listener - .local_addr() - .expect("tcp listener to be bound to a socket address."); + let handle = Handle::new(); - info!("API server listening on http://{}", bound_addr); // # DevSkim: ignore DS137138 + tokio::task::spawn(graceful_shutdown( + handle.clone(), + rx_halt, + format!("Shutting down API server on socket address: {address}"), + )); - let app = router(app_data); + info!("API server listening on http://{}", address); // # DevSkim: ignore DS137138 - tx.send(ServerStartedMessage { socket_addr: bound_addr }) + tx_start + .send(Started { socket_addr: address }) .expect("the API server should not be dropped"); - axum::serve(tcp_listener, app.into_make_service_with_connect_info::()) - .with_graceful_shutdown(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping API server on http://{} ...", bound_addr); // # DevSkim: ignore DS137138 - }) + custom_axum::from_tcp_with_timeouts(socket) + .handle(handle) + .acceptor(TimeoutAcceptor) + .serve(router.into_make_service_with_connect_info::()) .await .expect("API server should be running"); } diff --git a/src/web/api/server/signals.rs b/src/web/api/server/signals.rs new file mode 100644 index 00000000..872d6094 --- /dev/null +++ b/src/web/api/server/signals.rs @@ -0,0 +1,88 @@ +use std::net::SocketAddr; +use std::time::Duration; + +use derive_more::Display; +use log::info; +use tokio::time::sleep; + +/// This is the message that the "launcher" spawned task sends to the main +/// application process to notify the service was successfully started. +#[derive(Copy, Clone, Debug, Display)] +pub struct Started { + pub socket_addr: SocketAddr, +} + +/// This is the message that the "launcher" spawned task receives from the main +/// application process to notify the service to shutdown. +#[derive(Copy, Clone, Debug, Display)] +pub enum Halted { + Normal, +} + +pub async fn graceful_shutdown(handle: axum_server::Handle, rx_halt: tokio::sync::oneshot::Receiver, message: String) { + shutdown_signal_with_message(rx_halt, message).await; + + info!("Sending graceful shutdown signal"); + handle.graceful_shutdown(Some(Duration::from_secs(90))); + + println!("!! shuting down in 90 seconds !!"); + + loop { + sleep(Duration::from_secs(1)).await; + + info!("remaining alive connections: {}", handle.connection_count()); + } +} + +/// Same as `shutdown_signal()`, but shows a message when it resolves. +pub async fn shutdown_signal_with_message(rx_halt: tokio::sync::oneshot::Receiver, message: String) { + shutdown_signal(rx_halt).await; + + info!("{message}"); +} + +/// Resolves when the `stop_receiver` or the `global_shutdown_signal()` resolves. +/// +/// # Panics +/// +/// Will panic if the `stop_receiver` resolves with an error. +pub async fn shutdown_signal(rx_halt: tokio::sync::oneshot::Receiver) { + let halt = async { + match rx_halt.await { + Ok(signal) => signal, + Err(err) => panic!("Failed to install stop signal: {err}"), + } + }; + + tokio::select! { + signal = halt => { info!("Halt signal processed: {}", signal) }, + () = global_shutdown_signal() => { info!("Global shutdown signal processed") } + } +} + +/// Resolves on `ctrl_c` or the `terminate` signal. +/// +/// # Panics +/// +/// Will panic if the `ctrl_c` or `terminate` signal resolves with an error. +pub async fn global_shutdown_signal() { + let ctrl_c = async { + tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + () = ctrl_c => {}, + () = terminate => {} + } +} From 5d8296823ee09400b73cc6a3a259c5583ff4761f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 17:52:15 +0100 Subject: [PATCH 157/309] chore(deps): add cargo dependencies to parse TSL config --- Cargo.lock | 110 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- project-words.txt | 1 + 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b8b8edd..7493ae71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,6 +420,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.0.97" @@ -446,6 +455,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", + "serde", "windows-targets 0.52.5", ] @@ -675,6 +685,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.63", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.63", +] + [[package]] name = "data-url" version = "0.1.1" @@ -702,6 +747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1088,13 +1134,19 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1311,6 +1363,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1337,6 +1395,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1345,6 +1414,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.5", + "serde", ] [[package]] @@ -2532,6 +2602,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.63", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -2712,7 +2812,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 2.2.6", "log", "memchr", "native-tls", @@ -3232,7 +3332,7 @@ version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -3250,6 +3350,7 @@ dependencies = [ "axum-server", "binascii", "bytes", + "camino", "chrono", "clap", "config", @@ -3263,7 +3364,7 @@ dependencies = [ "http-body", "hyper", "hyper-util", - "indexmap", + "indexmap 2.2.6", "jsonwebtoken", "lazy_static", "lettre", @@ -3280,6 +3381,7 @@ dependencies = [ "serde_bytes", "serde_derive", "serde_json", + "serde_with", "sha-1", "sqlx", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 4f3d6b3a..f3de4e4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ axum = { version = "0", features = ["multipart"] } axum-server = { version = "0", features = ["tls-rustls"] } binascii = "0" bytes = "1" +camino = { version = "1.1.6", features = ["serde"] } chrono = { version = "0", default-features = false, features = ["clock"] } clap = { version = "4.5.4", features = ["derive", "env"] } config = "0" @@ -65,11 +66,12 @@ rand_core = { version = "0", features = ["std"] } regex = "1" reqwest = { version = "0", features = ["json", "multipart"] } rustversion = "1.0.14" -serde = { version = "1", features = ["rc"] } +serde = { version = "1", features = ["derive", "rc"] } serde_bencode = "0" serde_bytes = "0" serde_derive = "1" serde_json = "1" +serde_with = "3.8.1" sha-1 = "0" sqlx = { version = "0", features = ["migrate", "mysql", "runtime-tokio-native-tls", "sqlite", "time"] } tera = { version = "1", default-features = false } diff --git a/project-words.txt b/project-words.txt index d02ab0ca..c6dbbf95 100644 --- a/project-words.txt +++ b/project-words.txt @@ -8,6 +8,7 @@ Benoit binascii btih buildx +camino chrono clippy codecov From 969fffff1ada107e0ccba9ea55cd06944213be6a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 18:28:32 +0100 Subject: [PATCH 158/309] feat: [#426] add TSL info to the [net] section in the config toml file ```toml [net] port = 3001 [net.tsl] ssl_cert_path = "./storage/index/lib/tls/localhost.crt" ssl_key_path = "./storage/index/lib/tls/localhost.key" ``` ```json { "net": { "port": 3001, "tsl": { "ssl_cert_path": "./storage/index/lib/tls/localhost.crt", "ssl_key_path": "./storage/index/lib/tls/localhost.key" } } } ``` The TSL configuration is optional, but if you have that table (dict), it must contain the fields. This is an invalid configuration: ``` [net.tsl] ssl_cert_path = "" ssl_key_path = "" ``` See https://github.com/torrust/torrust-tracker/discussions/853. --- .../config/index.development.sqlite3.toml | 22 ++++++++------ src/config.rs | 30 +++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 669979af..0fffbced 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -4,19 +4,23 @@ log_level = "info" name = "Torrust" [tracker] -url = "udp://localhost:6969" -mode = "Public" api_url = "http://localhost:1212" +mode = "Public" token = "MyAccessToken" token_valid_seconds = 7257600 +url = "udp://localhost:6969" [net] port = 3001 +#[net.tsl] +#ssl_cert_path = "./storage/index/lib/tls/localhost.crt" +#ssl_key_path = "./storage/index/lib/tls/localhost.key" + [auth] email_on_signup = "Optional" -min_password_length = 6 max_password_length = 64 +min_password_length = 6 secret_key = "MaxVerstappenWC2021" [database] @@ -25,23 +29,23 @@ connect_url = "sqlite://data.db?mode=rwc" [mail] email_verification_enabled = false from = "example@email.com" -reply_to = "noreply@email.com" -username = "" password = "" -server = "" port = 25 +reply_to = "noreply@email.com" +server = "" +username = "" [image_cache] -max_request_timeout_ms = 1000 capacity = 128000000 entry_size_limit = 4000000 -user_quota_period_seconds = 3600 +max_request_timeout_ms = 1000 user_quota_bytes = 64000000 +user_quota_period_seconds = 3600 [api] default_torrent_page_size = 10 max_torrent_page_size = 30 [tracker_statistics_importer] +port = 3002 torrent_info_update_interval = 3600 -port = 3002 \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 6df6e7fe..eb7b3915 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,9 +3,11 @@ use std::path::Path; use std::sync::Arc; use std::{env, fs}; +use camino::Utf8PathBuf; use config::{Config, ConfigError, File, FileFormat}; use log::warn; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; @@ -216,6 +218,8 @@ pub struct Network { /// The base URL for the API. For example: `http://localhost`. /// If not set, the base URL will be inferred from the request. pub base_url: Option, + /// TSL configuration. + pub tsl: Option, } impl Default for Network { @@ -223,6 +227,7 @@ impl Default for Network { Self { port: 3001, base_url: None, + tsl: None, } } } @@ -394,6 +399,31 @@ impl Default for ImageCache { } } +#[serde_as] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] +pub struct Tsl { + /// Path to the SSL certificate file. + #[serde_as(as = "NoneAsEmptyString")] + #[serde(default = "Tsl::default_ssl_cert_path")] + pub ssl_cert_path: Option, + /// Path to the SSL key file. + #[serde_as(as = "NoneAsEmptyString")] + #[serde(default = "Tsl::default_ssl_key_path")] + pub ssl_key_path: Option, +} + +impl Tsl { + #[allow(clippy::unnecessary_wraps)] + fn default_ssl_cert_path() -> Option { + Some(Utf8PathBuf::new()) + } + + #[allow(clippy::unnecessary_wraps)] + fn default_ssl_key_path() -> Option { + Some(Utf8PathBuf::new()) + } +} + /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TorrustIndex { From 284d23549797817e70b292e8c21176405f71762f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 15 May 2024 18:30:23 +0100 Subject: [PATCH 159/309] feat: [#426] add TSL support You can provide a certificate and certificate key files to run the API with HTTPs. --- src/app.rs | 10 ++- src/main.rs | 4 +- src/web/api/mod.rs | 16 ++++- src/web/api/server/mod.rs | 103 ++++++++++++++++++++++++++---- tests/environments/app_starter.rs | 2 +- 5 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9cdd0523..a360ed79 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,13 +18,15 @@ use crate::services::torrent::{ use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository}; use crate::services::{proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; +use crate::web::api::server::signals::Halted; use crate::web::api::server::v1::auth::Authentication; use crate::web::api::Version; use crate::{console, mailer, tracker, web}; pub struct Running { pub api_socket_addr: SocketAddr, - pub api_server: Option>>, + pub api_server: JoinHandle>, + pub api_server_halt_task: tokio::sync::oneshot::Sender, pub tracker_data_importer_handle: tokio::task::JoinHandle<()>, } @@ -56,6 +58,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running // From [net] config let net_ip = "0.0.0.0".to_string(); let net_port = settings.net.port; + let opt_net_tsl = settings.net.tsl.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that // leads to requests hanging. @@ -168,12 +171,13 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running ); // Start API server - let running_api = web::api::start(app_data, &net_ip, net_port, api_version).await; + let running_api = web::api::start(app_data, &net_ip, net_port, opt_net_tsl, api_version).await; // Full running application Running { api_socket_addr: running_api.socket_addr, - api_server: running_api.api_server, + api_server: running_api.task, + api_server_halt_task: running_api.halt_task, tracker_data_importer_handle: tracker_statistics_importer_handle, } } diff --git a/src/main.rs b/src/main.rs index b09eedb6..387c8888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,9 @@ async fn main() -> Result<(), std::io::Error> { let app = app::run(configuration, &api_version).await; + assert!(!app.api_server_halt_task.is_closed(), "Halt channel should be open"); + match api_version { - Version::V1 => app.api_server.unwrap().await.expect("the API server was dropped"), + Version::V1 => app.api_server.await.expect("the API server was dropped"), } } diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index 0a4e108e..b1ac0601 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -14,7 +14,9 @@ use std::sync::Arc; use tokio::task::JoinHandle; +use self::server::signals::Halted; use crate::common::AppData; +use crate::config::Tsl; use crate::web::api; /// API versions. @@ -26,14 +28,22 @@ pub enum Version { pub struct Running { /// The socket address the API server is listening on. pub socket_addr: SocketAddr, + /// The channel sender to send halt signal to the server. + pub halt_task: tokio::sync::oneshot::Sender, /// The handle for the running API server. - pub api_server: Option>>, + pub task: JoinHandle>, } /// Starts the API server. #[must_use] -pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, implementation: &Version) -> api::Running { +pub async fn start( + app_data: Arc, + net_ip: &str, + net_port: u16, + opt_tsl: Option, + implementation: &Version, +) -> api::Running { match implementation { - Version::V1 => server::start(app_data, net_ip, net_port).await, + Version::V1 => server::start(app_data, net_ip, net_port, opt_tsl).await, } } diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index a327361e..0d60945f 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -3,37 +3,48 @@ pub mod signals; pub mod v1; use std::net::SocketAddr; +use std::panic::Location; use std::sync::Arc; +use axum_server::tls_rustls::RustlsConfig; use axum_server::Handle; -use log::info; +use log::{error, info}; +use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; +use torrust_index_located_error::LocatedError; use v1::routes::router; use self::signals::{Halted, Started}; use super::Running; use crate::common::AppData; +use crate::config::Tsl; use crate::web::api::server::custom_axum::TimeoutAcceptor; use crate::web::api::server::signals::graceful_shutdown; +pub type DynError = Arc; + /// Starts the API server. /// /// # Panics /// /// Panics if the API server can't be started. -pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Running { +pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, opt_tsl: Option) -> Running { let config_socket_addr: SocketAddr = format!("{net_ip}:{net_port}") .parse() .expect("API server socket address to be valid."); + let opt_rust_tls_config = make_rust_tls(&opt_tsl) + .await + .map(|tls| tls.expect("it should have a valid net tls configuration")); + let (tx_start, rx) = tokio::sync::oneshot::channel::(); - let (_tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); + let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); // Run the API server let join_handle = tokio::spawn(async move { info!("Starting API server with net config: {} ...", config_socket_addr); - start_server(config_socket_addr, app_data.clone(), tx_start, rx_halt).await; + start_server(config_socket_addr, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await; info!("API server stopped"); @@ -42,13 +53,18 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni // Wait until the API server is running let bound_addr = match rx.await { - Ok(msg) => msg.socket_addr, - Err(e) => panic!("API server start. The API server was dropped: {e}"), + Ok(started) => started.socket_addr, + Err(err) => { + let msg = format!("Unable to start API server: {err}"); + error!("{}", msg); + panic!("{}", msg); + } }; Running { socket_addr: bound_addr, - api_server: Some(join_handle), + halt_task: tx_halt, + task: join_handle, } } @@ -57,6 +73,7 @@ async fn start_server( app_data: Arc, tx_start: Sender, rx_halt: Receiver, + rust_tls_config: Option, ) { let router = router(app_data); let socket = std::net::TcpListener::bind(config_socket_addr).expect("Could not bind tcp_listener to address."); @@ -70,16 +87,74 @@ async fn start_server( format!("Shutting down API server on socket address: {address}"), )); - info!("API server listening on http://{}", address); // # DevSkim: ignore DS137138 + let tls = rust_tls_config.clone(); + let protocol = if tls.is_some() { "https" } else { "http" }; + + info!("API server listening on {}://{}", protocol, address); // # DevSkim: ignore DS137138 tx_start .send(Started { socket_addr: address }) .expect("the API server should not be dropped"); - custom_axum::from_tcp_with_timeouts(socket) - .handle(handle) - .acceptor(TimeoutAcceptor) - .serve(router.into_make_service_with_connect_info::()) - .await - .expect("API server should be running"); + match tls { + Some(tls) => custom_axum::from_tcp_rustls_with_timeouts(socket, tls) + .handle(handle) + .acceptor(TimeoutAcceptor) + .serve(router.into_make_service_with_connect_info::()) + .await + .expect("API server should be running"), + None => custom_axum::from_tcp_with_timeouts(socket) + .handle(handle) + .acceptor(TimeoutAcceptor) + .serve(router.into_make_service_with_connect_info::()) + .await + .expect("API server should be running"), + }; +} + +#[derive(Error, Debug)] +pub enum Error { + /// Enabled tls but missing config. + #[error("tls config missing")] + MissingTlsConfig { location: &'static Location<'static> }, + + /// Unable to parse tls Config. + #[error("bad tls config: {source}")] + BadTlsConfig { + source: LocatedError<'static, dyn std::error::Error + Send + Sync>, + ssl_cert_path: String, + ssl_key_path: String, + }, +} + +pub async fn make_rust_tls(tsl_config: &Option) -> Option> { + match tsl_config { + Some(tsl) => { + if let (Some(cert), Some(key)) = (tsl.ssl_cert_path.clone(), tsl.ssl_key_path.clone()) { + info!("Using https. Cert path: {cert}."); + info!("Using https. Key path: {key}."); + + let ssl_cert_path = cert.clone().to_string(); + let ssl_key_path = key.clone().to_string(); + + Some( + RustlsConfig::from_pem_file(cert, key) + .await + .map_err(|err| Error::BadTlsConfig { + source: (Arc::new(err) as DynError).into(), + ssl_cert_path, + ssl_key_path, + }), + ) + } else { + Some(Err(Error::MissingTlsConfig { + location: Location::caller(), + })) + } + } + None => { + info!("TLS not enabled"); + None + } + } } diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index b21f80d5..ec04a560 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -54,7 +54,7 @@ impl AppStarter { .expect("the app starter should not be dropped"); match api_version { - Version::V1 => app.api_server.unwrap().await, + Version::V1 => app.api_server.await, } }); From 1afe2343769214647ed82d4391904462810fe804 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 16 May 2024 16:22:22 +0100 Subject: [PATCH 160/309] feat: [#426] disable TimeoutAccceptor when TSL is enabled TSL does work with the TimeoutAccetor. How to enabled TSL for development with: ``` [net] port = 3001 [net.tsl] ssl_cert_path = "./storage/index/lib/tls/localhost.crt" ssl_key_path = "./storage/index/lib/tls/localhost.key" ``` You can fin the certificates in `./share/tsl`. This means there is no timeout for the first client request when you use TSL. The way to test tiemouts is: 1. Open a connection using telnet: `telnet 127.0.0.1 3001` 2. Wait 5 seconds. The connection should be closed after 5 seconds. That's what the TimeoutAcceptor does. Without the TimeoutAcceptor the connection will remain open until the client closes it. --- .../config/index.development.sqlite3.toml | 1 + share/tls/localhost.crt | 19 +++++++++++++ share/tls/localhost.key | 28 +++++++++++++++++++ src/web/api/server/mod.rs | 4 ++- 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100755 share/tls/localhost.crt create mode 100755 share/tls/localhost.key diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 0fffbced..11a3f48b 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -13,6 +13,7 @@ url = "udp://localhost:6969" [net] port = 3001 +# Uncomment if you want to enable TSL for development #[net.tsl] #ssl_cert_path = "./storage/index/lib/tls/localhost.crt" #ssl_key_path = "./storage/index/lib/tls/localhost.key" diff --git a/share/tls/localhost.crt b/share/tls/localhost.crt new file mode 100755 index 00000000..54f35ac7 --- /dev/null +++ b/share/tls/localhost.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDDzCCAfegAwIBAgIUQVYeAGfczJZDxiP/55P1V+hxLjgwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDUxNTE2MTUxNloXDTI0MDYx +NDE2MTUxNlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAsiVY2ny8JkTXvM1FSEp47UUNZcRCpQ3/JR1KYscK4yFk +t+2Fntqn7oYPFo17BU0fHZfJ/4ZFwgSCO2p41+plyAWjp9yjwA1Rgqs1eSvGceQG +cWZA8nIiehTdimOqV9gSr2lUpFUPvZhvfkoKUPH8kgnSsK6Vh5AHhOtMHJrTfSHi +SMyZlBMNm8XcHPI4Yc56rX56j0edQ+etmW+yF/sHxp4VuYLRg8Gy9LSBLhVYP2jb +3lHjraSpC6P1OQZPg+yDIJ67LPF3Io0POQQOqahHqKNXprakWNZzGKHklx5wSycW +LBBbwceEGFfoAap88czkh5RPVGkzaG9qI5nGjwT+iQIDAQABo1kwVzAUBgNVHREE +DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB +MB0GA1UdDgQWBBTNfmPhC1eBckwBVRUKFZXV94I4SDANBgkqhkiG9w0BAQsFAAOC +AQEADY9Z/RPdex3uSdo8gbEKkxzLFTE/DKiOk4ynpIjEmAm3PQ5JGX1bkXQU29WB +YFStue7OemFT1wCadv8xO4Y1WZdEDRAu1kAR+X30aL4hk03nOH3BOIlp972/yCjF +biAqUNJ1VbQkJHjBMFl/9pdsvrO1nz8ObgJrgyszCh+UXDk+mySEeJqiGYCCoZ3x +aQYnAO7+JVUgdXBmWd9BjNQAui8AwN+K5JelDecbwwh5Evykoa9Ey7W8yW23wuoK +MoVnti84JiF9eK/bQSRxdP9N8bECsHUSHWMOoA7+axOq1Q1L8oe67NCiBo//s28T +ZmJAlAeGXy1QqVTIslM8J+ceNQ== +-----END CERTIFICATE----- diff --git a/share/tls/localhost.key b/share/tls/localhost.key new file mode 100755 index 00000000..77e9e14f --- /dev/null +++ b/share/tls/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyJVjafLwmRNe8 +zUVISnjtRQ1lxEKlDf8lHUpixwrjIWS37YWe2qfuhg8WjXsFTR8dl8n/hkXCBII7 +anjX6mXIBaOn3KPADVGCqzV5K8Zx5AZxZkDyciJ6FN2KY6pX2BKvaVSkVQ+9mG9+ +SgpQ8fySCdKwrpWHkAeE60wcmtN9IeJIzJmUEw2bxdwc8jhhznqtfnqPR51D562Z +b7IX+wfGnhW5gtGDwbL0tIEuFVg/aNveUeOtpKkLo/U5Bk+D7IMgnrss8XcijQ85 +BA6pqEeoo1emtqRY1nMYoeSXHnBLJxYsEFvBx4QYV+gBqnzxzOSHlE9UaTNob2oj +mcaPBP6JAgMBAAECggEAAPMoUB+ga3mHoqgSGaO3cMWQn91s4Php2UbPj5RorQXr +IPx+71GbtVNLX5X7PjjZneg0a8yk57cQJ0TyWJIVXyET/ylptz3a7/lrbrY/Cgz8 +6GC8DQ7gceWelVhP1jLscgJpefpCIKfN+86uZa+EnYPdCSXXb/lQVYVhXRSJrdll +1LJuNAvW88c1zXKWJ+L05H3Q+O98F/6PpEcwln0mX9Qp7QyBNjeP1B1eQc8+S6CD +hgRifcY7KKdecDWh1i8haNqRUtXL7XAksesHJbxtIwaeu+8AXSQunpT2JOYFlzpy +yllEDcT2s+JutBqclINWggBEn1eHtksQKNLWrTVaiQKBgQDFdp8BwWRIYji9mAx5 +te4dwOTj+POSm6DCi9wXssNsKdaGXFhNw3Wla2AvWZ5P/t1Z+zrvqag8sAjEl+nI +7WHra3voOojDdZ1Kf6QhMQ/ZD1vm0mFa32tsRIUZ5vYP5qyXsgPEb2OE0QnKGCAM +DD1X96C/CEecunQyioAOaJ+AmwKBgQDm9LvmY0rSEGe/oiBvnrYjIyHUn59FcIlU +kGvTW1ynPtGT6vrOyZGDnw8uOEI00/E7YB8psdJLQ8aOgT4xUc2p7haNri/V794W +hhWs2+qvDWvURSRMF0PZeV1b2bDqDB3AP2XiwaHR3MQpc1t4chNNNB5vuD0TJVrB +NIXi0S41qwKBgQCR3l/17wQCyLQ7sn+8xV2ikyVDF1vveJHYRXMP+pmMZJe556u/ +vl1BFsIWGHDvjUm9N+7Arqa+Nhg0CjjEmj+UpnEBC4SOR2srZoE7l7+qTENKjy0l +8RetAi0FBm3NL01ePj20Ncjhi35c0VeTLtN+EUqo9Bfauo4t68xPWJBDcwKBgENk +3v/XsZmi1+N/t99afOO7+L9G5P8qW6iljBFc86iKGDYFt7Jn92JlI9Tk7czkm9wr +rGxKS4dS+7nR1QgnStBvfX1Sevr+x9vivKh4c/8o93I1yuW5VD89vxRybcGeT4At +/9kvj7zhowxFcUewYhmBP/Bx3sCbgeQnI3qQd9+JAoGAFgzLLXw5fdwjz1oz9Cwz +WetpWujjMImgsD7b/7XmKeKCG82uorsaFI5rBb4eJdgJHoqaNAEkFuNdhRcuqVh1 +uZG02rb8HICnhPV/4wgyhf6pZEWrpmF9q4aqoH67hfrRMuVUD250px3y2Ozs77JJ +c7S9s1qUr+vPk7+ywFh5xRk= +-----END PRIVATE KEY----- diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index 0d60945f..7d42c9e5 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -99,7 +99,9 @@ async fn start_server( match tls { Some(tls) => custom_axum::from_tcp_rustls_with_timeouts(socket, tls) .handle(handle) - .acceptor(TimeoutAcceptor) + // The TimeoutAcceptor is commented because TSL does not work with it. + // See: https://github.com/torrust/torrust-index/issues/204 + //.acceptor(TimeoutAcceptor) .serve(router.into_make_service_with_connect_info::()) .await .expect("API server should be running"), From be3ab6237b804b2b3e6bb98105c8ab3e78793c9b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 11:41:40 +0100 Subject: [PATCH 161/309] test: [#581] add some tests to configuration before refactoring We will overhaul the configuration, so it's convenient to have some tests. --- src/config.rs | 269 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index eb7b3915..9115dbf0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,7 +123,7 @@ impl From for Error { } /// Information displayed to the user in the website. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Website { /// The name of the website. pub name: String, @@ -139,7 +139,7 @@ impl Default for Website { /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum TrackerMode { // todo: use https://crates.io/crates/torrust-tracker-primitives /// Will track every new info hash and serve every peer. @@ -171,7 +171,7 @@ impl TrackerMode { } /// Configuration for the associated tracker. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Tracker { /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. pub url: String, @@ -211,7 +211,7 @@ impl Default for Tracker { pub const FREE_PORT: u16 = 0; /// The the base URL for the API. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Network { /// The port to listen on. Default to `3001`. pub port: u16, @@ -233,7 +233,7 @@ impl Default for Network { } /// Whether the email is required on signup or not. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum EmailOnSignup { /// The email is required on signup. Required, @@ -250,7 +250,7 @@ impl Default for EmailOnSignup { } /// Authentication options. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// Whether or not to require an email on signup. pub email_on_signup: EmailOnSignup, @@ -280,7 +280,7 @@ impl Auth { } /// Database configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Database { /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. pub connect_url: String, @@ -295,7 +295,7 @@ impl Default for Database { } /// SMTP configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Mail { /// Whether or not to enable email verification on signup. pub email_verification_enabled: bool, @@ -335,7 +335,7 @@ impl Default for Mail { /// proxy. The proxy will not download new images if the user has reached the /// quota. #[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ImageCache { /// Maximum time in seconds to wait for downloading the image form the original source. pub max_request_timeout_ms: u64, @@ -352,7 +352,7 @@ pub struct ImageCache { } /// Core configuration for the API -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Api { /// The default page size for torrent lists. pub default_torrent_page_size: u8, @@ -370,7 +370,7 @@ impl Default for Api { } /// Configuration for the tracker statistics importer. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TrackerStatisticsImporter { /// The interval in seconds to get statistics from the tracker. pub torrent_info_update_interval: u64, @@ -425,7 +425,7 @@ impl Tsl { } /// The whole configuration for the index. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct TorrustIndex { /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, /// `Debug` and `Trace`. Default is `Info`. @@ -637,10 +637,253 @@ fn parse_url(url_str: &str) -> Result { /// The public index configuration. /// There is an endpoint to get this configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ConfigurationPublic { website_name: String, tracker_url: String, tracker_mode: TrackerMode, email_on_signup: EmailOnSignup, } + +#[cfg(test)] +mod tests { + + use crate::config::{Configuration, ConfigurationPublic, Info}; + + #[cfg(test)] + fn default_config_toml() -> String { + let config = r#"[website] + name = "Torrust" + + [tracker] + url = "udp://localhost:6969" + mode = "Public" + api_url = "http://localhost:1212" + token = "MyAccessToken" + token_valid_seconds = 7257600 + + [net] + port = 3001 + + [auth] + email_on_signup = "Optional" + min_password_length = 6 + max_password_length = 64 + secret_key = "MaxVerstappenWC2021" + + [database] + connect_url = "sqlite://data.db?mode=rwc" + + [mail] + email_verification_enabled = false + from = "example@email.com" + reply_to = "noreply@email.com" + username = "" + password = "" + server = "" + port = 25 + + [image_cache] + max_request_timeout_ms = 1000 + capacity = 128000000 + entry_size_limit = 4000000 + user_quota_period_seconds = 3600 + user_quota_bytes = 64000000 + + [api] + default_torrent_page_size = 10 + max_torrent_page_size = 30 + + [tracker_statistics_importer] + torrent_info_update_interval = 3600 + port = 3002 + "# + .lines() + .map(str::trim_start) + .collect::>() + .join("\n"); + config + } + + #[tokio::test] + async fn configuration_should_build_settings_with_default_values() { + let configuration = Configuration::default().get_all().await; + + let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration"); + + assert_eq!(toml, default_config_toml()); + } + + #[tokio::test] + async fn configuration_should_return_all_settings() { + let configuration = Configuration::default().get_all().await; + + let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration"); + + assert_eq!(toml, default_config_toml()); + } + + #[tokio::test] + async fn configuration_should_return_only_public_settings() { + let configuration = Configuration::default(); + let all_settings = configuration.get_all().await; + + assert_eq!( + configuration.get_public().await, + ConfigurationPublic { + website_name: all_settings.website.name, + tracker_url: all_settings.tracker.url, + tracker_mode: all_settings.tracker.mode, + email_on_signup: all_settings.auth.email_on_signup, + } + ); + } + + #[tokio::test] + async fn configuration_should_return_the_site_name() { + let configuration = Configuration::default(); + assert_eq!(configuration.get_site_name().await, "Torrust".to_string()); + } + + #[tokio::test] + async fn configuration_should_return_the_api_base_url() { + let configuration = Configuration::default(); + assert_eq!(configuration.get_api_base_url().await, None); + + let mut settings_lock = configuration.settings.write().await; + settings_lock.net.base_url = Some("http://localhost".to_string()); + drop(settings_lock); + + assert_eq!(configuration.get_api_base_url().await, Some("http://localhost".to_string())); + } + + #[tokio::test] + async fn configuration_could_be_saved_in_a_toml_config_file() { + use std::{env, fs}; + + use uuid::Uuid; + + // Build temp config file path + let temp_directory = env::temp_dir(); + let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4())); + + // Convert to argument type for Configuration::save_to_file + let config_file_path = temp_file; + let path = config_file_path.to_string_lossy().to_string(); + + let default_configuration = Configuration::default(); + + default_configuration.save_to_file(&path).await; + + let contents = fs::read_to_string(&path).expect("written toml configuration file should be read"); + + assert_eq!(contents, default_config_toml()); + } + + #[tokio::test] + async fn configuration_could_be_loaded_from_a_toml_config_file() { + use std::{env, fs}; + + use uuid::Uuid; + + // Build temp config file path + let temp_directory = env::temp_dir(); + let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4())); + + let default_configuration = Configuration::default(); + + // Serialize the default configuration to TOML string + let toml_string = toml::to_string(&default_configuration.get_all().await).unwrap(); + + // Write the TOML string to the file + fs::write(&temp_file, toml_string).expect("Failed to write default configuration to a temp toml file"); + + // Convert to argument type for Configuration::save_to_file + let config_file_path = temp_file; + let path = config_file_path.to_string_lossy().to_string(); + + let configuration = Configuration::load_from_file(&path) + .await + .expect("Failed to load configuration from toml file"); + + assert_eq!(configuration.get_all().await, Configuration::default().get_all().await); + } + + #[tokio::test] + async fn configuration_could_be_loaded_from_a_toml_string() { + let info = Info { + index_toml: default_config_toml(), + tracker_api_token: None, + auth_secret_key: None, + }; + + let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); + + assert_eq!(configuration.get_all().await, Configuration::default().get_all().await); + } + + #[tokio::test] + async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() { + let info = Info { + index_toml: default_config_toml(), + tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()), + auth_secret_key: None, + }; + + let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); + + assert_eq!( + configuration.get_all().await.tracker.token, + "OVERRIDDEN API TOKEN".to_string() + ); + } + + #[tokio::test] + async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() { + let info = Info { + index_toml: default_config_toml(), + tracker_api_token: None, + auth_secret_key: Some("OVERRIDDEN AUTH SECRET KEY".to_string()), + }; + + let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); + + assert_eq!( + configuration.get_all().await.auth.secret_key, + "OVERRIDDEN AUTH SECRET KEY".to_string() + ); + } + + mod syntax_checks { + // todo: use rich types in configuration structs for basic syntax checks. + + use crate::config::Configuration; + + #[tokio::test] + async fn tracker_url_should_be_a_valid_url() { + let configuration = Configuration::default(); + + let mut settings_lock = configuration.settings.write().await; + settings_lock.tracker.url = "INVALID URL".to_string(); + drop(settings_lock); + + assert!(configuration.validate().await.is_err()); + } + } + + mod semantic_validation { + use crate::config::{Configuration, TrackerMode}; + + #[tokio::test] + async fn udp_trackers_in_close_mode_are_not_supported() { + let configuration = Configuration::default(); + + let mut settings_lock = configuration.settings.write().await; + settings_lock.tracker.mode = TrackerMode::Private; + settings_lock.tracker.url = "udp://localhost:6969".to_string(); + drop(settings_lock); + + assert!(configuration.validate().await.is_err()); + } + } +} From d7d6d88d2211d276200aeae6442f3102861aebca Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 15:14:36 +0100 Subject: [PATCH 162/309] chore: [#589] clean unused configuration code Load and save configuration from a toml file in the Configuration struct are not being used currently. --- src/config.rs | 105 ------------------ .../tracker_statistics_importer/app.rs | 2 +- 2 files changed, 1 insertion(+), 106 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9115dbf0..e863250d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,9 @@ //! Configuration for the application. -use std::path::Path; use std::sync::Arc; use std::{env, fs}; use camino::Utf8PathBuf; use config::{Config, ConfigError, File, FileFormat}; -use log::warn; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; @@ -487,42 +485,6 @@ impl Default for Configuration { } impl Configuration { - /// Loads the configuration from the configuration file. - /// - /// # Errors - /// - /// This function will return an error no configuration in the `CONFIG_PATH` exists, and a new file is is created. - /// This function will return an error if the `config` is not a valid `TorrustConfig` document. - pub async fn load_from_file(config_path: &str) -> Result { - let config_builder = Config::builder(); - - #[allow(unused_assignments)] - let mut config = Config::default(); - - if Path::new(config_path).exists() { - config = config_builder.add_source(File::with_name(config_path)).build()?; - } else { - warn!("No config file found. Creating default config file ..."); - - let config = Configuration::default(); - let () = config.save_to_file(config_path).await; - - return Err(ConfigError::Message(format!( - "No config file found. Created default config file in {config_path}. Edit the file and start the application." - ))); - } - - let torrust_config: TorrustIndex = match config.try_deserialize() { - Ok(data) => Ok(data), - Err(e) => Err(ConfigError::Message(format!("Errors while processing config: {e}."))), - }?; - - Ok(Configuration { - settings: RwLock::new(torrust_config), - config_path: Some(config_path.to_string()), - }) - } - /// Loads the configuration from the `Info` struct. The whole /// configuration in toml format is included in the `info.index_toml` string. /// @@ -554,21 +516,6 @@ impl Configuration { }) } - /// Returns the save to file of this [`Configuration`]. - /// - /// # Panics - /// - /// This function will panic if it can't write to the file. - pub async fn save_to_file(&self, config_path: &str) { - let settings = self.settings.read().await; - - let toml_string = toml::to_string(&*settings).expect("Could not encode TOML value"); - - drop(settings); - - fs::write(config_path, toml_string).expect("Could not write to file!"); - } - pub async fn get_all(&self) -> TorrustIndex { let settings_lock = self.settings.read().await; @@ -757,58 +704,6 @@ mod tests { assert_eq!(configuration.get_api_base_url().await, Some("http://localhost".to_string())); } - #[tokio::test] - async fn configuration_could_be_saved_in_a_toml_config_file() { - use std::{env, fs}; - - use uuid::Uuid; - - // Build temp config file path - let temp_directory = env::temp_dir(); - let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4())); - - // Convert to argument type for Configuration::save_to_file - let config_file_path = temp_file; - let path = config_file_path.to_string_lossy().to_string(); - - let default_configuration = Configuration::default(); - - default_configuration.save_to_file(&path).await; - - let contents = fs::read_to_string(&path).expect("written toml configuration file should be read"); - - assert_eq!(contents, default_config_toml()); - } - - #[tokio::test] - async fn configuration_could_be_loaded_from_a_toml_config_file() { - use std::{env, fs}; - - use uuid::Uuid; - - // Build temp config file path - let temp_directory = env::temp_dir(); - let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4())); - - let default_configuration = Configuration::default(); - - // Serialize the default configuration to TOML string - let toml_string = toml::to_string(&default_configuration.get_all().await).unwrap(); - - // Write the TOML string to the file - fs::write(&temp_file, toml_string).expect("Failed to write default configuration to a temp toml file"); - - // Convert to argument type for Configuration::save_to_file - let config_file_path = temp_file; - let path = config_file_path.to_string_lossy().to_string(); - - let configuration = Configuration::load_from_file(&path) - .await - .expect("Failed to load configuration from toml file"); - - assert_eq!(configuration.get_all().await, Configuration::default().get_all().await); - } - #[tokio::test] async fn configuration_could_be_loaded_from_a_toml_string() { let info = Info { diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index 115b7bd8..9bcd20b8 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -84,7 +84,7 @@ pub async fn run() { /// /// # Panics /// -/// Panics if `Configuration::load_from_file` has any error. +/// Panics if it can't connect to the database. pub async fn import() { println!("Importing statistics from linked tracker ..."); From 6c98e2b262f34a90ada7643c2e8f76c0e843dfb5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 15:52:24 +0100 Subject: [PATCH 163/309] refactor: [#589] versioning for configuration --- src/{config.rs => config/mod.rs} | 296 +------------------ src/config/v1/api.rs | 19 ++ src/config/v1/auth.rs | 48 +++ src/config/v1/database.rs | 16 + src/config/v1/image_cache.rs | 37 +++ src/config/v1/mail.rs | 34 +++ src/config/v1/mod.rs | 64 ++++ src/config/v1/net.rs | 25 ++ src/config/v1/tracker.rs | 38 +++ src/config/v1/tracker_statistics_importer.rs | 19 ++ src/config/v1/website.rs | 16 + 11 files changed, 330 insertions(+), 282 deletions(-) rename src/{config.rs => config/mod.rs} (66%) create mode 100644 src/config/v1/api.rs create mode 100644 src/config/v1/auth.rs create mode 100644 src/config/v1/database.rs create mode 100644 src/config/v1/image_cache.rs create mode 100644 src/config/v1/mail.rs create mode 100644 src/config/v1/mod.rs create mode 100644 src/config/v1/net.rs create mode 100644 src/config/v1/tracker.rs create mode 100644 src/config/v1/tracker_statistics_importer.rs create mode 100644 src/config/v1/website.rs diff --git a/src/config.rs b/src/config/mod.rs similarity index 66% rename from src/config.rs rename to src/config/mod.rs index e863250d..451b1315 100644 --- a/src/config.rs +++ b/src/config/mod.rs @@ -1,4 +1,6 @@ //! Configuration for the application. +pub mod v1; + use std::sync::Arc; use std::{env, fs}; @@ -11,6 +13,18 @@ use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; use url::{ParseError, Url}; +pub type TorrustIndex = v1::TorrustIndex; +pub type Api = v1::api::Api; +pub type Auth = v1::auth::Auth; +pub type Database = v1::database::Database; +pub type ImageCache = v1::image_cache::ImageCache; +pub type Mail = v1::mail::Mail; +pub type Network = v1::net::Network; +pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; +pub type Tracker = v1::tracker::Tracker; +pub type Website = v1::website::Website; +pub type EmailOnSignup = v1::auth::EmailOnSignup; + /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { @@ -120,21 +134,6 @@ impl From for Error { } } -/// Information displayed to the user in the website. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Website { - /// The name of the website. - pub name: String, -} - -impl Default for Website { - fn default() -> Self { - Self { - name: "Torrust".to_string(), - } - } -} - /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -168,235 +167,11 @@ impl TrackerMode { } } -/// Configuration for the associated tracker. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Tracker { - /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. - pub url: String, - /// The mode of the tracker. For example: `Public`. - /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) - /// crate for more information. - pub mode: TrackerMode, - /// The url of the tracker API. For example: `http://localhost:1212`. - pub api_url: String, - /// The token used to authenticate with the tracker API. - pub token: String, - /// The amount of seconds the token is valid. - pub token_valid_seconds: u64, -} - -impl Tracker { - fn override_tracker_api_token(&mut self, tracker_api_token: &str) { - self.token = tracker_api_token.to_string(); - } -} - -impl Default for Tracker { - fn default() -> Self { - Self { - url: "udp://localhost:6969".to_string(), - mode: TrackerMode::default(), - api_url: "http://localhost:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - } - } -} - /// Port number representing that the OS will choose one randomly from the available ports. /// /// It's the port number `0` pub const FREE_PORT: u16 = 0; -/// The the base URL for the API. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Network { - /// The port to listen on. Default to `3001`. - pub port: u16, - /// The base URL for the API. For example: `http://localhost`. - /// If not set, the base URL will be inferred from the request. - pub base_url: Option, - /// TSL configuration. - pub tsl: Option, -} - -impl Default for Network { - fn default() -> Self { - Self { - port: 3001, - base_url: None, - tsl: None, - } - } -} - -/// Whether the email is required on signup or not. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EmailOnSignup { - /// The email is required on signup. - Required, - /// The email is optional on signup. - Optional, - /// The email is not allowed on signup. It will only be ignored if provided. - None, // code-review: rename to `Ignored`? -} - -impl Default for EmailOnSignup { - fn default() -> Self { - Self::Optional - } -} - -/// Authentication options. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Auth { - /// Whether or not to require an email on signup. - pub email_on_signup: EmailOnSignup, - /// The minimum password length. - pub min_password_length: usize, - /// The maximum password length. - pub max_password_length: usize, - /// The secret key used to sign JWT tokens. - pub secret_key: String, -} - -impl Default for Auth { - fn default() -> Self { - Self { - email_on_signup: EmailOnSignup::default(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - } - } -} - -impl Auth { - fn override_secret_key(&mut self, secret_key: &str) { - self.secret_key = secret_key.to_string(); - } -} - -/// Database configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Database { - /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. - pub connect_url: String, -} - -impl Default for Database { - fn default() -> Self { - Self { - connect_url: "sqlite://data.db?mode=rwc".to_string(), - } - } -} - -/// SMTP configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Mail { - /// Whether or not to enable email verification on signup. - pub email_verification_enabled: bool, - /// The email address to send emails from. - pub from: String, - /// The email address to reply to. - pub reply_to: String, - /// The username to use for SMTP authentication. - pub username: String, - /// The password to use for SMTP authentication. - pub password: String, - /// The SMTP server to use. - pub server: String, - /// The SMTP port to use. - pub port: u16, -} - -impl Default for Mail { - fn default() -> Self { - Self { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::default(), - password: String::default(), - server: String::default(), - port: 25, - } - } -} - -/// Configuration for the image proxy cache. -/// -/// Users have a cache quota per period. For example: 100MB per day. -/// When users are navigating the site, they will be downloading images that are -/// embedded in the torrent description. These images will be cached in the -/// proxy. The proxy will not download new images if the user has reached the -/// quota. -#[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ImageCache { - /// Maximum time in seconds to wait for downloading the image form the original source. - pub max_request_timeout_ms: u64, - /// Cache size in bytes. - pub capacity: usize, - /// Maximum size in bytes for a single image. - pub entry_size_limit: usize, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the period in seconds (1 day in seconds). - pub user_quota_period_seconds: u64, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the maximum size in bytes (100MB in bytes). - pub user_quota_bytes: usize, -} - -/// Core configuration for the API -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Api { - /// The default page size for torrent lists. - pub default_torrent_page_size: u8, - /// The maximum page size for torrent lists. - pub max_torrent_page_size: u8, -} - -impl Default for Api { - fn default() -> Self { - Self { - default_torrent_page_size: 10, - max_torrent_page_size: 30, - } - } -} - -/// Configuration for the tracker statistics importer. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TrackerStatisticsImporter { - /// The interval in seconds to get statistics from the tracker. - pub torrent_info_update_interval: u64, - /// The port the Importer API is listening on. Default to `3002`. - pub port: u16, -} - -impl Default for TrackerStatisticsImporter { - fn default() -> Self { - Self { - torrent_info_update_interval: 3600, - port: 3002, - } - } -} - -impl Default for ImageCache { - fn default() -> Self { - Self { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - } - } -} - #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct Tsl { @@ -422,49 +197,6 @@ impl Tsl { } } -/// The whole configuration for the index. -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct TorrustIndex { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. - pub log_level: Option, - /// The website customizable values. - pub website: Website, - /// The tracker configuration. - pub tracker: Tracker, - /// The network configuration. - pub net: Network, - /// The authentication configuration. - pub auth: Auth, - /// The database configuration. - pub database: Database, - /// The SMTP configuration. - pub mail: Mail, - /// The image proxy cache configuration. - pub image_cache: ImageCache, - /// The API configuration. - pub api: Api, - /// The tracker statistics importer job configuration. - pub tracker_statistics_importer: TrackerStatisticsImporter, -} - -impl TorrustIndex { - fn override_tracker_api_token(&mut self, tracker_api_token: &str) { - self.tracker.override_tracker_api_token(tracker_api_token); - } - - fn override_auth_secret_key(&mut self, auth_secret_key: &str) { - self.auth.override_secret_key(auth_secret_key); - } - - pub fn remove_secrets(&mut self) { - "***".clone_into(&mut self.tracker.token); - "***".clone_into(&mut self.database.connect_url); - "***".clone_into(&mut self.mail.password); - "***".clone_into(&mut self.auth.secret_key); - } -} - /// The configuration service. #[derive(Debug)] pub struct Configuration { diff --git a/src/config/v1/api.rs b/src/config/v1/api.rs new file mode 100644 index 00000000..70f3ddab --- /dev/null +++ b/src/config/v1/api.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// Core configuration for the API +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Api { + /// The default page size for torrent lists. + pub default_torrent_page_size: u8, + /// The maximum page size for torrent lists. + pub max_torrent_page_size: u8, +} + +impl Default for Api { + fn default() -> Self { + Self { + default_torrent_page_size: 10, + max_torrent_page_size: 30, + } + } +} diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs new file mode 100644 index 00000000..13fec842 --- /dev/null +++ b/src/config/v1/auth.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +/// Authentication options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Auth { + /// Whether or not to require an email on signup. + pub email_on_signup: EmailOnSignup, + /// The minimum password length. + pub min_password_length: usize, + /// The maximum password length. + pub max_password_length: usize, + /// The secret key used to sign JWT tokens. + pub secret_key: String, +} + +impl Default for Auth { + fn default() -> Self { + Self { + email_on_signup: EmailOnSignup::default(), + min_password_length: 6, + max_password_length: 64, + secret_key: "MaxVerstappenWC2021".to_string(), + } + } +} + +impl Auth { + pub fn override_secret_key(&mut self, secret_key: &str) { + self.secret_key = secret_key.to_string(); + } +} + +/// Whether the email is required on signup or not. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum EmailOnSignup { + /// The email is required on signup. + Required, + /// The email is optional on signup. + Optional, + /// The email is not allowed on signup. It will only be ignored if provided. + None, // code-review: rename to `Ignored`? +} + +impl Default for EmailOnSignup { + fn default() -> Self { + Self::Optional + } +} diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs new file mode 100644 index 00000000..325a8936 --- /dev/null +++ b/src/config/v1/database.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Database configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Database { + /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. + pub connect_url: String, +} + +impl Default for Database { + fn default() -> Self { + Self { + connect_url: "sqlite://data.db?mode=rwc".to_string(), + } + } +} diff --git a/src/config/v1/image_cache.rs b/src/config/v1/image_cache.rs new file mode 100644 index 00000000..4f2990b0 --- /dev/null +++ b/src/config/v1/image_cache.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +/// Configuration for the image proxy cache. +/// +/// Users have a cache quota per period. For example: 100MB per day. +/// When users are navigating the site, they will be downloading images that are +/// embedded in the torrent description. These images will be cached in the +/// proxy. The proxy will not download new images if the user has reached the +/// quota. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ImageCache { + /// Maximum time in seconds to wait for downloading the image form the original source. + pub max_request_timeout_ms: u64, + /// Cache size in bytes. + pub capacity: usize, + /// Maximum size in bytes for a single image. + pub entry_size_limit: usize, + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the period in seconds (1 day in seconds). + pub user_quota_period_seconds: u64, + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the maximum size in bytes (100MB in bytes). + pub user_quota_bytes: usize, +} + +impl Default for ImageCache { + fn default() -> Self { + Self { + max_request_timeout_ms: 1000, + capacity: 128_000_000, + entry_size_limit: 4_000_000, + user_quota_period_seconds: 3600, + user_quota_bytes: 64_000_000, + } + } +} diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs new file mode 100644 index 00000000..4e166281 --- /dev/null +++ b/src/config/v1/mail.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Mail { + /// Whether or not to enable email verification on signup. + pub email_verification_enabled: bool, + /// The email address to send emails from. + pub from: String, + /// The email address to reply to. + pub reply_to: String, + /// The username to use for SMTP authentication. + pub username: String, + /// The password to use for SMTP authentication. + pub password: String, + /// The SMTP server to use. + pub server: String, + /// The SMTP port to use. + pub port: u16, +} + +impl Default for Mail { + fn default() -> Self { + Self { + email_verification_enabled: false, + from: "example@email.com".to_string(), + reply_to: "noreply@email.com".to_string(), + username: String::default(), + password: String::default(), + server: String::default(), + port: 25, + } + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs new file mode 100644 index 00000000..fa44aafc --- /dev/null +++ b/src/config/v1/mod.rs @@ -0,0 +1,64 @@ +pub mod api; +pub mod auth; +pub mod database; +pub mod image_cache; +pub mod mail; +pub mod net; +pub mod tracker; +pub mod tracker_statistics_importer; +pub mod website; + +use serde::{Deserialize, Serialize}; + +use self::api::Api; +use self::auth::Auth; +use self::database::Database; +use self::image_cache::ImageCache; +use self::mail::Mail; +use self::net::Network; +use self::tracker::Tracker; +use self::tracker_statistics_importer::TrackerStatisticsImporter; +use self::website::Website; + +/// The whole configuration for the index. +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct TorrustIndex { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, + /// `Debug` and `Trace`. Default is `Info`. + pub log_level: Option, + /// The website customizable values. + pub website: Website, + /// The tracker configuration. + pub tracker: Tracker, + /// The network configuration. + pub net: Network, + /// The authentication configuration. + pub auth: Auth, + /// The database configuration. + pub database: Database, + /// The SMTP configuration. + pub mail: Mail, + /// The image proxy cache configuration. + pub image_cache: ImageCache, + /// The API configuration. + pub api: Api, + /// The tracker statistics importer job configuration. + pub tracker_statistics_importer: TrackerStatisticsImporter, +} + +impl TorrustIndex { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { + self.tracker.override_tracker_api_token(tracker_api_token); + } + + pub fn override_auth_secret_key(&mut self, auth_secret_key: &str) { + self.auth.override_secret_key(auth_secret_key); + } + + pub fn remove_secrets(&mut self) { + "***".clone_into(&mut self.tracker.token); + "***".clone_into(&mut self.database.connect_url); + "***".clone_into(&mut self.mail.password); + "***".clone_into(&mut self.auth.secret_key); + } +} diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs new file mode 100644 index 00000000..fa473e16 --- /dev/null +++ b/src/config/v1/net.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::Tsl; + +/// The the base URL for the API. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Network { + /// The port to listen on. Default to `3001`. + pub port: u16, + /// The base URL for the API. For example: `http://localhost`. + /// If not set, the base URL will be inferred from the request. + pub base_url: Option, + /// TSL configuration. + pub tsl: Option, +} + +impl Default for Network { + fn default() -> Self { + Self { + port: 3001, + base_url: None, + tsl: None, + } + } +} diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs new file mode 100644 index 00000000..ff0ffe71 --- /dev/null +++ b/src/config/v1/tracker.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::TrackerMode; + +/// Configuration for the associated tracker. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Tracker { + /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. + pub url: String, + /// The mode of the tracker. For example: `Public`. + /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) + /// crate for more information. + pub mode: TrackerMode, + /// The url of the tracker API. For example: `http://localhost:1212`. + pub api_url: String, + /// The token used to authenticate with the tracker API. + pub token: String, + /// The amount of seconds the token is valid. + pub token_valid_seconds: u64, +} + +impl Tracker { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { + self.token = tracker_api_token.to_string(); + } +} + +impl Default for Tracker { + fn default() -> Self { + Self { + url: "udp://localhost:6969".to_string(), + mode: TrackerMode::default(), + api_url: "http://localhost:1212".to_string(), + token: "MyAccessToken".to_string(), + token_valid_seconds: 7_257_600, + } + } +} diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v1/tracker_statistics_importer.rs new file mode 100644 index 00000000..9c43a4a4 --- /dev/null +++ b/src/config/v1/tracker_statistics_importer.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// Configuration for the tracker statistics importer. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TrackerStatisticsImporter { + /// The interval in seconds to get statistics from the tracker. + pub torrent_info_update_interval: u64, + /// The port the Importer API is listening on. Default to `3002`. + pub port: u16, +} + +impl Default for TrackerStatisticsImporter { + fn default() -> Self { + Self { + torrent_info_update_interval: 3600, + port: 3002, + } + } +} diff --git a/src/config/v1/website.rs b/src/config/v1/website.rs new file mode 100644 index 00000000..a7a6fbef --- /dev/null +++ b/src/config/v1/website.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Information displayed to the user in the website. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Website { + /// The name of the website. + pub name: String, +} + +impl Default for Website { + fn default() -> Self { + Self { + name: "Torrust".to_string(), + } + } +} From 8675d6b566951432c7df6c86198022f9604d166b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 16:34:03 +0100 Subject: [PATCH 164/309] docs: [#589] add todo for TrackerMode --- src/config/mod.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 451b1315..32f87a7f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -134,17 +134,41 @@ impl From for Error { } } +/* todo: + +Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. + +Enum variants (Index -> Tracker): + +- `Public` -> `Public` +- `Private` -> `Private` +- `Whitelisted` -> `Listed` +- `PrivateWhitelisted` -> `PrivateListed` + +Enum serialized values (Index -> Tracker): + +- `Public` -> `public` +- `Private` -> `private` +- `Whitelisted` -> `listed` +- `PrivateWhitelisted` -> `private_listed` + +It's a breaking change for the toml config file en the API. + +*/ + /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum TrackerMode { - // todo: use https://crates.io/crates/torrust-tracker-primitives /// Will track every new info hash and serve every peer. Public, + /// Will only serve authenticated peers. Private, + /// Will only track whitelisted info hashes. Whitelisted, + /// Will only track whitelisted info hashes and serve authenticated peers. PrivateWhitelisted, } From 07a3fb68514ddeaba24a38dfcbbf3fe0fb83904b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 17:00:49 +0100 Subject: [PATCH 165/309] reafctor: reaname TorrustIndex to Settings --- src/config/mod.rs | 2 +- src/config/v1/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 32f87a7f..61c7c179 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -13,7 +13,7 @@ use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; use url::{ParseError, Url}; -pub type TorrustIndex = v1::TorrustIndex; +pub type TorrustIndex = v1::Settings; pub type Api = v1::api::Api; pub type Auth = v1::auth::Auth; pub type Database = v1::database::Database; diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index fa44aafc..c84132fe 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -22,7 +22,7 @@ use self::website::Website; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct TorrustIndex { +pub struct Settings { /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, /// `Debug` and `Trace`. Default is `Info`. pub log_level: Option, @@ -46,7 +46,7 @@ pub struct TorrustIndex { pub tracker_statistics_importer: TrackerStatisticsImporter, } -impl TorrustIndex { +impl Settings { pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { self.tracker.override_tracker_api_token(tracker_api_token); } From 1a283092217025aa779e09f3ab932e36a181ae6f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 17:05:28 +0100 Subject: [PATCH 166/309] refactor: [#581] remove unused code --- src/config/mod.rs | 5 ----- tests/environments/app_starter.rs | 5 +---- tests/environments/isolated.rs | 6 +----- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 61c7c179..f06b9498 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -226,16 +226,12 @@ impl Tsl { pub struct Configuration { /// The state of the configuration. pub settings: RwLock, - /// The path to the configuration file. This is `None` if the configuration - /// was loaded from the environment. - pub config_path: Option, } impl Default for Configuration { fn default() -> Configuration { Configuration { settings: RwLock::new(TorrustIndex::default()), - config_path: None, } } } @@ -268,7 +264,6 @@ impl Configuration { Ok(Configuration { settings: RwLock::new(index_config), - config_path: None, }) } diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index ec04a560..16e4306f 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -10,7 +10,6 @@ use torrust_index::{app, config}; /// It launches the app and provides a way to stop it. pub struct AppStarter { configuration: config::TorrustIndex, - config_path: Option, /// The application binary state (started or not): /// - `None`: if the app is not started, /// - `RunningState`: if the app was started. @@ -19,10 +18,9 @@ pub struct AppStarter { impl AppStarter { #[must_use] - pub fn with_custom_configuration(configuration: config::TorrustIndex, config_path: Option) -> Self { + pub fn with_custom_configuration(configuration: config::TorrustIndex) -> Self { Self { configuration, - config_path, running_state: None, } } @@ -35,7 +33,6 @@ impl AppStarter { pub async fn start(&mut self, api_version: Version) { let configuration = Configuration { settings: RwLock::new(self.configuration.clone()), - config_path: self.config_path.clone(), }; // Open a channel to communicate back with this function diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 175976f2..bcb2eba2 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -33,12 +33,8 @@ impl TestEnv { let temp_dir = TempDir::new().expect("failed to create a temporary directory"); let configuration = ephemeral(&temp_dir); - // Even if we load the configuration from the environment variable, we - // still need to provide a path to save the configuration when the - // configuration is updated via the `POST /settings` endpoints. - let config_path = format!("{}/config.toml", temp_dir.path().to_string_lossy()); - let app_starter = AppStarter::with_custom_configuration(configuration, Some(config_path)); + let app_starter = AppStarter::with_custom_configuration(configuration); Self { app_starter, temp_dir } } From 790e1ec928dc93afef2ad495f50634f7eeac70ed Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 17:10:11 +0100 Subject: [PATCH 167/309] refactor: [#581] rename TorrustIndex to Settings --- src/config/mod.rs | 16 ++++++++-------- src/services/settings.rs | 6 +++--- src/web/api/client/v1/contexts/settings/mod.rs | 2 +- src/web/api/server/v1/contexts/settings/mod.rs | 4 ++-- tests/common/contexts/settings/mod.rs | 2 +- tests/environments/app_starter.rs | 6 +++--- tests/environments/isolated.rs | 8 ++++---- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index f06b9498..a2b1ec6b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -13,7 +13,7 @@ use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; use url::{ParseError, Url}; -pub type TorrustIndex = v1::Settings; +pub type Settings = v1::Settings; pub type Api = v1::api::Api; pub type Auth = v1::auth::Auth; pub type Database = v1::database::Database; @@ -225,13 +225,13 @@ impl Tsl { #[derive(Debug)] pub struct Configuration { /// The state of the configuration. - pub settings: RwLock, + pub settings: RwLock, } impl Default for Configuration { fn default() -> Configuration { Configuration { - settings: RwLock::new(TorrustIndex::default()), + settings: RwLock::new(Settings::default()), } } } @@ -252,22 +252,22 @@ impl Configuration { let config_builder = Config::builder() .add_source(File::from_str(&info.index_toml, FileFormat::Toml)) .build()?; - let mut index_config: TorrustIndex = config_builder.try_deserialize()?; + let mut settings: Settings = config_builder.try_deserialize()?; if let Some(ref token) = info.tracker_api_token { - index_config.override_tracker_api_token(token); + settings.override_tracker_api_token(token); }; if let Some(ref secret_key) = info.auth_secret_key { - index_config.override_auth_secret_key(secret_key); + settings.override_auth_secret_key(secret_key); }; Ok(Configuration { - settings: RwLock::new(index_config), + settings: RwLock::new(settings), }) } - pub async fn get_all(&self) -> TorrustIndex { + pub async fn get_all(&self) -> Settings { let settings_lock = self.settings.read().await; settings_lock.clone() diff --git a/src/services/settings.rs b/src/services/settings.rs index 1693df73..a4b0e92e 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use super::user::DbUserRepository; -use crate::config::{Configuration, ConfigurationPublic, TorrustIndex}; +use crate::config::{Configuration, ConfigurationPublic, Settings}; use crate::errors::ServiceError; use crate::models::user::UserId; @@ -25,7 +25,7 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_all(&self, user_id: &UserId) -> Result { + pub async fn get_all(&self, user_id: &UserId) -> Result { let user = self.user_repository.get_compact(user_id).await?; // Check if user is administrator @@ -44,7 +44,7 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_all_masking_secrets(&self, user_id: &UserId) -> Result { + pub async fn get_all_masking_secrets(&self, user_id: &UserId) -> Result { let user = self.user_repository.get_compact(user_id).await?; // Check if user is administrator diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index e08a4103..e8f54759 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, TorrustIndex as DomainSettings, Tracker as DomainTracker, + Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index bd8cd54c..e5978890 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -82,7 +82,7 @@ //! ``` //! **Resource** //! -//! Refer to the [`TorrustIndex`](crate::config::TorrustIndex) +//! Refer to the [`TorrustIndex`](crate::config::Settings) //! struct for more information about the response attributes. //! //! # Update all settings @@ -110,7 +110,7 @@ //! //! **Resource** //! -//! Refer to the [`TorrustIndex`](crate::config::TorrustIndex) +//! Refer to the [`TorrustIndex`](crate::config::Settings) //! struct for more information about the response attributes. //! //! # Get site name diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index ece13b6d..42e4771d 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -3,7 +3,7 @@ pub mod responses; use serde::{Deserialize, Serialize}; use torrust_index::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, TorrustIndex as DomainSettings, Tracker as DomainTracker, + Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index 16e4306f..a981d524 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -9,7 +9,7 @@ use torrust_index::{app, config}; /// It launches the app and provides a way to stop it. pub struct AppStarter { - configuration: config::TorrustIndex, + configuration: config::Settings, /// The application binary state (started or not): /// - `None`: if the app is not started, /// - `RunningState`: if the app was started. @@ -18,7 +18,7 @@ pub struct AppStarter { impl AppStarter { #[must_use] - pub fn with_custom_configuration(configuration: config::TorrustIndex) -> Self { + pub fn with_custom_configuration(configuration: config::Settings) -> Self { Self { configuration, running_state: None, @@ -78,7 +78,7 @@ impl AppStarter { } #[must_use] - pub fn server_configuration(&self) -> config::TorrustIndex { + pub fn server_configuration(&self) -> config::Settings { self.configuration.clone() } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index bcb2eba2..bb9dc24e 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -46,7 +46,7 @@ impl TestEnv { /// Provides the whole server configuration. #[must_use] - pub fn server_configuration(&self) -> config::TorrustIndex { + pub fn server_configuration(&self) -> config::Settings { self.app_starter.server_configuration() } @@ -69,10 +69,10 @@ impl Default for TestEnv { } /// Provides a configuration with ephemeral data for testing. -fn ephemeral(temp_dir: &TempDir) -> config::TorrustIndex { - let mut configuration = config::TorrustIndex { +fn ephemeral(temp_dir: &TempDir) -> config::Settings { + let mut configuration = config::Settings { log_level: Some("off".to_owned()), // Change to `debug` for tests debugging - ..config::TorrustIndex::default() + ..config::Settings::default() }; // Ephemeral API port From 8d3c8cba0396c412b7baa72331fbab6173fbb764 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 20 May 2024 16:51:30 +0100 Subject: [PATCH 168/309] refactor: [#581] move semantic validation to the type that has the knowledge. --- src/app.rs | 5 +-- src/config/mod.rs | 74 +++++++++------------------------------- src/config/v1/mod.rs | 7 ++++ src/config/v1/tracker.rs | 26 +++++++++++++- src/config/validator.rs | 22 ++++++++++++ 5 files changed, 73 insertions(+), 61 deletions(-) create mode 100644 src/config/validator.rs diff --git a/src/app.rs b/src/app.rs index a360ed79..9df253e6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,7 @@ use tokio::task::JoinHandle; use crate::bootstrap::logging; use crate::cache::image::manager::ImageCacheService; use crate::common::AppData; +use crate::config::validator::Validator; use crate::config::Configuration; use crate::databases::database; use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service}; @@ -41,8 +42,6 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running logging::setup(&log_level); - configuration.validate().await.expect("invalid configuration"); - let configuration = Arc::new(configuration); // Get configuration settings needed to build the app dependencies and @@ -50,6 +49,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let settings = configuration.settings.read().await; + settings.validate().expect("invalid settings"); + // From [database] config let database_connect_url = settings.database.connect_url.clone(); // From [importer] config diff --git a/src/config/mod.rs b/src/config/mod.rs index a2b1ec6b..24d1efc5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ //! Configuration for the application. pub mod v1; +pub mod validator; use std::sync::Arc; use std::{env, fs}; @@ -11,7 +12,7 @@ use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; -use url::{ParseError, Url}; +use url::Url; pub type Settings = v1::Settings; pub type Api = v1::api::Api; @@ -114,26 +115,6 @@ pub enum Error { Infallible, } -/// Errors that can occur validating the configuration. -#[derive(Error, Debug)] -pub enum ValidationError { - /// Unable to load the configuration from the configuration file. - #[error("Invalid tracker URL: {source}")] - InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, - - #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] - UdpTrackersInPrivateModeNotSupported, -} - -impl From for Error { - #[track_caller] - fn from(err: ConfigError) -> Self { - Self::ConfigError { - source: Located(err).into(), - } - } -} - /* todo: Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. @@ -295,44 +276,17 @@ impl Configuration { settings_lock.net.base_url.clone() } +} - /// # Errors - /// - /// Will return an error if the configuration is invalid. - pub async fn validate(&self) -> Result<(), ValidationError> { - self.validate_tracker_config().await - } - - /// # Errors - /// - /// Will return an error if the `tracker` configuration section is invalid. - pub async fn validate_tracker_config(&self) -> Result<(), ValidationError> { - let settings_lock = self.settings.read().await; - - let tracker_mode = settings_lock.tracker.mode.clone(); - let tracker_url = settings_lock.tracker.url.clone(); - - let tracker_url = match parse_url(&tracker_url) { - Ok(url) => url, - Err(err) => { - return Err(ValidationError::InvalidTrackerUrl { - source: Located(err).into(), - }) - } - }; - - if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { - return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); +impl From for Error { + #[track_caller] + fn from(err: ConfigError) -> Self { + Self::ConfigError { + source: Located(err).into(), } - - Ok(()) } } -fn parse_url(url_str: &str) -> Result { - Url::parse(url_str) -} - /// The public index configuration. /// There is an endpoint to get this configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -343,6 +297,10 @@ pub struct ConfigurationPublic { email_on_signup: EmailOnSignup, } +fn parse_url(url_str: &str) -> Result { + Url::parse(url_str) +} + #[cfg(test)] mod tests { @@ -503,6 +461,7 @@ mod tests { mod syntax_checks { // todo: use rich types in configuration structs for basic syntax checks. + use crate::config::validator::Validator; use crate::config::Configuration; #[tokio::test] @@ -511,13 +470,13 @@ mod tests { let mut settings_lock = configuration.settings.write().await; settings_lock.tracker.url = "INVALID URL".to_string(); - drop(settings_lock); - assert!(configuration.validate().await.is_err()); + assert!(settings_lock.validate().is_err()); } } mod semantic_validation { + use crate::config::validator::Validator; use crate::config::{Configuration, TrackerMode}; #[tokio::test] @@ -527,9 +486,8 @@ mod tests { let mut settings_lock = configuration.settings.write().await; settings_lock.tracker.mode = TrackerMode::Private; settings_lock.tracker.url = "udp://localhost:6969".to_string(); - drop(settings_lock); - assert!(configuration.validate().await.is_err()); + assert!(settings_lock.validate().is_err()); } } } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index c84132fe..04d8b9f9 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -19,6 +19,7 @@ use self::net::Network; use self::tracker::Tracker; use self::tracker_statistics_importer::TrackerStatisticsImporter; use self::website::Website; +use super::validator::{ValidationError, Validator}; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] @@ -62,3 +63,9 @@ impl Settings { "***".clone_into(&mut self.auth.secret_key); } } + +impl Validator for Settings { + fn validate(&self) -> Result<(), ValidationError> { + self.tracker.validate() + } +} diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index ff0ffe71..fd95a82e 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; +use torrust_index_located_error::Located; -use crate::config::TrackerMode; +use super::{ValidationError, Validator}; +use crate::config::{parse_url, TrackerMode}; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -25,6 +27,28 @@ impl Tracker { } } +impl Validator for Tracker { + fn validate(&self) -> Result<(), ValidationError> { + let tracker_mode = self.mode.clone(); + let tracker_url = self.url.clone(); + + let tracker_url = match parse_url(&tracker_url) { + Ok(url) => url, + Err(err) => { + return Err(ValidationError::InvalidTrackerUrl { + source: Located(err).into(), + }) + } + }; + + if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { + return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); + } + + Ok(()) + } +} + impl Default for Tracker { fn default() -> Self { Self { diff --git a/src/config/validator.rs b/src/config/validator.rs new file mode 100644 index 00000000..d3ef047a --- /dev/null +++ b/src/config/validator.rs @@ -0,0 +1,22 @@ +//! Trait to validate the whole settings of sections of the settings. +use thiserror::Error; +use torrust_index_located_error::LocatedError; +use url::ParseError; + +/// Errors that can occur validating the configuration. +#[derive(Error, Debug)] +pub enum ValidationError { + /// Unable to load the configuration from the configuration file. + #[error("Invalid tracker URL: {source}")] + InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, + + #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] + UdpTrackersInPrivateModeNotSupported, +} + +pub trait Validator { + /// # Errors + /// + /// Will return an error if the configuration is invalid. + fn validate(&self) -> Result<(), ValidationError>; +} From ccb2fef9b035e418c4554ea6762075595072071d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 20 May 2024 16:54:15 +0100 Subject: [PATCH 169/309] chore(deps): add cargo dep figment --- Cargo.lock | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 84 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7493ae71..9066aed3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -895,6 +904,22 @@ dependencies = [ "log", ] +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "parking_lot", + "pear", + "serde", + "tempfile", + "toml", + "uncased", + "version_check", +] + [[package]] name = "finl_unicode" version = "1.2.0" @@ -1417,6 +1442,12 @@ dependencies = [ "serde", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "ipnet" version = "2.9.0" @@ -1920,6 +1951,29 @@ dependencies = [ "sha2", ] +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.63", +] + [[package]] name = "pem" version = "3.0.4" @@ -2089,6 +2143,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", + "version_check", + "yansi", +] + [[package]] name = "psm" version = "0.1.21" @@ -3357,6 +3424,7 @@ dependencies = [ "derive_more", "email_address", "fern", + "figment", "futures", "futures-util", "hex", @@ -3527,6 +3595,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4105,6 +4182,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index f3de4e4e..97e20351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ config = "0" derive_more = "0" email_address = "0" fern = "0" +figment = { version = "0.10", features = ["env", "test", "toml"] } futures = "0" futures-util = "0.3.30" hex = "0" From 018dfc33504b8a0e3dd5df8b984732852644b5ec Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 20 May 2024 18:18:38 +0100 Subject: [PATCH 170/309] refactor: [#581] migration to Figment Use Figment to load settings from toml file and allow overriding settings with env vars. --- src/config/mod.rs | 201 ++++++++++++++++++++++++++----------- src/utils/parse_torrent.rs | 14 ++- 2 files changed, 151 insertions(+), 64 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 24d1efc5..cf60b902 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,18 +2,21 @@ pub mod v1; pub mod validator; +use std::env; use std::sync::Arc; -use std::{env, fs}; use camino::Utf8PathBuf; -use config::{Config, ConfigError, File, FileFormat}; +use figment::providers::{Env, Format, Serialized, Toml}; +use figment::Figment; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; use tokio::sync::RwLock; -use torrust_index_located_error::{Located, LocatedError}; +use torrust_index_located_error::LocatedError; use url::Url; +use crate::web::api::server::DynError; + pub type Settings = v1::Settings; pub type Api = v1::api::Api; pub type Auth = v1::auth::Auth; @@ -26,10 +29,16 @@ pub type Tracker = v1::tracker::Tracker; pub type Website = v1::website::Website; pub type EmailOnSignup = v1::auth::EmailOnSignup; +/// Prefix for env vars that overwrite configuration options. +const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; +/// Path separator in env var names for nested values in configuration. +const CONFIG_OVERRIDE_SEPARATOR: &str = "__"; + /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { - index_toml: String, + config_toml: Option, + config_toml_path: String, tracker_api_token: Option, auth_secret_key: Option, } @@ -51,40 +60,33 @@ impl Info { /// #[allow(clippy::needless_pass_by_value)] pub fn new( - env_var_config: String, - env_var_path_config: String, - default_path_config: String, + env_var_config_toml: String, + env_var_config_toml_path: String, + default_config_toml_path: String, env_var_tracker_api_token: String, env_var_auth_secret_key: String, ) -> Result { - let index_toml = if let Ok(index_toml) = env::var(&env_var_config) { - println!("Loading configuration from env var {env_var_config} ..."); - - index_toml + let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) { + println!("Loading configuration from environment variable {config_toml} ..."); + Some(config_toml) } else { - let config_path = if let Ok(config_path) = env::var(env_var_path_config) { - println!("Loading configuration file: `{config_path}` ..."); - - config_path - } else { - println!("Loading default configuration file: `{default_path_config}` ..."); - - default_path_config - }; + None + }; - fs::read_to_string(config_path) - .map_err(|e| Error::UnableToLoadFromConfigFile { - source: (Arc::new(e) as Arc).into(), - })? - .parse() - .map_err(|_e: std::convert::Infallible| Error::Infallible)? + let config_toml_path = if let Ok(config_toml_path) = env::var(env_var_config_toml_path) { + println!("Loading configuration from file: `{config_toml_path}` ..."); + config_toml_path + } else { + println!("Loading configuration from default configuration file: `{default_config_toml_path}` ..."); + default_config_toml_path }; let tracker_api_token = env::var(env_var_tracker_api_token).ok(); let auth_secret_key = env::var(env_var_auth_secret_key).ok(); Ok(Self { - index_toml, + config_toml, + config_toml_path, tracker_api_token, auth_secret_key, }) @@ -96,7 +98,7 @@ impl Info { pub enum Error { /// Unable to load the configuration from the environment variable. /// This error only occurs if there is no configuration file and the - /// `TORRUST_TRACKER_CONFIG_TOML` environment variable is not set. + /// `TORRUST_INDEX_CONFIG_TOML` environment variable is not set. #[error("Unable to load from Environmental Variable: {source}")] UnableToLoadFromEnvironmentVariable { source: LocatedError<'static, dyn std::error::Error + Send + Sync>, @@ -109,12 +111,23 @@ pub enum Error { /// Unable to load the configuration from the configuration file. #[error("Failed processing the configuration: {source}")] - ConfigError { source: LocatedError<'static, ConfigError> }, + ConfigError { + source: LocatedError<'static, dyn std::error::Error + Send + Sync>, + }, #[error("The error for errors that can never happen.")] Infallible, } +impl From for Error { + #[track_caller] + fn from(err: figment::Error) -> Self { + Self::ConfigError { + source: (Arc::new(err) as DynError).into(), + } + } +} + /* todo: Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. @@ -218,7 +231,20 @@ impl Default for Configuration { } impl Configuration { - /// Loads the configuration from the `Info` struct. The whole + /// Loads the configuration from the `Info` struct. + /// + /// # Errors + /// + /// Will return `Err` if the environment variable does not exist or has a bad configuration. + pub fn load(info: &Info) -> Result { + let settings = Self::load_settings(info)?; + + Ok(Configuration { + settings: RwLock::new(settings), + }) + } + + /// Loads the settings from the `Info` struct. The whole /// configuration in toml format is included in the `info.index_toml` string. /// /// Optionally will override the: @@ -229,23 +255,33 @@ impl Configuration { /// # Errors /// /// Will return `Err` if the environment variable does not exist or has a bad configuration. - pub fn load(info: &Info) -> Result { - let config_builder = Config::builder() - .add_source(File::from_str(&info.index_toml, FileFormat::Toml)) - .build()?; - let mut settings: Settings = config_builder.try_deserialize()?; + pub fn load_settings(info: &Info) -> Result { + let figment = if let Some(config_toml) = &info.config_toml { + // Config in env var has priority over config file path + Figment::from(Serialized::defaults(Settings::default())) + .merge(Toml::string(config_toml)) + .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) + } else { + Figment::from(Serialized::defaults(Settings::default())) + .merge(Toml::file(&info.config_toml_path)) + .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) + }; + + //println!("figment: {figment:#?}"); + + let mut settings: Settings = figment.extract()?; if let Some(ref token) = info.tracker_api_token { + // todo: remove when using only Figment env var name: `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` settings.override_tracker_api_token(token); }; if let Some(ref secret_key) = info.auth_secret_key { + // todo: remove when using only Figment env var name: `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` settings.override_auth_secret_key(secret_key); }; - Ok(Configuration { - settings: RwLock::new(settings), - }) + Ok(settings) } pub async fn get_all(&self) -> Settings { @@ -278,15 +314,6 @@ impl Configuration { } } -impl From for Error { - #[track_caller] - fn from(err: ConfigError) -> Self { - Self::ConfigError { - source: Located(err).into(), - } - } -} - /// The public index configuration. /// There is an endpoint to get this configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -304,7 +331,7 @@ fn parse_url(url_str: &str) -> Result { #[cfg(test)] mod tests { - use crate::config::{Configuration, ConfigurationPublic, Info}; + use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; #[cfg(test)] fn default_config_toml() -> String { @@ -415,21 +442,30 @@ mod tests { #[tokio::test] async fn configuration_could_be_loaded_from_a_toml_string() { - let info = Info { - index_toml: default_config_toml(), - tracker_api_token: None, - auth_secret_key: None, - }; + figment::Jail::expect_with(|jail| { + jail.create_dir("templates")?; + jail.create_file("templates/verify.html", "EMAIL TEMPLATE")?; + + let info = Info { + config_toml: Some(default_config_toml()), + config_toml_path: String::new(), + tracker_api_token: None, + auth_secret_key: None, + }; - let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); + let settings = Configuration::load_settings(&info).expect("Failed to load configuration from info"); - assert_eq!(configuration.get_all().await, Configuration::default().get_all().await); + assert_eq!(settings, Settings::default()); + + Ok(()) + }); } #[tokio::test] - async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() { + async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file_deprecated() { let info = Info { - index_toml: default_config_toml(), + config_toml: Some(default_config_toml()), + config_toml_path: String::new(), tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()), auth_secret_key: None, }; @@ -443,9 +479,33 @@ mod tests { } #[tokio::test] - async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() { + async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() { + figment::Jail::expect_with(|jail| { + jail.create_dir("templates")?; + jail.create_file("templates/verify.html", "EMAIL TEMPLATE")?; + + jail.set_env("TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN", "OVERRIDDEN API TOKEN"); + + let info = Info { + config_toml: Some(default_config_toml()), + config_toml_path: String::new(), + tracker_api_token: None, + auth_secret_key: None, + }; + + let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); + + assert_eq!(settings.tracker.token, "OVERRIDDEN API TOKEN".to_string()); + + Ok(()) + }); + } + + #[tokio::test] + async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file_deprecated() { let info = Info { - index_toml: default_config_toml(), + config_toml: Some(default_config_toml()), + config_toml_path: String::new(), tracker_api_token: None, auth_secret_key: Some("OVERRIDDEN AUTH SECRET KEY".to_string()), }; @@ -458,6 +518,29 @@ mod tests { ); } + #[tokio::test] + async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() { + figment::Jail::expect_with(|jail| { + jail.create_dir("templates")?; + jail.create_file("templates/verify.html", "EMAIL TEMPLATE")?; + + jail.set_env("TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY", "OVERRIDDEN AUTH SECRET KEY"); + + let info = Info { + config_toml: Some(default_config_toml()), + config_toml_path: String::new(), + tracker_api_token: None, + auth_secret_key: None, + }; + + let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); + + assert_eq!(settings.auth.secret_key, "OVERRIDDEN AUTH SECRET KEY".to_string()); + + Ok(()) + }); + } + mod syntax_checks { // todo: use rich types in configuration structs for basic syntax checks. diff --git a/src/utils/parse_torrent.rs b/src/utils/parse_torrent.rs index dd2ffd7e..83bfbf8e 100644 --- a/src/utils/parse_torrent.rs +++ b/src/utils/parse_torrent.rs @@ -115,32 +115,36 @@ pub fn calculate_info_hash(bytes: &[u8]) -> Result Date: Mon, 20 May 2024 18:22:14 +0100 Subject: [PATCH 171/309] chore(deps): [#581] remove unused dep config It was replaced with Figment. --- Cargo.lock | 146 +---------------------------------------------------- Cargo.toml | 1 - 2 files changed, 1 insertion(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9066aed3..af1fb606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,67 +534,18 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "config" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" -dependencies = [ - "async-trait", - "convert_case 0.6.0", - "json5", - "lazy_static", - "nom", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml", - "yaml-rust", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", -] - [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -678,12 +629,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -776,7 +721,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", + "convert_case", "proc-macro2", "quote", "rustc_version", @@ -795,15 +740,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -1172,12 +1108,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1499,17 +1429,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -1598,12 +1517,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1883,16 +1796,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-multimap" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" -dependencies = [ - "dlv-list", - "hashbrown 0.13.2", -] - [[package]] name = "parking_lot" version = "0.12.2" @@ -1933,12 +1836,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "pbkdf2" version = "0.12.2" @@ -2345,18 +2242,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.5.0", - "serde", - "serde_derive", -] - [[package]] name = "roxmltree" version = "0.14.1" @@ -2386,16 +2271,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rust-ini" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3250,15 +3125,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tiny-skia" version = "0.6.6" @@ -3420,7 +3286,6 @@ dependencies = [ "camino", "chrono", "clap", - "config", "derive_more", "email_address", "fern", @@ -4173,15 +4038,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 97e20351..8abd1d9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ bytes = "1" camino = { version = "1.1.6", features = ["serde"] } chrono = { version = "0", default-features = false, features = ["clock"] } clap = { version = "4.5.4", features = ["derive", "env"] } -config = "0" derive_more = "0" email_address = "0" fern = "0" From 757751fc90772289f20d2625213f6739c888467a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 21 May 2024 08:17:49 +0100 Subject: [PATCH 172/309] fix: [#581] broken tests using relative paths for fixtures after introducing figment::Jail for testing. It seems Jail changes the current dir and that make other tests fails if they use relative path to load files (in this case fixtures). A solution like useing the env var CARGO_MANIFEST_DIR does not work with `cargo next` when building the container. It only worked when you run the tests with cargo test. --- src/utils/parse_torrent.rs | 142 ++++++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 17 deletions(-) diff --git a/src/utils/parse_torrent.rs b/src/utils/parse_torrent.rs index 83bfbf8e..dffeac27 100644 --- a/src/utils/parse_torrent.rs +++ b/src/utils/parse_torrent.rs @@ -115,21 +115,136 @@ pub fn calculate_info_hash(bytes: &[u8]) -> Result Vec { + /* code-review: + + This is the contents of the torrent file in fixtures dir. After adding + some tests using the following: + + `figment::Jail::expect_with(|jail| { ... });`` + + some tests using relative paths for fixtures failed. It seems + Figment::Jail changes the current dir. In the drop function it restores + it but that could cause a problem when test are running in parallel. + + For the time being, I have replaced loading the torrent file with + this function. This should make the test faster. + + I don't know why this happens only with these two tests. + + */ + + // cspell:disable-next-line + // "tests/fixtures/torrents/6c690018c5786dbbb00161f62b0712d69296df97_with_custom_info_dict_key.torrent" + + [ + 100, 56, 58, 97, 110, 110, 111, 117, 110, 99, 101, 52, 49, 58, 104, 116, 116, 112, 115, 58, 47, 47, 97, 99, 97, 100, + 101, 109, 105, 99, 116, 111, 114, 114, 101, 110, 116, 115, 46, 99, 111, 109, 47, 97, 110, 110, 111, 117, 110, 99, + 101, 46, 112, 104, 112, 49, 51, 58, 97, 110, 110, 111, 117, 110, 99, 101, 45, 108, 105, 115, 116, 108, 108, 52, 49, + 58, 104, 116, 116, 112, 115, 58, 47, 47, 97, 99, 97, 100, 101, 109, 105, 99, 116, 111, 114, 114, 101, 110, 116, 115, + 46, 99, 111, 109, 47, 97, 110, 110, 111, 117, 110, 99, 101, 46, 112, 104, 112, 101, 108, 52, 54, 58, 104, 116, 116, + 112, 115, 58, 47, 47, 105, 112, 118, 54, 46, 97, 99, 97, 100, 101, 109, 105, 99, 116, 111, 114, 114, 101, 110, 116, + 115, 46, 99, 111, 109, 47, 97, 110, 110, 111, 117, 110, 99, 101, 46, 112, 104, 112, 101, 108, 52, 50, 58, 117, 100, + 112, 58, 47, 47, 116, 114, 97, 99, 107, 101, 114, 46, 111, 112, 101, 110, 116, 114, 97, 99, 107, 114, 46, 111, 114, + 103, 58, 49, 51, 51, 55, 47, 97, 110, 110, 111, 117, 110, 99, 101, 101, 108, 52, 52, 58, 117, 100, 112, 58, 47, 47, + 116, 114, 97, 99, 107, 101, 114, 46, 111, 112, 101, 110, 98, 105, 116, 116, 111, 114, 114, 101, 110, 116, 46, 99, + 111, 109, 58, 56, 48, 47, 97, 110, 110, 111, 117, 110, 99, 101, 101, 108, 51, 54, 58, 104, 116, 116, 112, 58, 47, 47, + 98, 116, 49, 46, 97, 114, 99, 104, 105, 118, 101, 46, 111, 114, 103, 58, 54, 57, 54, 57, 47, 97, 110, 110, 111, 117, + 110, 99, 101, 101, 108, 51, 54, 58, 104, 116, 116, 112, 58, 47, 47, 98, 116, 50, 46, 97, 114, 99, 104, 105, 118, 101, + 46, 111, 114, 103, 58, 54, 57, 54, 57, 47, 97, 110, 110, 111, 117, 110, 99, 101, 101, 101, 55, 58, 99, 111, 109, 109, + 101, 110, 116, 54, 51, 52, 58, 84, 104, 105, 115, 32, 99, 111, 110, 116, 101, 110, 116, 32, 104, 111, 115, 116, 101, + 100, 32, 97, 116, 32, 116, 104, 101, 32, 73, 110, 116, 101, 114, 110, 101, 116, 32, 65, 114, 99, 104, 105, 118, 101, + 32, 97, 116, 32, 104, 116, 116, 112, 115, 58, 47, 47, 97, 114, 99, 104, 105, 118, 101, 46, 111, 114, 103, 47, 100, + 101, 116, 97, 105, 108, 115, 47, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, 46, 116, + 97, 114, 10, 70, 105, 108, 101, 115, 32, 109, 97, 121, 32, 104, 97, 118, 101, 32, 99, 104, 97, 110, 103, 101, 100, + 44, 32, 119, 104, 105, 99, 104, 32, 112, 114, 101, 118, 101, 110, 116, 115, 32, 116, 111, 114, 114, 101, 110, 116, + 115, 32, 102, 114, 111, 109, 32, 100, 111, 119, 110, 108, 111, 97, 100, 105, 110, 103, 32, 99, 111, 114, 114, 101, + 99, 116, 108, 121, 32, 111, 114, 32, 99, 111, 109, 112, 108, 101, 116, 101, 108, 121, 59, 32, 112, 108, 101, 97, 115, + 101, 32, 99, 104, 101, 99, 107, 32, 102, 111, 114, 32, 97, 110, 32, 117, 112, 100, 97, 116, 101, 100, 32, 116, 111, + 114, 114, 101, 110, 116, 32, 97, 116, 32, 104, 116, 116, 112, 115, 58, 47, 47, 97, 114, 99, 104, 105, 118, 101, 46, + 111, 114, 103, 47, 100, 111, 119, 110, 108, 111, 97, 100, 47, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, + 103, 104, 116, 115, 46, 116, 97, 114, 47, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, + 46, 116, 97, 114, 95, 97, 114, 99, 104, 105, 118, 101, 46, 116, 111, 114, 114, 101, 110, 116, 10, 78, 111, 116, 101, + 58, 32, 114, 101, 116, 114, 105, 101, 118, 97, 108, 32, 117, 115, 117, 97, 108, 108, 121, 32, 114, 101, 113, 117, + 105, 114, 101, 115, 32, 97, 32, 99, 108, 105, 101, 110, 116, 32, 116, 104, 97, 116, 32, 115, 117, 112, 112, 111, 114, + 116, 115, 32, 119, 101, 98, 115, 101, 101, 100, 105, 110, 103, 32, 40, 71, 101, 116, 82, 105, 103, 104, 116, 32, 115, + 116, 121, 108, 101, 41, 46, 10, 78, 111, 116, 101, 58, 32, 109, 97, 110, 121, 32, 73, 110, 116, 101, 114, 110, 101, + 116, 32, 65, 114, 99, 104, 105, 118, 101, 32, 116, 111, 114, 114, 101, 110, 116, 115, 32, 99, 111, 110, 116, 97, 105, + 110, 32, 97, 32, 39, 112, 97, 100, 32, 102, 105, 108, 101, 39, 32, 100, 105, 114, 101, 99, 116, 111, 114, 121, 46, + 32, 84, 104, 105, 115, 32, 100, 105, 114, 101, 99, 116, 111, 114, 121, 32, 97, 110, 100, 32, 116, 104, 101, 32, 102, + 105, 108, 101, 115, 32, 119, 105, 116, 104, 105, 110, 32, 105, 116, 32, 109, 97, 121, 32, 98, 101, 32, 101, 114, 97, + 115, 101, 100, 32, 111, 110, 99, 101, 32, 114, 101, 116, 114, 105, 101, 118, 97, 108, 32, 99, 111, 109, 112, 108, + 101, 116, 101, 115, 46, 10, 78, 111, 116, 101, 58, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 114, 97, 112, 112, + 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, 46, 116, 97, 114, 95, 109, 101, 116, 97, 46, 120, 109, 108, 32, + 99, 111, 110, 116, 97, 105, 110, 115, 32, 109, 101, 116, 97, 100, 97, 116, 97, 32, 97, 98, 111, 117, 116, 32, 116, + 104, 105, 115, 32, 116, 111, 114, 114, 101, 110, 116, 39, 115, 32, 99, 111, 110, 116, 101, 110, 116, 115, 46, 49, 48, + 58, 99, 114, 101, 97, 116, 101, 100, 32, 98, 121, 49, 53, 58, 105, 97, 95, 109, 97, 107, 101, 95, 116, 111, 114, 114, + 101, 110, 116, 49, 51, 58, 99, 114, 101, 97, 116, 105, 111, 110, 32, 100, 97, 116, 101, 105, 49, 54, 56, 57, 50, 55, + 51, 55, 56, 55, 101, 52, 58, 105, 110, 102, 111, 100, 49, 49, 58, 99, 111, 108, 108, 101, 99, 116, 105, 111, 110, + 115, 108, 51, 49, 58, 111, 114, 103, 46, 97, 114, 99, 104, 105, 118, 101, 46, 114, 97, 112, 112, 112, 105, 100, 45, + 119, 101, 105, 103, 104, 116, 115, 46, 116, 97, 114, 101, 53, 58, 102, 105, 108, 101, 115, 108, 100, 53, 58, 99, 114, + 99, 51, 50, 56, 58, 53, 55, 100, 51, 51, 102, 99, 99, 54, 58, 108, 101, 110, 103, 116, 104, 105, 49, 49, 53, 50, 56, + 51, 50, 52, 101, 51, 58, 109, 100, 53, 51, 50, 58, 101, 57, 49, 98, 98, 52, 98, 97, 56, 50, 54, 57, 53, 49, 54, 49, + 98, 101, 54, 56, 102, 56, 98, 51, 51, 97, 101, 55, 54, 49, 52, 50, 53, 58, 109, 116, 105, 109, 101, 49, 48, 58, 49, + 54, 56, 57, 50, 55, 51, 55, 51, 48, 52, 58, 112, 97, 116, 104, 108, 50, 50, 58, 82, 65, 80, 80, 80, 73, 68, 32, 87, + 101, 105, 103, 104, 116, 115, 46, 116, 97, 114, 46, 103, 122, 101, 52, 58, 115, 104, 97, 49, 52, 48, 58, 52, 53, 57, + 55, 48, 101, 102, 51, 51, 99, 98, 51, 48, 52, 57, 97, 55, 97, 56, 54, 50, 57, 101, 52, 48, 99, 56, 102, 53, 101, 53, + 50, 54, 56, 100, 49, 100, 99, 53, 51, 101, 100, 53, 58, 99, 114, 99, 51, 50, 56, 58, 99, 54, 53, 56, 102, 100, 52, + 102, 54, 58, 108, 101, 110, 103, 116, 104, 105, 50, 48, 52, 56, 48, 101, 51, 58, 109, 100, 53, 51, 50, 58, 97, 55, + 56, 50, 98, 50, 97, 53, 51, 98, 97, 52, 57, 102, 48, 100, 52, 53, 102, 51, 100, 100, 54, 101, 51, 53, 101, 48, 100, + 53, 57, 51, 53, 58, 109, 116, 105, 109, 101, 49, 48, 58, 49, 54, 56, 57, 50, 55, 51, 55, 56, 51, 52, 58, 112, 97, + 116, 104, 108, 51, 49, 58, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, 46, 116, 97, 114, + 95, 109, 101, 116, 97, 46, 115, 113, 108, 105, 116, 101, 101, 52, 58, 115, 104, 97, 49, 52, 48, 58, 98, 99, 98, 48, + 54, 98, 51, 49, 54, 52, 102, 49, 100, 50, 97, 98, 97, 50, 50, 101, 102, 54, 48, 52, 54, 101, 98, 56, 48, 102, 54, 53, + 50, 54, 52, 101, 57, 102, 98, 97, 101, 100, 53, 58, 99, 114, 99, 51, 50, 56, 58, 56, 49, 52, 48, 97, 53, 99, 55, 54, + 58, 108, 101, 110, 103, 116, 104, 105, 49, 48, 52, 52, 101, 51, 58, 109, 100, 53, 51, 50, 58, 49, 98, 97, 98, 50, 49, + 101, 53, 48, 101, 48, 54, 97, 98, 52, 50, 100, 51, 97, 55, 55, 100, 56, 55, 50, 98, 102, 50, 53, 50, 101, 53, 53, 58, + 109, 116, 105, 109, 101, 49, 48, 58, 49, 54, 56, 57, 50, 55, 51, 55, 54, 51, 52, 58, 112, 97, 116, 104, 108, 50, 56, + 58, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, 46, 116, 97, 114, 95, 109, 101, 116, 97, + 46, 120, 109, 108, 101, 52, 58, 115, 104, 97, 49, 52, 48, 58, 98, 50, 102, 48, 102, 50, 98, 98, 101, 99, 51, 52, 97, + 97, 57, 49, 52, 48, 102, 98, 57, 97, 99, 51, 102, 99, 98, 49, 57, 48, 53, 56, 56, 97, 52, 57, 54, 97, 97, 51, 101, + 101, 52, 58, 110, 97, 109, 101, 49, 57, 58, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, 104, 116, 115, + 46, 116, 97, 114, 49, 50, 58, 112, 105, 101, 99, 101, 32, 108, 101, 110, 103, 116, 104, 105, 53, 50, 52, 50, 56, 56, + 101, 54, 58, 112, 105, 101, 99, 101, 115, 52, 54, 48, 58, 171, 236, 85, 110, 15, 123, 231, 211, 48, 12, 246, 104, + 140, 144, 109, 153, 12, 62, 50, 181, 44, 242, 182, 124, 12, 50, 82, 188, 114, 111, 7, 30, 115, 171, 118, 241, 188, + 50, 43, 252, 33, 212, 127, 26, 233, 114, 53, 64, 126, 195, 180, 137, 9, 43, 237, 75, 216, 176, 108, 101, 140, 39, 88, + 174, 251, 114, 117, 115, 68, 55, 136, 40, 32, 210, 148, 189, 164, 106, 248, 210, 166, 253, 2, 101, 28, 28, 223, 184, + 86, 109, 58, 210, 126, 167, 61, 202, 226, 73, 247, 54, 141, 23, 119, 110, 50, 173, 239, 165, 68, 194, 143, 182, 156, + 36, 86, 173, 232, 251, 123, 166, 113, 192, 129, 229, 67, 3, 145, 212, 79, 176, 166, 100, 202, 41, 27, 13, 29, 64, + 125, 57, 78, 118, 150, 235, 1, 24, 243, 245, 80, 142, 47, 250, 84, 252, 73, 102, 133, 216, 56, 135, 120, 155, 10, + 143, 122, 163, 44, 143, 114, 54, 173, 109, 116, 11, 252, 197, 87, 113, 134, 251, 243, 207, 202, 201, 218, 236, 97, + 98, 162, 42, 27, 167, 133, 137, 145, 143, 170, 192, 192, 203, 13, 87, 216, 183, 231, 100, 77, 242, 132, 115, 118, + 152, 251, 58, 23, 72, 215, 156, 1, 254, 202, 109, 31, 197, 151, 52, 5, 84, 57, 218, 194, 110, 23, 65, 17, 105, 243, + 70, 209, 125, 22, 211, 192, 135, 59, 195, 178, 12, 29, 224, 226, 73, 195, 5, 210, 76, 0, 90, 91, 120, 1, 18, 62, 191, + 82, 67, 73, 109, 26, 238, 35, 121, 210, 14, 40, 182, 132, 126, 197, 237, 121, 222, 100, 2, 237, 71, 113, 61, 147, 22, + 196, 162, 118, 24, 119, 84, 197, 49, 72, 150, 58, 81, 193, 74, 146, 144, 145, 243, 207, 72, 91, 36, 134, 85, 168, + 235, 12, 198, 45, 134, 226, 41, 86, 9, 44, 56, 11, 205, 193, 202, 69, 230, 100, 106, 71, 254, 187, 46, 71, 154, 119, + 69, 41, 233, 114, 25, 32, 111, 66, 121, 43, 55, 185, 83, 37, 237, 15, 41, 4, 213, 226, 150, 241, 222, 207, 153, 190, + 50, 170, 184, 10, 29, 11, 159, 185, 214, 171, 92, 80, 67, 120, 133, 65, 9, 1, 36, 207, 224, 137, 118, 91, 77, 169, + 202, 114, 192, 223, 146, 71, 15, 13, 206, 202, 150, 198, 126, 165, 65, 95, 43, 167, 187, 4, 204, 247, 68, 127, 148, + 30, 36, 210, 27, 23, 202, 24, 121, 144, 163, 214, 32, 117, 162, 150, 104, 6, 88, 90, 222, 245, 44, 26, 144, 34, 114, + 51, 142, 213, 178, 168, 250, 229, 233, 231, 105, 98, 2, 124, 9, 179, 76, 101, 54, 58, 108, 111, 99, 97, 108, 101, 50, + 58, 101, 110, 53, 58, 116, 105, 116, 108, 101, 49, 57, 58, 114, 97, 112, 112, 112, 105, 100, 45, 119, 101, 105, 103, + 104, 116, 115, 46, 116, 97, 114, 56, 58, 117, 114, 108, 45, 108, 105, 115, 116, 108, 50, 57, 58, 104, 116, 116, 112, + 115, 58, 47, 47, 97, 114, 99, 104, 105, 118, 101, 46, 111, 114, 103, 47, 100, 111, 119, 110, 108, 111, 97, 100, 47, + 52, 48, 58, 104, 116, 116, 112, 58, 47, 47, 105, 97, 57, 48, 50, 55, 48, 50, 46, 117, 115, 46, 97, 114, 99, 104, 105, + 118, 101, 46, 111, 114, 103, 47, 50, 50, 47, 105, 116, 101, 109, 115, 47, 52, 48, 58, 104, 116, 116, 112, 58, 47, 47, + 105, 97, 56, 48, 50, 55, 48, 50, 46, 117, 115, 46, 97, 114, 99, 104, 105, 118, 101, 46, 111, 114, 103, 47, 50, 50, + 47, 105, 116, 101, 109, 115, 47, 101, 101, + ] + .to_vec() + } + #[test] fn it_should_calculate_the_original_info_hash_using_all_fields_in_the_info_key_dictionary() { - let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let torrent_relative_path = Path::new( - // cspell:disable-next-line - "tests/fixtures/torrents/6c690018c5786dbbb00161f62b0712d69296df97_with_custom_info_dict_key.torrent", - ); - let torrent_path = root_dir.join(torrent_relative_path); - - let original_info_hash = super::calculate_info_hash(&std::fs::read(torrent_path).unwrap()).unwrap(); + let original_info_hash = super::calculate_info_hash(&torrent_with_custom_info_dict_key()).unwrap(); assert_eq!( original_info_hash, @@ -139,14 +254,7 @@ mod tests { #[test] fn it_should_calculate_the_new_info_hash_ignoring_non_standard_fields_in_the_info_key_dictionary() { - let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let torrent_relative_path = Path::new( - // cspell:disable-next-line - "tests/fixtures/torrents/6c690018c5786dbbb00161f62b0712d69296df97_with_custom_info_dict_key.torrent", - ); - let torrent_path = root_dir.join(torrent_relative_path); - - let torrent = super::decode_torrent(&std::fs::read(torrent_path).unwrap()).unwrap(); + let torrent = super::decode_torrent(&torrent_with_custom_info_dict_key()).unwrap(); // The infohash is not the original infohash of the torrent file, // but the infohash of the info dictionary without the custom keys. From 66de69576dc0e240e087c940e171e7acc7ce861c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 21 May 2024 09:17:43 +0100 Subject: [PATCH 173/309] test: show error when test env is not running --- tests/common/client.rs | 9 ++++++--- tests/environments/shared.rs | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/common/client.rs b/tests/common/client.rs index 1e938d1d..9312ac6f 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use reqwest::multipart; +use reqwest::{multipart, Error}; use serde::Serialize; use super::connection_info::ConnectionInfo; @@ -39,9 +39,12 @@ impl Client { } /// It checks if the server is running. - pub async fn server_is_running(&self) -> bool { + pub async fn server_is_running(&self) -> Result<(), Error> { let response = self.http_client.inner_get("").await; - response.is_ok() + match response { + Ok(_) => Ok(()), + Err(err) => Err(err), + } } // Context: about diff --git a/tests/environments/shared.rs b/tests/environments/shared.rs index d9db57be..30ad1193 100644 --- a/tests/environments/shared.rs +++ b/tests/environments/shared.rs @@ -16,8 +16,10 @@ impl TestEnv { pub async fn running() -> Self { let env = Self::default(); let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - let is_running = client.server_is_running().await; - assert!(is_running, "Test server is not running on {}", env.authority); + match client.server_is_running().await { + Ok(()) => {} + Err(err) => panic!("Test server is not running on {}. Error: {err}", env.authority), + } env } From 7a05b8d9da18f53d3bd6ab9a7946b013450ddf73 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 21 May 2024 16:12:02 +0100 Subject: [PATCH 174/309] chore(deps): update dependencies ```ouput cargo update Updating crates.io index Locking 23 packages to latest compatible versions Updating anyhow v1.0.83 -> v1.0.86 Adding atomic-waker v1.1.2 Updating cc v1.0.97 -> v1.0.98 Updating crc32fast v1.4.0 -> v1.4.2 Updating crossbeam-utils v0.8.19 -> v0.8.20 Updating either v1.11.0 -> v1.12.0 Updating h2 v0.4.4 -> v0.4.5 Updating libc v0.2.154 -> v0.2.155 Updating linux-raw-sys v0.4.13 -> v0.4.14 (latest: v0.6.4) Updating miniz_oxide v0.7.2 -> v0.7.3 Updating proc-macro2 v1.0.82 -> v1.0.83 Updating rustls v0.23.5 -> v0.23.7 Updating rustls-webpki v0.102.3 -> v0.102.4 Updating rustversion v1.0.16 -> v1.0.17 Updating serde v1.0.201 -> v1.0.202 Updating serde_derive v1.0.201 -> v1.0.202 Updating serde_spanned v0.6.5 -> v0.6.6 Updating syn v2.0.63 -> v2.0.65 Updating thiserror v1.0.60 -> v1.0.61 Updating thiserror-impl v1.0.60 -> v1.0.61 Updating toml v0.8.12 -> v0.8.13 Updating toml_datetime v0.6.5 -> v0.6.6 Updating toml_edit v0.22.12 -> v0.22.13 ``` --- Cargo.lock | 138 ++++++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af1fb606..041568dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arc-swap" @@ -190,7 +190,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -211,6 +211,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -440,9 +446,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", @@ -509,7 +515,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -588,9 +594,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -625,9 +631,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -660,7 +666,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -671,7 +677,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -748,9 +754,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" dependencies = [ "serde", ] @@ -997,7 +1003,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -1085,15 +1091,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap 2.2.6", "slab", @@ -1484,7 +1490,7 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.5", + "rustls 0.23.7", "rustls-pemfile", "socket2", "tokio", @@ -1496,9 +1502,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -1519,9 +1525,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1600,9 +1606,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", "simd-adler32", @@ -1775,7 +1781,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -1868,7 +1874,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -1927,7 +1933,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -1964,7 +1970,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2033,9 +2039,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -2048,7 +2054,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "version_check", "yansi", ] @@ -2313,15 +2319,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.5" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.3", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -2354,9 +2360,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -2365,9 +2371,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustybuzz" @@ -2465,9 +2471,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] @@ -2493,13 +2499,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2525,9 +2531,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2571,7 +2577,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2977,9 +2983,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -3076,22 +3082,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -3180,7 +3186,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -3209,7 +3215,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.5", + "rustls 0.23.7", "rustls-pki-types", "tokio", ] @@ -3240,9 +3246,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", @@ -3252,18 +3258,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.2.6", "serde", @@ -3424,7 +3430,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -3720,7 +3726,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "wasm-bindgen-shared", ] @@ -3754,7 +3760,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4061,7 +4067,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] From fc1528d39e11ab685a9e11fc5381b5a7f45aad3a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 07:48:15 +0100 Subject: [PATCH 175/309] feat: [#596] rename common E2E env vars We should not use specific names (sufix _E2E) for env vars that are not specidif for E2E tests. These env vars are generic enva vars also used in production. They only have different values for the E2E test env. ``` TORRUST_INDEX_E2E_CONFIG -> TORRUST_INDEX_CONFIG TORRUST_INDEX_E2E_TRACKER_API_TOKEN -> TORRUST_INDEX_TRACKER_API_TOKEN TORRUST_INDEX_E2E_AUTH_SECRET_KEY -> TORRUST_INDEX_AUTH_SECRET_KEY TORRUST_INDEX_E2E_PATH_CONFIG -> TORRUST_INDEX_PATH_CONFIG ``` --- contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh | 2 +- .../dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 2 +- tests/e2e/config.rs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index cbbc0530..395c0df2 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -40,7 +40,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ + TORRUST_INDEX_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ cargo test || { diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 5842723c..a4d6583c 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -38,7 +38,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_E2E_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ + TORRUST_INDEX_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ cargo test || { diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index a1f0a721..8e88a3d1 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -9,16 +9,16 @@ use torrust_index::config::{Configuration, Info}; /// The whole `index.toml` file content. It has priority over the config file. /// Even if the file is not on the default path. -const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_E2E_CONFIG"; +const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; /// Token needed to communicate with the Torrust Tracker -const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_E2E_TRACKER_API_TOKEN"; +const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; /// Secret key used to encrypt and decrypt -const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_E2E_AUTH_SECRET_KEY"; +const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; /// The `index.toml` file location. -pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_E2E_PATH_CONFIG"; +pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; // Default values pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/index.development.sqlite3.toml"; @@ -34,7 +34,7 @@ pub const ENV_VAR_DB_CONNECT_URL: &str = "TORRUST_INDEX_E2E_DB_CONNECT_URL"; /// There are two methods to inject the configuration: /// /// 1. By using a config file: `index.toml`. -/// 2. Environment variable: `TORRUST_INDEX_E2E_CONFIG`. The variable contains the same contents as the `index.toml` file. +/// 2. Environment variable: `TORRUST_INDEX_CONFIG`. The variable contains the same contents as the `index.toml` file. /// /// Environment variable has priority over the config file. /// From b8d0f9893103edc7d25e60c5ddb3f0dd84f53d1f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 08:10:17 +0100 Subject: [PATCH 176/309] refactor: [#596] merge E2E and prod common env var constants --- src/bootstrap/config.rs | 6 +++--- tests/e2e/config.rs | 14 +------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 782c3d1a..b98bb42e 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -8,13 +8,13 @@ use crate::config::{Configuration, Info}; /// The whole `index.toml` file content. It has priority over the config file. /// Even if the file is not on the default path. -const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; +pub const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; /// Token needed to communicate with the Torrust Tracker -const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; +pub const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; /// Secret key used to encrypt and decrypt -const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; +pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; /// The `index.toml` file location. pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index 8e88a3d1..61a21b4d 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -5,21 +5,9 @@ // Environment variables +use torrust_index::bootstrap::config::{ENV_VAR_API_ADMIN_TOKEN, ENV_VAR_AUTH_SECRET_KEY, ENV_VAR_CONFIG, ENV_VAR_PATH_CONFIG}; use torrust_index::config::{Configuration, Info}; -/// The whole `index.toml` file content. It has priority over the config file. -/// Even if the file is not on the default path. -const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; - -/// Token needed to communicate with the Torrust Tracker -const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; - -/// Secret key used to encrypt and decrypt -const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; - -/// The `index.toml` file location. -pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; - // Default values pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/index.development.sqlite3.toml"; From 5910fda5662e6b3054e6d4be62a8b67a2e11709e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 08:15:54 +0100 Subject: [PATCH 177/309] refactor: [#596] move env var constants to config mod --- src/bootstrap/config.rs | 22 +--------------------- src/config/mod.rs | 37 +++++++++++++++++++++---------------- tests/e2e/config.rs | 10 +--------- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index b98bb42e..c14454ab 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -6,19 +6,6 @@ use crate::config::{Configuration, Info}; -/// The whole `index.toml` file content. It has priority over the config file. -/// Even if the file is not on the default path. -pub const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; - -/// Token needed to communicate with the Torrust Tracker -pub const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; - -/// Secret key used to encrypt and decrypt -pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; - -/// The `index.toml` file location. -pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; - // Default values pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/index.development.sqlite3.toml"; @@ -42,14 +29,7 @@ pub const ENV_VAR_CORS_PERMISSIVE: &str = "TORRUST_INDEX_API_CORS_PERMISSIVE"; /// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG`. #[must_use] pub fn initialize_configuration() -> Configuration { - let info = Info::new( - ENV_VAR_CONFIG.to_string(), - ENV_VAR_PATH_CONFIG.to_string(), - DEFAULT_PATH_CONFIG.to_string(), - ENV_VAR_API_ADMIN_TOKEN.to_string(), - ENV_VAR_AUTH_SECRET_KEY.to_string(), - ) - .unwrap(); + let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).unwrap(); Configuration::load(&info).unwrap() } diff --git a/src/config/mod.rs b/src/config/mod.rs index cf60b902..10648333 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -31,9 +31,23 @@ pub type EmailOnSignup = v1::auth::EmailOnSignup; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; + /// Path separator in env var names for nested values in configuration. const CONFIG_OVERRIDE_SEPARATOR: &str = "__"; +/// The whole `index.toml` file content. It has priority over the config file. +/// Even if the file is not on the default path. +pub const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; + +/// Token needed to communicate with the Torrust Tracker +pub const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; + +/// Secret key used to encrypt and decrypt +pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; + +/// The `index.toml` file location. +pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; + /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { @@ -44,28 +58,19 @@ pub struct Info { } impl Info { - /// Build Configuration Info - /// - /// # Examples - /// - /// ```no_run - /// # use torrust_index::config::Info; - /// # let (env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token, env_var_auth_secret_key) = ("".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()); - /// let result = Info::new(env_var_config, env_var_path_config, default_path_config, env_var_tracker_api_token, env_var_auth_secret_key); - /// ``` + /// Build configuration Info. /// /// # Errors /// /// Will return `Err` if unable to obtain a configuration. /// #[allow(clippy::needless_pass_by_value)] - pub fn new( - env_var_config_toml: String, - env_var_config_toml_path: String, - default_config_toml_path: String, - env_var_tracker_api_token: String, - env_var_auth_secret_key: String, - ) -> Result { + pub fn new(default_config_toml_path: String) -> Result { + let env_var_config_toml = ENV_VAR_CONFIG.to_string(); + let env_var_config_toml_path = ENV_VAR_PATH_CONFIG.to_string(); + let env_var_tracker_api_token = ENV_VAR_API_ADMIN_TOKEN.to_string(); + let env_var_auth_secret_key = ENV_VAR_AUTH_SECRET_KEY.to_string(); + let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) { println!("Loading configuration from environment variable {config_toml} ..."); Some(config_toml) diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index 61a21b4d..09e61003 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -5,7 +5,6 @@ // Environment variables -use torrust_index::bootstrap::config::{ENV_VAR_API_ADMIN_TOKEN, ENV_VAR_AUTH_SECRET_KEY, ENV_VAR_CONFIG, ENV_VAR_PATH_CONFIG}; use torrust_index::config::{Configuration, Info}; // Default values @@ -34,14 +33,7 @@ pub const ENV_VAR_DB_CONNECT_URL: &str = "TORRUST_INDEX_E2E_DB_CONNECT_URL"; /// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG`. #[must_use] pub fn initialize_configuration() -> Configuration { - let info = Info::new( - ENV_VAR_CONFIG.to_string(), - ENV_VAR_PATH_CONFIG.to_string(), - DEFAULT_PATH_CONFIG.to_string(), - ENV_VAR_API_ADMIN_TOKEN.to_string(), - ENV_VAR_AUTH_SECRET_KEY.to_string(), - ) - .unwrap(); + let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).unwrap(); Configuration::load(&info).unwrap() } From 97e36349b1e6ba33e048be651ba583b94b57ff3a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 09:59:23 +0100 Subject: [PATCH 178/309] refactor: [#596] rename env vars ``` TORRUST_INDEX_CONFIG -> TORRUST_INDEX_CONFIG_TOML TORRUST_INDEX_PATH_CONFIG -> TORRUST_INDEX_CONFIG_TOML_PATH TORRUST_INDEX_TRACKER_API_TOKEN -> TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN TORRUST_INDEX_AUTH_SECRET_KEY -> TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY ``` --- .env.local | 4 ++-- Containerfile | 4 ++-- README.md | 10 ++++----- compose.yaml | 6 ++--- .../container/e2e/mysql/e2e-env-down.sh | 2 +- .../container/e2e/mysql/e2e-env-up.sh | 4 ++-- .../container/e2e/mysql/run-e2e-tests.sh | 2 +- .../container/e2e/sqlite/e2e-env-down.sh | 2 +- .../container/e2e/sqlite/e2e-env-up.sh | 8 +++---- .../container/e2e/sqlite/run-e2e-tests.sh | 2 +- contrib/dev-tools/container/run.sh | 4 ++-- docs/containers.md | 14 ++++++------ .../default/config/index.container.mysql.toml | 4 ++-- .../config/index.container.sqlite3.toml | 2 +- .../config/index.e2e.container.mysql.toml | 4 ++-- .../config/index.e2e.container.sqlite3.toml | 4 ++-- src/bootstrap/config.rs | 4 ++-- src/config/mod.rs | 22 ++++++++++--------- src/lib.rs | 6 ++--- tests/e2e/config.rs | 4 ++-- 20 files changed, 57 insertions(+), 55 deletions(-) diff --git a/.env.local b/.env.local index c970e247..3122ef20 100644 --- a/.env.local +++ b/.env.local @@ -1,6 +1,6 @@ DATABASE_URL=sqlite://storage/database/data.db?mode=rwc -TORRUST_INDEX_CONFIG= -TORRUST_INDEX_AUTH_SECRET_KEY=MaxVerstappenWC2021 +TORRUST_INDEX_CONFIG_TOML= +TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY=MaxVerstappenWC2021 USER_ID=1000 TORRUST_TRACKER_CONFIG_TOML= TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER=Sqlite3 diff --git a/Containerfile b/Containerfile index c1b89791..0c09684f 100644 --- a/Containerfile +++ b/Containerfile @@ -97,13 +97,13 @@ FROM gcr.io/distroless/cc-debian12:debug as runtime RUN ["/busybox/cp", "-sp", "/busybox/sh","/busybox/cat","/busybox/ls","/busybox/env", "/bin/"] COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec -ARG TORRUST_INDEX_PATH_CONFIG="/etc/torrust/index/index.toml" +ARG TORRUST_INDEX_CONFIG_TOML_PATH="/etc/torrust/index/index.toml" ARG TORRUST_INDEX_DATABASE_DRIVER="Sqlite3" ARG USER_ID=1000 ARG API_PORT=3001 ARG IMPORTER_API_PORT=3002 -ENV TORRUST_INDEX_PATH_CONFIG=${TORRUST_INDEX_PATH_CONFIG} +ENV TORRUST_INDEX_CONFIG_TOML_PATH=${TORRUST_INDEX_CONFIG_TOML_PATH} ENV TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER} ENV USER_ID=${USER_ID} ENV API_PORT=${API_PORT} diff --git a/README.md b/README.md index 60348b37..7bbfba2d 100644 --- a/README.md +++ b/README.md @@ -83,14 +83,14 @@ cp ./share/default/config/index.development.sqlite3.toml ./storage/index/etc/ind vim ./storage/index/etc/index.toml # Run the index with the updated configuration: -TORRUST_INDEX_PATH_CONFIG="./storage/index/etc/index.toml" cargo run +TORRUST_INDEX_CONFIG_TOML_PATH="./storage/index/etc/index.toml" cargo run ``` _Optionally, you may choose to supply the entire configuration as an environmental variable:_ ```sh # Use a configuration supplied on an environmental variable: -TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") cargo run +TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") cargo run ``` _For deployment, you __should__ override: @@ -100,9 +100,9 @@ _For deployment, you __should__ override: ```sh # Please use the secret that you generated for the torrust-tracker configuration. # Override secret in configuration using an environmental variable -TORRUST_INDEX_CONFIG=$(cat "./storage/index/etc/index.toml") \ - TORRUST_INDEX_TRACKER_API_TOKEN=$(cat "./storage/tracker/lib/tracker_api_admin_token.secret") \ - TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ +TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") \ + TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN=$(cat "./storage/tracker/lib/tracker_api_admin_token.secret") \ + TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ cargo run ``` diff --git a/compose.yaml b/compose.yaml index ecac7447..002053a2 100644 --- a/compose.yaml +++ b/compose.yaml @@ -9,11 +9,11 @@ services: tty: true environment: - USER_ID=${USER_ID} - - TORRUST_INDEX_CONFIG=${TORRUST_INDEX_CONFIG} + - TORRUST_INDEX_CONFIG_TOML=${TORRUST_INDEX_CONFIG_TOML} - TORRUST_INDEX_DATABASE=${TORRUST_INDEX_DATABASE:-e2e_testing_sqlite3} - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - - TORRUST_INDEX_TRACKER_API_TOKEN=${TORRUST_INDEX_TRACKER_API_TOKEN:-MyAccessToken} - - TORRUST_INDEX_AUTH_SECRET_KEY=${TORRUST_INDEX_AUTH_SECRET_KEY:-MaxVerstappenWC2021} + - TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN=${TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN:-MyAccessToken} + - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY=${TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY:-MaxVerstappenWC2021} networks: - server_side ports: diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh index 54123056..8e0c05f4 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index f1d6f82c..a865d4a2 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -4,10 +4,10 @@ TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ + TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ TORRUST_INDEX_DATABASE="torrust_index_e2e_testing" \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ - TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ + TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 395c0df2..79cea17a 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -40,7 +40,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_PATH_CONFIG="./share/default/config/index.e2e.container.mysql.toml" \ + TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ cargo test || { diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh index 3dfc14e3..da45b824 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh index e9a424f5..3f744cb4 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh @@ -1,14 +1,14 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ + TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ - TORRUST_INDEX_TRACKER_API_TOKEN="MyAccessToken" \ - TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ + TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ + TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index a4d6583c..42b2f7d1 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -38,7 +38,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_PATH_CONFIG="./share/default/config/index.e2e.container.sqlite3.toml" \ + TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.e2e.container.sqlite3.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ cargo test || { diff --git a/contrib/dev-tools/container/run.sh b/contrib/dev-tools/container/run.sh index c967f788..64f7c543 100755 --- a/contrib/dev-tools/container/run.sh +++ b/contrib/dev-tools/container/run.sh @@ -1,11 +1,11 @@ #!/bin/bash USER_ID=${USER_ID:-1000} -TORRUST_INDEX_CONFIG=$(cat config.toml) +TORRUST_INDEX_CONFIG_TOML=$(cat config.toml) docker run -it \ --user="$USER_ID" \ --publish 3001:3001/tcp \ - --env TORRUST_INDEX_CONFIG="$TORRUST_INDEX_CONFIG" \ + --env TORRUST_INDEX_CONFIG_TOML="$TORRUST_INDEX_CONFIG_TOML" \ --volume "$(pwd)/storage":"/app/storage" \ torrust-index diff --git a/docs/containers.md b/docs/containers.md index f356d2f4..5039d363 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -147,11 +147,11 @@ Environmental variables are loaded through the `--env`, in the format `--env VAR The following environmental variables can be set: -- `TORRUST_INDEX_PATH_CONFIG` - The in-container path to the index configuration file, (default: `"/etc/torrust/index/index.toml"`). -- `TORRUST_INDEX_TRACKER_API_TOKEN` - Override of the admin token. If set, this value overrides any value set in the config. -- `TORRUST_INDEX_AUTH_SECRET_KEY` - Override of the auth secret key. If set, this value overrides any value set in the config. +- `TORRUST_INDEX_CONFIG_TOML_PATH` - The in-container path to the index configuration file, (default: `"/etc/torrust/index/index.toml"`). +- `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` - Override of the admin token. If set, this value overrides any value set in the config. +- `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - Override of the auth secret key. If set, this value overrides any value set in the config. - `TORRUST_INDEX_DATABASE_DRIVER` - The database type used for the container, (options: `sqlite3`, `mysql`, default `sqlite3`). Please Note: This dose not override the database configuration within the `.toml` config file. -- `TORRUST_INDEX_CONFIG` - Load config from this environmental variable instead from a file, (i.e: `TORRUST_INDEX_CONFIG=$(cat index-index.toml)`). +- `TORRUST_INDEX_CONFIG_TOML` - Load config from this environmental variable instead from a file, (i.e: `TORRUST_INDEX_CONFIG_TOML=$(cat index-index.toml)`). - `USER_ID` - The user id for the runtime crated `torrust` user. Please Note: This user id should match the ownership of the host-mapped volumes, (default `1000`). - `API_PORT` - The port for the index API. This should match the port used in the configuration, (default `3001`). @@ -201,8 +201,8 @@ mkdir -p ./storage/index/lib/ ./storage/index/log/ ./storage/index/etc/ ## Run Torrust Index Container Image docker run -it \ - --env TORRUST_INDEX_TRACKER_API_TOKEN="MySecretToken" \ - --env TORRUST_INDEX_AUTH_SECRET_KEY="MaxVerstappenWC2021" \ + --env TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MySecretToken" \ + --env TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ --env USER_ID="$(id -u)" \ --publish 0.0.0.0:3001:3001/tcp \ --volume ./storage/index/lib:/var/lib/torrust/index:Z \ @@ -222,7 +222,7 @@ mkdir -p ./storage/index/lib/ ./storage/index/log/ ./storage/index/etc/ ## Run Torrust Index Container Image podman run -it \ - --env TORRUST_INDEX_TRACKER_API_TOKEN="MySecretToken" \ + --env TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MySecretToken" \ --env USER_ID="$(id -u)" \ --publish 0.0.0.0:3001:3001/tcp \ --volume ./storage/index/lib:/var/lib/torrust/index:Z \ diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 5da07797..7bd934a4 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,6 +1,6 @@ # Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` -# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` log_level = "info" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index ac1dd71d..77d06a93 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -4,7 +4,7 @@ log_level = "info" name = "Torrust" # Please override the tracker token setting the -# `TORRUST_INDEX_TRACKER_API_TOKEN` +# `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` # environmental variable! [tracker] diff --git a/share/default/config/index.e2e.container.mysql.toml b/share/default/config/index.e2e.container.mysql.toml index b5e85813..beabe832 100644 --- a/share/default/config/index.e2e.container.mysql.toml +++ b/share/default/config/index.e2e.container.mysql.toml @@ -1,6 +1,6 @@ # Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` -# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` log_level = "info" diff --git a/share/default/config/index.e2e.container.sqlite3.toml b/share/default/config/index.e2e.container.sqlite3.toml index 9e220369..e3409ac4 100644 --- a/share/default/config/index.e2e.container.sqlite3.toml +++ b/share/default/config/index.e2e.container.sqlite3.toml @@ -1,6 +1,6 @@ # Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_TRACKER_API_TOKEN` -# auth::secret_key -> `TORRUST_INDEX_AUTH_SECRET_KEY` +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` log_level = "info" diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index c14454ab..aaef11df 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -17,7 +17,7 @@ pub const ENV_VAR_CORS_PERMISSIVE: &str = "TORRUST_INDEX_API_CORS_PERMISSIVE"; /// There are two methods to inject the configuration: /// /// 1. By using a config file: `index.toml`. -/// 2. Environment variable: `TORRUST_INDEX_CONFIG`. The variable contains the same contents as the `index.toml` file. +/// 2. Environment variable: `TORRUST_INDEX_CONFIG_TOML`. The variable contains the same contents as the `index.toml` file. /// /// Environment variable has priority over the config file. /// @@ -26,7 +26,7 @@ pub const ENV_VAR_CORS_PERMISSIVE: &str = "TORRUST_INDEX_API_CORS_PERMISSIVE"; /// # Panics /// /// Will panic if it can't load the configuration from either -/// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG`. +/// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG_TOML`. #[must_use] pub fn initialize_configuration() -> Configuration { let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).unwrap(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 10648333..420813dc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -37,17 +37,19 @@ const CONFIG_OVERRIDE_SEPARATOR: &str = "__"; /// The whole `index.toml` file content. It has priority over the config file. /// Even if the file is not on the default path. -pub const ENV_VAR_CONFIG: &str = "TORRUST_INDEX_CONFIG"; +pub const ENV_VAR_CONFIG_TOML: &str = "TORRUST_INDEX_CONFIG_TOML"; -/// Token needed to communicate with the Torrust Tracker -pub const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; +/// The `index.toml` file location. +pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_INDEX_CONFIG_TOML_PATH"; + +/// Token needed to communicate with the Torrust Tracker. +/// Deprecated. Use `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN`. +pub const ENV_VAR_TRACKER_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; /// Secret key used to encrypt and decrypt +/// Deprecated. Use `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY`. pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; -/// The `index.toml` file location. -pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_INDEX_PATH_CONFIG"; - /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { @@ -66,9 +68,9 @@ impl Info { /// #[allow(clippy::needless_pass_by_value)] pub fn new(default_config_toml_path: String) -> Result { - let env_var_config_toml = ENV_VAR_CONFIG.to_string(); - let env_var_config_toml_path = ENV_VAR_PATH_CONFIG.to_string(); - let env_var_tracker_api_token = ENV_VAR_API_ADMIN_TOKEN.to_string(); + let env_var_config_toml = ENV_VAR_CONFIG_TOML.to_string(); + let env_var_config_toml_path = ENV_VAR_CONFIG_TOML_PATH.to_string(); + let env_var_tracker_api_admin_token = ENV_VAR_TRACKER_API_ADMIN_TOKEN.to_string(); let env_var_auth_secret_key = ENV_VAR_AUTH_SECRET_KEY.to_string(); let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) { @@ -86,7 +88,7 @@ impl Info { default_config_toml_path }; - let tracker_api_token = env::var(env_var_tracker_api_token).ok(); + let tracker_api_token = env::var(env_var_tracker_api_admin_token).ok(); let auth_secret_key = env::var(env_var_auth_secret_key).ok(); Ok(Self { diff --git a/src/lib.rs b/src/lib.rs index 4f155d12..87455dfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,10 +215,10 @@ //! //! For more information about configuration you can visit the documentation for the [`config`]) module. //! -//! Alternatively to the `config.toml` file you can use one environment variable `TORRUST_INDEX_CONFIG` to pass the configuration to the tracker: +//! Alternatively to the `config.toml` file you can use one environment variable `TORRUST_INDEX_CONFIG_TOML` to pass the configuration to the tracker: //! //! ```text -//! TORRUST_INDEX_CONFIG=$(cat config.toml) +//! TORRUST_INDEX_CONFIG_TOML=$(cat config.toml) //! cargo run //! ``` //! @@ -226,7 +226,7 @@ //! //! The env var contains the same data as the `config.toml`. It's particularly useful in you are [running the index with docker](https://github.com/torrust/torrust-index/tree/develop/docker). //! -//! > **NOTICE**: The `TORRUST_INDEX_CONFIG` env var has priority over the `config.toml` file. +//! > **NOTICE**: The `TORRUST_INDEX_CONFIG_TOML` env var has priority over the `config.toml` file. //! //! > **NOTICE**: You can also change the location for the configuration file with the `TORRUST_INDEX_CONFIG_PATH` env var. //! diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index 09e61003..be46b7e2 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -21,7 +21,7 @@ pub const ENV_VAR_DB_CONNECT_URL: &str = "TORRUST_INDEX_E2E_DB_CONNECT_URL"; /// There are two methods to inject the configuration: /// /// 1. By using a config file: `index.toml`. -/// 2. Environment variable: `TORRUST_INDEX_CONFIG`. The variable contains the same contents as the `index.toml` file. +/// 2. Environment variable: `TORRUST_INDEX_CONFIG_TOML`. The variable contains the same contents as the `index.toml` file. /// /// Environment variable has priority over the config file. /// @@ -30,7 +30,7 @@ pub const ENV_VAR_DB_CONNECT_URL: &str = "TORRUST_INDEX_E2E_DB_CONNECT_URL"; /// # Panics /// /// Will panic if it can't load the configuration from either -/// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG`. +/// `./index.toml` file or the env var `TORRUST_INDEX_CONFIG_TOML`. #[must_use] pub fn initialize_configuration() -> Configuration { let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).unwrap(); From e0d56a4d55dcb82adce5cade9294aadb3d50a718 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 23 May 2024 14:02:54 +0100 Subject: [PATCH 179/309] fix: [#601] linting errors --- .../commands/tracker_statistics_importer/app.rs | 6 +++--- src/lib.rs | 6 +++--- src/upgrades/from_v1_0_0_to_v2_0_0/upgrader.rs | 10 +++++----- src/utils/parse_torrent.rs | 4 ++-- tests/e2e/mod.rs | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index 9bcd20b8..d7cc16fc 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -16,11 +16,11 @@ //! Statistics are also imported: //! //! - Periodically by the importer job. The importer job is executed every hour -//! by default. See [`TrackerStatisticsImporter`](crate::config::TrackerStatisticsImporter) -//! for more details. +//! by default. See [`TrackerStatisticsImporter`](crate::config::TrackerStatisticsImporter) +//! for more details. //! - When a new torrent is added. //! - When the API returns data about a torrent statistics are collected from -//! the tracker in real time. +//! the tracker in real time. use std::env; use std::sync::Arc; diff --git a/src/lib.rs b/src/lib.rs index 87455dfe..b386549d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,8 +88,8 @@ //! ``` //! //! > **NOTICE**: those are the commands for `Ubuntu`. If you are using a -//! different OS, you will need to install the equivalent packages. Please -//! refer to the documentation of your OS. +//! > different OS, you will need to install the equivalent packages. Please +//! > refer to the documentation of your OS. //! //! With the default configuration you will need to create the `storage` directory: //! @@ -154,7 +154,7 @@ //! > **WARNING**: The `.env` file is also used by docker-compose. //! //! > **NOTICE**: Refer to the [sqlx-cli](https://github.com/launchbadge/sqlx/tree/main/sqlx-cli) -//! documentation for other commands to create new migrations or run them. +//! > documentation for other commands to create new migrations or run them. //! //! > **NOTICE**: You can run the index with [tmux](https://github.com/tmux/tmux/wiki) with `tmux new -s torrust-index`. //! diff --git a/src/upgrades/from_v1_0_0_to_v2_0_0/upgrader.rs b/src/upgrades/from_v1_0_0_to_v2_0_0/upgrader.rs index 71e58413..51b58637 100644 --- a/src/upgrades/from_v1_0_0_to_v2_0_0/upgrader.rs +++ b/src/upgrades/from_v1_0_0_to_v2_0_0/upgrader.rs @@ -23,8 +23,8 @@ //! //! - The database schema was changed. //! - The torrents are now stored entirely in the database. The torrent files -//! are not stored in the filesystem anymore. This command reads the torrent -//! files from the filesystem and store them in the database. +//! are not stored in the filesystem anymore. This command reads the torrent +//! files from the filesystem and store them in the database. //! //! We recommend to download your production database and the torrent files dir. //! And run the command in a local environment with the version `v2.0.0.`. Then, @@ -36,13 +36,13 @@ //! NOTES for `torrust_users` table transfer: //! //! - In v2, the table `torrust_user` contains a field `date_registered` non -//! existing in v1. We changed that column to allow `NULL`. We also added the -//! new column `date_imported` with the datetime when the upgrader was executed. +//! existing in v1. We changed that column to allow `NULL`. We also added the +//! new column `date_imported` with the datetime when the upgrader was executed. //! //! NOTES for `torrust_user_profiles` table transfer: //! //! - In v2, the table `torrust_user_profiles` contains two new fields: `bio` -//! and `avatar`. Empty string is used as default value. +//! and `avatar`. Empty string is used as default value. //! //! //! If you want more information about this command you can read the [issue 56](https://github.com/torrust/torrust-index/issues/56). diff --git a/src/utils/parse_torrent.rs b/src/utils/parse_torrent.rs index dffeac27..0446f331 100644 --- a/src/utils/parse_torrent.rs +++ b/src/utils/parse_torrent.rs @@ -94,9 +94,9 @@ struct ParsedInfoDictFromMetainfoFile { /// This function will return an error if: /// /// - The torrent file is not a valid bencoded torrent file containing an `info` -/// dictionary key. +/// dictionary key. /// - The original torrent info-hash cannot be bencoded from the parsed `info` -/// dictionary is not a valid bencoded dictionary. +/// dictionary is not a valid bencoded dictionary. pub fn calculate_info_hash(bytes: &[u8]) -> Result { // Extract the info dictionary let metainfo: ParsedInfoDictFromMetainfoFile = diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index ff350454..7f691e69 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -7,8 +7,8 @@ //! set the environment variable `TORRUST_INDEX_E2E_SHARED` to `true`. //! //! > **NOTICE**: The server must be running before running the tests. The -//! server url is hardcoded to `http://localhost:3001` for now. We are planning -//! to make it configurable in the future via a environment variable. +//! > server url is hardcoded to `http://localhost:3001` for now. We are planning +//! > to make it configurable in the future via a environment variable. //! //! ```text //! TORRUST_INDEX_E2E_SHARED=true cargo test @@ -22,8 +22,8 @@ //! ``` //! //! > **NOTICE**: Some tests require the real tracker to be running, so they -//! can only be run in shared mode until we implement a mock for the -//! `torrust_index::tracker::TrackerService`. +//! > can only be run in shared mode until we implement a mock for the +//! > `torrust_index::tracker::TrackerService`. //! //! You may have errors like `Too many open files (os error 24)`. If so, you //! need to increase the limit of open files for the current user. You can do From c7c9225eb06dff997f679758ed04800b4bf1a0b3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 12:53:35 +0100 Subject: [PATCH 180/309] refactor: [#599] extract types for torrust_index::config::v1::auth::Auth --- src/config/mod.rs | 5 ++- src/config/v1/auth.rs | 45 +++++++++++++++++-- src/config/v1/mod.rs | 4 +- .../api/client/v1/contexts/settings/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 2 +- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 420813dc..c8fb08aa 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -338,6 +338,7 @@ fn parse_url(url_str: &str) -> Result { #[cfg(test)] mod tests { + use crate::config::v1::auth::SecretKey; use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; #[cfg(test)] @@ -521,7 +522,7 @@ mod tests { assert_eq!( configuration.get_all().await.auth.secret_key, - "OVERRIDDEN AUTH SECRET KEY".to_string() + SecretKey::new("OVERRIDDEN AUTH SECRET KEY") ); } @@ -542,7 +543,7 @@ mod tests { let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); - assert_eq!(settings.auth.secret_key, "OVERRIDDEN AUTH SECRET KEY".to_string()); + assert_eq!(settings.auth.secret_key, SecretKey::new("OVERRIDDEN AUTH SECRET KEY")); Ok(()) }); diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 13fec842..b23c536d 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -1,3 +1,5 @@ +use std::fmt; + use serde::{Deserialize, Serialize}; /// Authentication options. @@ -10,7 +12,7 @@ pub struct Auth { /// The maximum password length. pub max_password_length: usize, /// The secret key used to sign JWT tokens. - pub secret_key: String, + pub secret_key: SecretKey, } impl Default for Auth { @@ -19,14 +21,14 @@ impl Default for Auth { email_on_signup: EmailOnSignup::default(), min_password_length: 6, max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), + secret_key: SecretKey::new("MaxVerstappenWC2021"), } } } impl Auth { pub fn override_secret_key(&mut self, secret_key: &str) { - self.secret_key = secret_key.to_string(); + self.secret_key = SecretKey::new(secret_key); } } @@ -46,3 +48,40 @@ impl Default for EmailOnSignup { Self::Optional } } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SecretKey(String); + +impl SecretKey { + /// # Panics + /// + /// Will panic if the key if empty. + #[must_use] + pub fn new(key: &str) -> Self { + assert!(!key.is_empty(), "secret key cannot be empty"); + + Self(key.to_owned()) + } + + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl fmt::Display for SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::SecretKey; + + #[test] + #[should_panic(expected = "secret key cannot be empty")] + fn secret_key_can_not_be_empty() { + drop(SecretKey::new("")); + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 04d8b9f9..775865de 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -11,7 +11,7 @@ pub mod website; use serde::{Deserialize, Serialize}; use self::api::Api; -use self::auth::Auth; +use self::auth::{Auth, SecretKey}; use self::database::Database; use self::image_cache::ImageCache; use self::mail::Mail; @@ -60,7 +60,7 @@ impl Settings { "***".clone_into(&mut self.tracker.token); "***".clone_into(&mut self.database.connect_url); "***".clone_into(&mut self.mail.password); - "***".clone_into(&mut self.auth.secret_key); + self.auth.secret_key = SecretKey::new("***"); } } diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index e8f54759..c25bcd79 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -135,7 +135,7 @@ impl From for Auth { email_on_signup: format!("{:?}", auth.email_on_signup), min_password_length: auth.min_password_length, max_password_length: auth.max_password_length, - secret_key: auth.secret_key, + secret_key: auth.secret_key.to_string(), } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 42e4771d..c8aafa8d 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -134,7 +134,7 @@ impl From for Auth { email_on_signup: format!("{:?}", auth.email_on_signup), min_password_length: auth.min_password_length, max_password_length: auth.max_password_length, - secret_key: auth.secret_key, + secret_key: auth.secret_key.to_string(), } } } From 28617b675cde4b4314f2a4f8304df87ccdbecf71 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 13:29:30 +0100 Subject: [PATCH 181/309] refactor: [#599] extract types for torrust_index::config::v1::database::Database --- src/app.rs | 2 +- src/config/v1/database.rs | 76 ++++++++++++++++++- src/config/v1/mod.rs | 4 +- .../tracker_statistics_importer/app.rs | 2 +- .../api/client/v1/contexts/settings/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 2 +- tests/environments/app_starter.rs | 2 +- tests/environments/isolated.rs | 4 +- 8 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9df253e6..761fbb3a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -52,7 +52,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running settings.validate().expect("invalid settings"); // From [database] config - let database_connect_url = settings.database.connect_url.clone(); + let database_connect_url = settings.database.connect_url.clone().to_string(); // From [importer] config let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; let importer_port = settings.tracker_statistics_importer.port; diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs index 325a8936..ee125347 100644 --- a/src/config/v1/database.rs +++ b/src/config/v1/database.rs @@ -1,16 +1,86 @@ +use std::fmt; + use serde::{Deserialize, Serialize}; /// Database configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Database { - /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. - pub connect_url: String, + /// The connection string for the database. For example: + /// + /// Masked: `***`. + /// Sqlite: `sqlite://data.db?mode=rwc`. + /// Mysql: `mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing`. + pub connect_url: ConnectOptions, } impl Default for Database { fn default() -> Self { Self { - connect_url: "sqlite://data.db?mode=rwc".to_string(), + connect_url: ConnectOptions::new("sqlite://data.db?mode=rwc"), } } } + +/// This allows a particular case when we want to hide the connection options +/// because it contains secrets we don't want to show. +const DB_CONNECT_MASKED: &str = "***"; + +/// Prefix for connection to `SQLite` database. +const DB_CONNECT_SQLITE_PREFIX: &str = "sqlite://"; + +/// Prefix for connection to `MySQL` database. +const DB_CONNECT_MYSQL_PREFIX: &str = "mysql://"; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ConnectOptions(String); + +impl ConnectOptions { + /// # Panics + /// + /// Will panic if the connect options are empty. + #[must_use] + pub fn new(connect_options: &str) -> Self { + assert!(!connect_options.is_empty(), "database connect options cannot be empty"); + assert!( + connect_options.starts_with(DB_CONNECT_SQLITE_PREFIX) + || connect_options.starts_with(DB_CONNECT_MYSQL_PREFIX) + || connect_options.starts_with(DB_CONNECT_MASKED), + "database driver not supported" + ); + + Self(connect_options.to_owned()) + } + + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl fmt::Display for ConnectOptions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::ConnectOptions; + + #[test] + #[should_panic(expected = "database connect options cannot be empty")] + fn database_connect_options_can_not_be_empty() { + drop(ConnectOptions::new("")); + } + + #[test] + #[should_panic(expected = "database driver not supported")] + fn database_connect_options_only_supports_sqlite_and_mysql() { + drop(ConnectOptions::new("not-supported://")); + } + + #[test] + fn database_connect_options_can_be_masked() { + drop(ConnectOptions::new("***")); + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 775865de..5aa668ce 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use self::api::Api; use self::auth::{Auth, SecretKey}; -use self::database::Database; +use self::database::{ConnectOptions, Database}; use self::image_cache::ImageCache; use self::mail::Mail; use self::net::Network; @@ -58,7 +58,7 @@ impl Settings { pub fn remove_secrets(&mut self) { "***".clone_into(&mut self.tracker.token); - "***".clone_into(&mut self.database.connect_url); + self.database.connect_url = ConnectOptions::new("***"); "***".clone_into(&mut self.mail.password); self.auth.secret_key = SecretKey::new("***"); } diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index d7cc16fc..ad0e8fd2 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -103,7 +103,7 @@ pub async fn import() { eprintln!("Tracker url: {}", tracker_url.green()); let database = Arc::new( - database::connect(&settings.database.connect_url) + database::connect(&settings.database.connect_url.to_string()) .await .expect("unable to connect to db"), ); diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index c25bcd79..44ba0b80 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -143,7 +143,7 @@ impl From for Auth { impl From for Database { fn from(database: DomainDatabase) -> Self { Self { - connect_url: database.connect_url, + connect_url: database.connect_url.to_string(), } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index c8aafa8d..4007e796 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -142,7 +142,7 @@ impl From for Auth { impl From for Database { fn from(database: DomainDatabase) -> Self { Self { - connect_url: database.connect_url, + connect_url: database.connect_url.to_string(), } } } diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index a981d524..3d6fe36c 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -89,7 +89,7 @@ impl AppStarter { #[must_use] pub fn database_connect_url(&self) -> String { - self.configuration.database.connect_url.clone() + self.configuration.database.connect_url.clone().to_string() } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index bb9dc24e..bad6278d 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,5 +1,6 @@ use tempfile::TempDir; use torrust_index::config; +use torrust_index::config::v1::database::ConnectOptions; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; @@ -82,7 +83,8 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { configuration.tracker_statistics_importer.port = FREE_PORT; // Ephemeral SQLite database - configuration.database.connect_url = format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir)); + configuration.database.connect_url = + ConnectOptions::new(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))); configuration } From 748f35784c1fb22fbc510f5c684e58ebbf34f9d7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 13:55:41 +0100 Subject: [PATCH 182/309] chore(deps): enable serialization for lettre We will use the type `Mailbox` in the configuration and it needs to be serializable. --- Cargo.lock | 3 +++ Cargo.toml | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 041568dd..33c88287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1492,11 +1492,14 @@ dependencies = [ "quoted_printable", "rustls 0.23.7", "rustls-pemfile", + "serde", + "serde_json", "socket2", "tokio", "tokio-native-tls", "tokio-rustls 0.26.0", "url", + "uuid", "webpki-roots", ] diff --git a/Cargo.toml b/Cargo.toml index 8abd1d9f..df6897cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,14 @@ hyper-util = { version = "0.1.3", features = ["http1", "http2", "tokio"] } indexmap = "2" jsonwebtoken = "9" lazy_static = "1.4.0" -lettre = { version = "0", features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls", "tokio1-rustls-tls"] } +lettre = { version = "0", features = [ + "builder", + "file-transport-envelope", + "smtp-transport", + "tokio1", + "tokio1-native-tls", + "tokio1-rustls-tls", +] } log = "0" pbkdf2 = { version = "0", features = ["simple"] } pin-project-lite = "0.2" From 7edbf7cf2026cdb28f0c3817d0dc412922e286fb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 22 May 2024 13:56:52 +0100 Subject: [PATCH 183/309] refactor: [#599] extract types for config::v1::mail::Mail --- src/config/v1/mail.rs | 9 +++++---- src/mailer.rs | 4 ++-- src/web/api/client/v1/contexts/settings/mod.rs | 4 ++-- tests/common/contexts/settings/mod.rs | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 4e166281..45ed6145 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -1,3 +1,4 @@ +use lettre::message::Mailbox; use serde::{Deserialize, Serialize}; /// SMTP configuration. @@ -6,9 +7,9 @@ pub struct Mail { /// Whether or not to enable email verification on signup. pub email_verification_enabled: bool, /// The email address to send emails from. - pub from: String, + pub from: Mailbox, /// The email address to reply to. - pub reply_to: String, + pub reply_to: Mailbox, /// The username to use for SMTP authentication. pub username: String, /// The password to use for SMTP authentication. @@ -23,8 +24,8 @@ impl Default for Mail { fn default() -> Self { Self { email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), + from: "example@email.com".parse().expect("valid mailbox"), + reply_to: "noreply@email.com".parse().expect("valid mailbox"), username: String::default(), password: String::default(), server: String::default(), diff --git a/src/mailer.rs b/src/mailer.rs index 39c1d0d4..d260729e 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -121,8 +121,8 @@ impl Service { let settings = self.cfg.settings.read().await; Message::builder() - .from(settings.mail.from.parse().unwrap()) - .reply_to(settings.mail.reply_to.parse().unwrap()) + .from(settings.mail.from.clone()) + .reply_to(settings.mail.reply_to.clone()) .to(to.parse().unwrap()) } diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 44ba0b80..3b97b4b7 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -152,8 +152,8 @@ impl From for Mail { fn from(mail: DomainMail) -> Self { Self { email_verification_enabled: mail.email_verification_enabled, - from: mail.from, - reply_to: mail.reply_to, + from: mail.from.to_string(), + reply_to: mail.reply_to.to_string(), username: mail.username, password: mail.password, server: mail.server, diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 4007e796..4a3b92cd 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -151,8 +151,8 @@ impl From for Mail { fn from(mail: DomainMail) -> Self { Self { email_verification_enabled: mail.email_verification_enabled, - from: mail.from, - reply_to: mail.reply_to, + from: mail.from.to_string(), + reply_to: mail.reply_to.to_string(), username: mail.username, password: mail.password, server: mail.server, From c422e52a42297f61c1bb52b97a4bd03686fbe89e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 23 May 2024 13:34:09 +0100 Subject: [PATCH 184/309] chore(deps): [#599] enable serialization for URls --- Cargo.lock | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 33c88287..3724c563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3615,6 +3615,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df6897cc..461ca72a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ tower = { version = "0.4", features = ["timeout"] } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } trace = "0.1.7" tracing = "0.1.40" -url = "2.5.0" +url = { version = "2.5.0", features = ["serde"] } urlencoding = "2" uuid = { version = "1", features = ["v4"] } From ab4717ddd533c971d5cf3dbaef10f25787cca21c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 23 May 2024 13:36:20 +0100 Subject: [PATCH 185/309] refactor: [#599] extract types for config::v1::net::Network --- src/config/mod.rs | 9 +++++---- src/config/v1/net.rs | 8 +++++++- src/mailer.rs | 8 ++++---- src/web/api/client/v1/contexts/settings/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index c8fb08aa..b4284713 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -316,8 +316,7 @@ impl Configuration { pub async fn get_api_base_url(&self) -> Option { let settings_lock = self.settings.read().await; - - settings_lock.net.base_url.clone() + settings_lock.net.base_url.as_ref().map(std::string::ToString::to_string) } } @@ -338,6 +337,8 @@ fn parse_url(url_str: &str) -> Result { #[cfg(test)] mod tests { + use url::Url; + use crate::config::v1::auth::SecretKey; use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; @@ -442,10 +443,10 @@ mod tests { assert_eq!(configuration.get_api_base_url().await, None); let mut settings_lock = configuration.settings.write().await; - settings_lock.net.base_url = Some("http://localhost".to_string()); + settings_lock.net.base_url = Some(Url::parse("http://localhost").unwrap()); drop(settings_lock); - assert_eq!(configuration.get_api_base_url().await, Some("http://localhost".to_string())); + assert_eq!(configuration.get_api_base_url().await, Some("http://localhost/".to_string())); } #[tokio::test] diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index fa473e16..28b101fc 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -1,15 +1,21 @@ use serde::{Deserialize, Serialize}; +use url::Url; use crate::config::Tsl; /// The the base URL for the API. +/// +/// NOTICE: that `port` and por in `base_url` does not necessarily match because +/// the application migth be running behind a proxy. The local socket could be +/// bound to, for example, port 80 but the application could be exposed publicly +/// via port 443, which is a very common setup. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Network { /// The port to listen on. Default to `3001`. pub port: u16, /// The base URL for the API. For example: `http://localhost`. /// If not set, the base URL will be inferred from the request. - pub base_url: Option, + pub base_url: Option, /// TSL configuration. pub tsl: Option, } diff --git a/src/mailer.rs b/src/mailer.rs index d260729e..955aefbe 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -141,10 +141,10 @@ impl Service { let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(key)).unwrap(); - let mut base_url = &base_url.to_string(); - if let Some(cfg_base_url) = &settings.net.base_url { - base_url = cfg_base_url; - } + let base_url = match &settings.net.base_url { + Some(url) => url.to_string(), + None => base_url.to_string(), + }; format!("{base_url}/{API_VERSION_URL_PREFIX}/user/email/verify/{token}") } diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 3b97b4b7..174a92ef 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -124,7 +124,7 @@ impl From for Network { fn from(net: DomainNetwork) -> Self { Self { port: net.port, - base_url: net.base_url, + base_url: net.base_url.map(|url_without_port| url_without_port.to_string()), } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 4a3b92cd..0eca1bb6 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -123,7 +123,7 @@ impl From for Network { fn from(net: DomainNetwork) -> Self { Self { port: net.port, - base_url: net.base_url, + base_url: net.base_url.as_ref().map(std::string::ToString::to_string), } } } From b1d526709848f73b618c66739d4aa09fafe9e4b0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 23 May 2024 13:53:19 +0100 Subject: [PATCH 186/309] refactor: [#599] use URL type for connect_url in database config We were using a cusomt type but sqlx defines it as a URL. WE can validate this type in the configration and let the app domain validate if the connection URL is a valid one. For example, if the app supports the database driver. Otherwise we have to change this first level valitation anytime we support a new DB. --- src/config/v1/database.rs | 74 +------------------ src/config/v1/mod.rs | 6 +- .../tracker_statistics_importer/app.rs | 2 +- tests/e2e/environment.rs | 9 ++- tests/environments/isolated.rs | 4 +- 5 files changed, 19 insertions(+), 76 deletions(-) diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs index ee125347..5efa824a 100644 --- a/src/config/v1/database.rs +++ b/src/config/v1/database.rs @@ -1,86 +1,20 @@ -use std::fmt; - use serde::{Deserialize, Serialize}; +use url::Url; /// Database configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Database { - /// The connection string for the database. For example: + /// The connection URL for the database. For example: /// - /// Masked: `***`. /// Sqlite: `sqlite://data.db?mode=rwc`. /// Mysql: `mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing`. - pub connect_url: ConnectOptions, + pub connect_url: Url, } impl Default for Database { fn default() -> Self { Self { - connect_url: ConnectOptions::new("sqlite://data.db?mode=rwc"), + connect_url: Url::parse("sqlite://data.db?mode=rwc").unwrap(), } } } - -/// This allows a particular case when we want to hide the connection options -/// because it contains secrets we don't want to show. -const DB_CONNECT_MASKED: &str = "***"; - -/// Prefix for connection to `SQLite` database. -const DB_CONNECT_SQLITE_PREFIX: &str = "sqlite://"; - -/// Prefix for connection to `MySQL` database. -const DB_CONNECT_MYSQL_PREFIX: &str = "mysql://"; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ConnectOptions(String); - -impl ConnectOptions { - /// # Panics - /// - /// Will panic if the connect options are empty. - #[must_use] - pub fn new(connect_options: &str) -> Self { - assert!(!connect_options.is_empty(), "database connect options cannot be empty"); - assert!( - connect_options.starts_with(DB_CONNECT_SQLITE_PREFIX) - || connect_options.starts_with(DB_CONNECT_MYSQL_PREFIX) - || connect_options.starts_with(DB_CONNECT_MASKED), - "database driver not supported" - ); - - Self(connect_options.to_owned()) - } - - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } -} - -impl fmt::Display for ConnectOptions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[cfg(test)] -mod tests { - use super::ConnectOptions; - - #[test] - #[should_panic(expected = "database connect options cannot be empty")] - fn database_connect_options_can_not_be_empty() { - drop(ConnectOptions::new("")); - } - - #[test] - #[should_panic(expected = "database driver not supported")] - fn database_connect_options_only_supports_sqlite_and_mysql() { - drop(ConnectOptions::new("not-supported://")); - } - - #[test] - fn database_connect_options_can_be_masked() { - drop(ConnectOptions::new("***")); - } -} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 5aa668ce..8deea44d 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use self::api::Api; use self::auth::{Auth, SecretKey}; -use self::database::{ConnectOptions, Database}; +use self::database::Database; use self::image_cache::ImageCache; use self::mail::Mail; use self::net::Network; @@ -58,7 +58,9 @@ impl Settings { pub fn remove_secrets(&mut self) { "***".clone_into(&mut self.tracker.token); - self.database.connect_url = ConnectOptions::new("***"); + if let Some(_password) = self.database.connect_url.password() { + let _ = self.database.connect_url.set_password(Some("***")); + } "***".clone_into(&mut self.mail.password); self.auth.secret_key = SecretKey::new("***"); } diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index ad0e8fd2..eccbbc4c 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -103,7 +103,7 @@ pub async fn import() { eprintln!("Tracker url: {}", tracker_url.green()); let database = Arc::new( - database::connect(&settings.database.connect_url.to_string()) + database::connect(settings.database.connect_url.as_ref()) .await .expect("unable to connect to db"), ); diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 40840953..c81bf70a 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,6 +1,7 @@ use std::env; use torrust_index::web::api::Version; +use url::Url; use super::config::{initialize_configuration, ENV_VAR_DB_CONNECT_URL, ENV_VAR_INDEX_SHARED}; use crate::common::contexts::settings::Settings; @@ -90,8 +91,14 @@ impl TestEnv { pub fn server_settings_masking_secrets(&self) -> Option { match self.starting_settings.clone() { Some(mut settings) => { + // Mask password in DB connect URL if present + let mut connect_url = Url::parse(&settings.database.connect_url).expect("valid database connect URL"); + if let Some(_password) = connect_url.password() { + let _ = connect_url.set_password(Some("***")); + settings.database.connect_url = connect_url.to_string(); + } + "***".clone_into(&mut settings.tracker.token); - "***".clone_into(&mut settings.database.connect_url); "***".clone_into(&mut settings.mail.password); "***".clone_into(&mut settings.auth.secret_key); Some(settings) diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index bad6278d..7a389930 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,8 +1,8 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::database::ConnectOptions; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; +use url::Url; use super::app_starter::AppStarter; use crate::common::random; @@ -84,7 +84,7 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { // Ephemeral SQLite database configuration.database.connect_url = - ConnectOptions::new(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))); + Url::parse(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))).unwrap(); configuration } From 8e539826ed7a1dd3ec43eea25a38a82f6e7d4cae Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 23 May 2024 15:58:34 +0100 Subject: [PATCH 187/309] refactor: [#599] extract types for config::v1::tracker::Tracker --- .../config/index.development.sqlite3.toml | 2 +- src/config/mod.rs | 44 ++++-------- src/config/v1/mod.rs | 6 +- src/config/v1/tracker.rs | 72 +++++++++++++------ src/config/validator.rs | 6 -- .../tracker_statistics_importer/app.rs | 2 +- src/databases/database.rs | 3 +- src/databases/mysql.rs | 5 +- src/databases/sqlite.rs | 5 +- src/errors.rs | 6 +- src/lib.rs | 4 +- src/models/response.rs | 7 +- src/models/torrent_file.rs | 13 ++-- src/services/torrent.rs | 3 +- src/tracker/api.rs | 21 ++++-- src/tracker/service.rs | 35 +++++---- src/tracker/statistics_importer.rs | 7 +- .../api/client/v1/contexts/settings/mod.rs | 8 ++- .../api/server/v1/contexts/settings/mod.rs | 4 +- tests/common/client.rs | 27 +++++-- tests/common/contexts/settings/mod.rs | 8 ++- tests/common/contexts/settings/responses.rs | 3 +- tests/e2e/environment.rs | 6 +- .../web/api/v1/contexts/torrent/contract.rs | 6 +- .../e2e/web/api/v1/contexts/torrent/steps.rs | 2 +- 25 files changed, 182 insertions(+), 123 deletions(-) diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 11a3f48b..c0b04253 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -4,7 +4,7 @@ log_level = "info" name = "Torrust" [tracker] -api_url = "http://localhost:1212" +api_url = "http://localhost:1212/" mode = "Public" token = "MyAccessToken" token_valid_seconds = 7257600 diff --git a/src/config/mod.rs b/src/config/mod.rs index b4284713..9a2bc633 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -15,6 +15,7 @@ use tokio::sync::RwLock; use torrust_index_located_error::LocatedError; use url::Url; +use self::v1::tracker::ApiToken; use crate::web::api::server::DynError; pub type Settings = v1::Settings; @@ -55,7 +56,7 @@ pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; pub struct Info { config_toml: Option, config_toml_path: String, - tracker_api_token: Option, + tracker_api_token: Option, auth_secret_key: Option, } @@ -88,7 +89,10 @@ impl Info { default_config_toml_path }; - let tracker_api_token = env::var(env_var_tracker_api_admin_token).ok(); + let tracker_api_token = env::var(env_var_tracker_api_admin_token) + .ok() + .map(|token| ApiToken::new(&token)); + let auth_secret_key = env::var(env_var_auth_secret_key).ok(); Ok(Self { @@ -325,21 +329,18 @@ impl Configuration { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ConfigurationPublic { website_name: String, - tracker_url: String, + tracker_url: Url, tracker_mode: TrackerMode, email_on_signup: EmailOnSignup, } -fn parse_url(url_str: &str) -> Result { - Url::parse(url_str) -} - #[cfg(test)] mod tests { use url::Url; use crate::config::v1::auth::SecretKey; + use crate::config::v1::tracker::ApiToken; use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; #[cfg(test)] @@ -350,7 +351,7 @@ mod tests { [tracker] url = "udp://localhost:6969" mode = "Public" - api_url = "http://localhost:1212" + api_url = "http://localhost:1212/" token = "MyAccessToken" token_valid_seconds = 7257600 @@ -475,7 +476,7 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), - tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()), + tracker_api_token: Some(ApiToken::new("OVERRIDDEN API TOKEN")), auth_secret_key: None, }; @@ -483,7 +484,7 @@ mod tests { assert_eq!( configuration.get_all().await.tracker.token, - "OVERRIDDEN API TOKEN".to_string() + ApiToken::new("OVERRIDDEN API TOKEN") ); } @@ -504,7 +505,7 @@ mod tests { let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); - assert_eq!(settings.tracker.token, "OVERRIDDEN API TOKEN".to_string()); + assert_eq!(settings.tracker.token, ApiToken::new("OVERRIDDEN API TOKEN")); Ok(()) }); @@ -550,24 +551,9 @@ mod tests { }); } - mod syntax_checks { - // todo: use rich types in configuration structs for basic syntax checks. - - use crate::config::validator::Validator; - use crate::config::Configuration; - - #[tokio::test] - async fn tracker_url_should_be_a_valid_url() { - let configuration = Configuration::default(); - - let mut settings_lock = configuration.settings.write().await; - settings_lock.tracker.url = "INVALID URL".to_string(); - - assert!(settings_lock.validate().is_err()); - } - } - mod semantic_validation { + use url::Url; + use crate::config::validator::Validator; use crate::config::{Configuration, TrackerMode}; @@ -577,7 +563,7 @@ mod tests { let mut settings_lock = configuration.settings.write().await; settings_lock.tracker.mode = TrackerMode::Private; - settings_lock.tracker.url = "udp://localhost:6969".to_string(); + settings_lock.tracker.url = Url::parse("udp://localhost:6969").unwrap(); assert!(settings_lock.validate().is_err()); } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 8deea44d..fea3e2ae 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -16,7 +16,7 @@ use self::database::Database; use self::image_cache::ImageCache; use self::mail::Mail; use self::net::Network; -use self::tracker::Tracker; +use self::tracker::{ApiToken, Tracker}; use self::tracker_statistics_importer::TrackerStatisticsImporter; use self::website::Website; use super::validator::{ValidationError, Validator}; @@ -48,7 +48,7 @@ pub struct Settings { } impl Settings { - pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) { self.tracker.override_tracker_api_token(tracker_api_token); } @@ -57,7 +57,7 @@ impl Settings { } pub fn remove_secrets(&mut self) { - "***".clone_into(&mut self.tracker.token); + self.tracker.token = ApiToken::new("***"); if let Some(_password) = self.database.connect_url.password() { let _ = self.database.connect_url.set_password(Some("***")); } diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index fd95a82e..bc8a2eb0 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -1,29 +1,31 @@ +use std::fmt; + use serde::{Deserialize, Serialize}; -use torrust_index_located_error::Located; +use url::Url; use super::{ValidationError, Validator}; -use crate::config::{parse_url, TrackerMode}; +use crate::config::TrackerMode; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Tracker { /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. - pub url: String, + pub url: Url, /// The mode of the tracker. For example: `Public`. /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. pub mode: TrackerMode, - /// The url of the tracker API. For example: `http://localhost:1212`. - pub api_url: String, + /// The url of the tracker API. For example: `http://localhost:1212/`. + pub api_url: Url, /// The token used to authenticate with the tracker API. - pub token: String, - /// The amount of seconds the token is valid. + pub token: ApiToken, + /// The amount of seconds the tracker API token is valid. pub token_valid_seconds: u64, } impl Tracker { - pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { - self.token = tracker_api_token.to_string(); + pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) { + self.token = tracker_api_token.clone(); } } @@ -32,15 +34,6 @@ impl Validator for Tracker { let tracker_mode = self.mode.clone(); let tracker_url = self.url.clone(); - let tracker_url = match parse_url(&tracker_url) { - Ok(url) => url, - Err(err) => { - return Err(ValidationError::InvalidTrackerUrl { - source: Located(err).into(), - }) - } - }; - if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); } @@ -52,11 +45,48 @@ impl Validator for Tracker { impl Default for Tracker { fn default() -> Self { Self { - url: "udp://localhost:6969".to_string(), + url: Url::parse("udp://localhost:6969").unwrap(), mode: TrackerMode::default(), - api_url: "http://localhost:1212".to_string(), - token: "MyAccessToken".to_string(), + api_url: Url::parse("http://localhost:1212/").unwrap(), + token: ApiToken::new("MyAccessToken"), token_valid_seconds: 7_257_600, } } } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ApiToken(String); + +impl ApiToken { + /// # Panics + /// + /// Will panic if the tracker API token if empty. + #[must_use] + pub fn new(key: &str) -> Self { + assert!(!key.is_empty(), "tracker API token cannot be empty"); + + Self(key.to_owned()) + } + + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl fmt::Display for ApiToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::ApiToken; + + #[test] + #[should_panic(expected = "tracker API token cannot be empty")] + fn secret_key_can_not_be_empty() { + drop(ApiToken::new("")); + } +} diff --git a/src/config/validator.rs b/src/config/validator.rs index d3ef047a..3578461d 100644 --- a/src/config/validator.rs +++ b/src/config/validator.rs @@ -1,15 +1,9 @@ //! Trait to validate the whole settings of sections of the settings. use thiserror::Error; -use torrust_index_located_error::LocatedError; -use url::ParseError; /// Errors that can occur validating the configuration. #[derive(Error, Debug)] pub enum ValidationError { - /// Unable to load the configuration from the configuration file. - #[error("Invalid tracker URL: {source}")] - InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, - #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] UdpTrackersInPrivateModeNotSupported, } diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index eccbbc4c..edb43304 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -100,7 +100,7 @@ pub async fn import() { let tracker_url = settings.tracker.url.clone(); - eprintln!("Tracker url: {}", tracker_url.green()); + eprintln!("Tracker url: {}", tracker_url.to_string().green()); let database = Arc::new( database::connect(settings.database.connect_url.as_ref()) diff --git a/src/databases/database.rs b/src/databases/database.rs index a19970be..ce3843b0 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; +use url::Url; use crate::databases::mysql::Mysql; use crate::databases::sqlite::Sqlite; @@ -336,7 +337,7 @@ pub trait Database: Sync + Send { async fn get_tags_for_torrent_id(&self, torrent_id: i64) -> Result, Error>; /// Update the seeders and leechers info for a torrent with `torrent_id`, `tracker_url`, `seeders` and `leechers`. - async fn update_tracker_info(&self, torrent_id: i64, tracker_url: &str, seeders: i64, leechers: i64) -> Result<(), Error>; + async fn update_tracker_info(&self, torrent_id: i64, tracker_url: &Url, seeders: i64, leechers: i64) -> Result<(), Error>; /// Delete a torrent with `torrent_id`. async fn delete_torrent(&self, torrent_id: i64) -> Result<(), Error>; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index d28f1ae7..beec1a80 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool}; +use url::Url; use super::database::TABLES_TO_TRUNCATE; use crate::databases::database; @@ -1072,13 +1073,13 @@ impl Database for Mysql { async fn update_tracker_info( &self, torrent_id: i64, - tracker_url: &str, + tracker_url: &Url, seeders: i64, leechers: i64, ) -> Result<(), database::Error> { query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES (?, ?, ?, ?, ?)") .bind(torrent_id) - .bind(tracker_url) + .bind(tracker_url.to_string()) .bind(seeders) .bind(leechers) .bind(datetime_now()) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 421292d6..a70beac6 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool}; +use url::Url; use super::database::TABLES_TO_TRUNCATE; use crate::databases::database; @@ -1064,13 +1065,13 @@ impl Database for Sqlite { async fn update_tracker_info( &self, torrent_id: i64, - tracker_url: &str, + tracker_url: &Url, seeders: i64, leechers: i64, ) -> Result<(), database::Error> { query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(torrent_id) - .bind(tracker_url) + .bind(tracker_url.to_string()) .bind(seeders) .bind(leechers) .bind(datetime_now()) diff --git a/src/errors.rs b/src/errors.rs index 71ff3ab3..9723b4f7 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -161,7 +161,7 @@ pub enum ServiceError { #[display(fmt = "Tracker response error. The operation could not be performed.")] TrackerResponseError, - #[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can be parsed.")] + #[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can't be parsed.")] TrackerUnknownResponse, #[display(fmt = "Torrent not found in tracker.")] @@ -253,8 +253,8 @@ impl From for ServiceError { fn from(e: TrackerAPIError) -> Self { eprintln!("{e}"); match e { - TrackerAPIError::TrackerOffline => ServiceError::TrackerOffline, - TrackerAPIError::InternalServerError => ServiceError::TrackerResponseError, + TrackerAPIError::TrackerOffline { error: _ } => ServiceError::TrackerOffline, + TrackerAPIError::InternalServerError | TrackerAPIError::NotFound => ServiceError::TrackerResponseError, TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker, TrackerAPIError::UnexpectedResponseStatus | TrackerAPIError::MissingResponseBody diff --git a/src/lib.rs b/src/lib.rs index b386549d..c3148e86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! [tracker] //! url = "udp://localhost:6969" //! mode = "Public" -//! api_url = "http://localhost:1212" +//! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 //! ``` @@ -172,7 +172,7 @@ //! [tracker] //! url = "udp://localhost:6969" //! mode = "Public" -//! api_url = "http://localhost:1212" +//! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 //! diff --git a/src/models/response.rs b/src/models/response.rs index 020eb9be..ea3ef7f3 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use url::Url; use super::category::Category; use super::torrent::TorrentId; @@ -107,12 +108,12 @@ impl TorrentResponse { } /// It adds the tracker URL in the first position of the tracker list. - pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) { + pub fn include_url_as_main_tracker(&mut self, tracker_url: &Url) { // Remove any existing instances of tracker_url - self.trackers.retain(|tracker| tracker != tracker_url); + self.trackers.retain(|tracker| *tracker != tracker_url.to_string()); // Insert tracker_url at the first position - self.trackers.insert(0, tracker_url.to_owned()); + self.trackers.insert(0, tracker_url.to_owned().to_string()); } } diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 1bbd7157..ac28a6dc 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_bencode::ser; use serde_bytes::ByteBuf; use sha1::{Digest, Sha1}; +use url::Url; use super::info_hash::InfoHash; use crate::utils::hex::{from_bytes, into_bytes}; @@ -127,14 +128,14 @@ impl Torrent { /// /// It will be the URL in the `announce` field and also the first URL in the /// `announce_list`. - pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) { + pub fn include_url_as_main_tracker(&mut self, tracker_url: &Url) { self.set_announce_to(tracker_url); self.add_url_to_front_of_announce_list(tracker_url); } /// Sets the announce url to the tracker url. - pub fn set_announce_to(&mut self, tracker_url: &str) { - self.announce = Some(tracker_url.to_owned()); + pub fn set_announce_to(&mut self, tracker_url: &Url) { + self.announce = Some(tracker_url.to_owned().to_string()); } /// Adds a new tracker URL to the front of the `announce_list`, removes duplicates, @@ -146,15 +147,15 @@ impl Torrent { /// a strict requirement of the `BitTorrent` protocol; it's more of a /// convention followed by some torrent creators for redundancy and to /// ensure better availability of trackers. - pub fn add_url_to_front_of_announce_list(&mut self, tracker_url: &str) { + pub fn add_url_to_front_of_announce_list(&mut self, tracker_url: &Url) { if let Some(list) = &mut self.announce_list { // Remove the tracker URL from existing lists for inner_list in list.iter_mut() { - inner_list.retain(|url| url != tracker_url); + inner_list.retain(|url| *url != tracker_url.to_string()); } // Prepend a new vector containing the tracker_url - let vec = vec![tracker_url.to_owned()]; + let vec = vec![tracker_url.to_owned().to_string()]; list.insert(0, vec); // Remove any empty inner lists diff --git a/src/services/torrent.rs b/src/services/torrent.rs index c3c7e7f0..606f8550 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use log::debug; use serde_derive::{Deserialize, Serialize}; +use url::Url; use super::category::DbCategoryRepository; use super::user::DbUserRepository; @@ -432,7 +433,7 @@ impl Index { Ok(torrent_response) } - async fn get_tracker_url(&self) -> String { + async fn get_tracker_url(&self) -> Url { let settings = self.configuration.settings.read().await; settings.tracker.url.clone() } diff --git a/src/tracker/api.rs b/src/tracker/api.rs index c81a745e..f2e83510 100644 --- a/src/tracker/api.rs +++ b/src/tracker/api.rs @@ -1,25 +1,28 @@ use std::time::Duration; use reqwest::{Error, Response}; +use url::Url; pub struct ConnectionInfo { /// The URL of the tracker API. Eg: . - pub url: String, + pub url: Url, /// The token used to authenticate with the tracker API. pub token: String, } impl ConnectionInfo { #[must_use] - pub fn new(url: String, token: String) -> Self { + pub fn new(url: Url, token: String) -> Self { Self { url, token } } } const TOKEN_PARAM_NAME: &str = "token"; +const API_PATH: &str = "api/v1"; +const TOTAL_REQUEST_TIMEOUT_IN_SECS: u64 = 5; pub struct Client { pub connection_info: ConnectionInfo, - api_base_url: String, + api_base_url: Url, client: reqwest::Client, token_param: [(String, String); 1], } @@ -28,14 +31,20 @@ impl Client { /// # Errors /// /// Will fails if it can't build a HTTP client with a timeout. + /// + /// # Panics + /// + /// Will panic if the API base URL is not valid. pub fn new(connection_info: ConnectionInfo) -> Result { - let base_url = format!("{}/api/v1", connection_info.url); - let client = reqwest::Client::builder().timeout(Duration::from_secs(5)).build()?; + let api_base_url = connection_info.url.join(API_PATH).expect("valid URL API path"); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(TOTAL_REQUEST_TIMEOUT_IN_SECS)) + .build()?; let token_param = [(TOKEN_PARAM_NAME.to_string(), connection_info.token.to_string())]; Ok(Self { connection_info, - api_base_url: base_url, + api_base_url, client, token_param, }) diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 3036ce89..5de57d11 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -4,6 +4,7 @@ use derive_more::{Display, Error}; use hyper::StatusCode; use log::{debug, error}; use serde::{Deserialize, Serialize}; +use url::Url; use super::api::{Client, ConnectionInfo}; use crate::config::Configuration; @@ -14,8 +15,8 @@ use crate::models::user::UserId; #[derive(Debug, Display, PartialEq, Eq, Error)] #[allow(dead_code)] pub enum TrackerAPIError { - #[display(fmt = "Error with tracker connection.")] - TrackerOffline, + #[display(fmt = "Error with tracker request: {error}.")] + TrackerOffline { error: String }, #[display(fmt = "Invalid token for tracker API. Check the tracker token in settings.")] InvalidToken, @@ -23,6 +24,9 @@ pub enum TrackerAPIError { #[display(fmt = "Tracker returned an internal server error.")] InternalServerError, + #[display(fmt = "Tracker returned a not found error.")] + NotFound, + #[display(fmt = "Tracker returned an unexpected response status.")] UnexpectedResponseStatus, @@ -77,7 +81,7 @@ pub struct Service { database: Arc>, api_client: Client, token_valid_seconds: u64, - tracker_url: String, + tracker_url: Url, } impl Service { @@ -88,7 +92,7 @@ impl Service { let settings = cfg.settings.read().await; let api_client = Client::new(ConnectionInfo::new( settings.tracker.api_url.clone(), - settings.tracker.token.clone(), + settings.tracker.token.clone().to_string(), )) .expect("a reqwest client should be provided"); let token_valid_seconds = settings.tracker.token_valid_seconds; @@ -140,7 +144,7 @@ impl Service { } } } - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), } } @@ -182,7 +186,7 @@ impl Service { } } } - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), } } @@ -197,7 +201,7 @@ impl Service { /// /// Will return an error if the HTTP request to get generated a new /// user tracker key failed. - pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result { + pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result { debug!(target: "tracker-service", "get personal announce url for user: {user_id}"); let tracker_key = self.database.get_user_tracker_key(user_id).await; @@ -206,7 +210,7 @@ impl Service { Some(tracker_key) => Ok(self.announce_url_with_key(&tracker_key)), None => match self.retrieve_new_tracker_key(user_id).await { Ok(new_tracker_key) => Ok(self.announce_url_with_key(&new_tracker_key)), - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), }, } } @@ -263,7 +267,7 @@ impl Service { } } } - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), } } @@ -283,6 +287,7 @@ impl Service { match maybe_response { Ok(response) => { let status: StatusCode = map_status_code(response.status()); + let url = response.url().clone(); let body = response.text().await.map_err(|_| { error!(target: "tracker-service", "response without body"); @@ -305,13 +310,17 @@ impl Service { Err(TrackerAPIError::InternalServerError) } } + StatusCode::NOT_FOUND => { + error!(target: "tracker-service", "get torrents info 404 response: url {url}"); + Err(TrackerAPIError::NotFound) + } _ => { error!(target: "tracker-service", "get torrents info unhandled response: status {status}, body: {body}"); Err(TrackerAPIError::UnexpectedResponseStatus) } } } - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), } } @@ -360,14 +369,14 @@ impl Service { } } } - Err(_) => Err(TrackerAPIError::TrackerOffline), + Err(err) => Err(TrackerAPIError::TrackerOffline { error: err.to_string() }), } } /// It builds the announce url appending the user tracker key. /// Eg: - fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String { - format!("{}/{}", self.tracker_url, tracker_key.key) + fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> Url { + Url::parse(&format!("{}/{}", self.tracker_url, tracker_key.key)).unwrap() } fn invalid_token_body() -> String { diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index b9842855..87512a42 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -4,6 +4,7 @@ use std::time::Instant; use chrono::{DateTime, Utc}; use log::{debug, error, info}; use text_colorizer::Colorize; +use url::Url; use super::service::{Service, TorrentInfo, TrackerAPIError}; use crate::config::Configuration; @@ -14,7 +15,7 @@ const LOG_TARGET: &str = "Tracker Stats Importer"; pub struct StatisticsImporter { database: Arc>, tracker_service: Arc, - tracker_url: String, + tracker_url: Url, } impl StatisticsImporter { @@ -41,7 +42,7 @@ impl StatisticsImporter { return Ok(()); } - info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); + info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.to_string().yellow()); // Start the timer before the loop let start_time = Instant::now(); @@ -91,7 +92,7 @@ impl StatisticsImporter { return Ok(()); } - info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.yellow()); + info!(target: LOG_TARGET, "Importing {} torrents statistics from tracker {} ...", torrents.len().to_string().yellow(), self.tracker_url.to_string().yellow()); // Import stats for all torrents in one request diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 174a92ef..d762a98f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -1,7 +1,9 @@ pub mod responses; use serde::{Deserialize, Serialize}; +use url::Url; +use crate::config::v1::tracker::ApiToken; use crate::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, @@ -28,10 +30,10 @@ pub struct Website { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Tracker { - pub url: String, + pub url: Url, pub mode: String, - pub api_url: String, - pub token: String, + pub api_url: Url, + pub token: ApiToken, pub token_valid_seconds: u64, } diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index e5978890..fe9aeab3 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -36,7 +36,7 @@ //! "tracker": { //! "url": "udp://localhost:6969", //! "mode": "Public", -//! "api_url": "http://localhost:1212", +//! "api_url": "http://localhost:1212/", //! "token": "MyAccessToken", //! "token_valid_seconds": 7257600 //! }, @@ -102,7 +102,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"Public","api_url":"http://localhost:1212","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"Public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! diff --git a/tests/common/client.rs b/tests/common/client.rs index 9312ac6f..687b5044 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -21,15 +21,26 @@ impl Client { // todo: forms in POST requests can be passed by reference. fn base_path() -> String { - "/v1".to_string() + "v1".to_string() + } + + /// Remove last '/' char in the address if present. + /// + /// For example: to . + fn base_url(bind_address: &str) -> String { + let mut url = bind_address.to_owned(); + if url.ends_with('/') { + url.pop(); + } + url } pub fn unauthenticated(bind_address: &str) -> Self { - Self::new(ConnectionInfo::anonymous(bind_address, &Self::base_path())) + Self::new(ConnectionInfo::anonymous(&Self::base_url(bind_address), &Self::base_path())) } pub fn authenticated(bind_address: &str, token: &str) -> Self { - Self::new(ConnectionInfo::new(bind_address, &Self::base_path(), token)) + Self::new(ConnectionInfo::new(&Self::base_url(bind_address), &Self::base_path(), token)) } pub fn new(connection_info: ConnectionInfo) -> Self { @@ -340,9 +351,13 @@ impl Http { } fn base_url(&self, path: &str) -> String { - format!( - "http://{}{}{path}", + let url = format!( + "http://{}/{}{path}", // DevSkim: ignore DS137138 &self.connection_info.bind_address, &self.connection_info.base_path - ) + ); + + println!("URL: {url}"); + + url } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 0eca1bb6..b72ca7d5 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -1,11 +1,13 @@ pub mod responses; use serde::{Deserialize, Serialize}; +use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; +use url::Url; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Settings { @@ -27,10 +29,10 @@ pub struct Website { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Tracker { - pub url: String, + pub url: Url, pub mode: String, - pub api_url: String, - pub token: String, + pub api_url: Url, + pub token: ApiToken, pub token_valid_seconds: u64, } diff --git a/tests/common/contexts/settings/responses.rs b/tests/common/contexts/settings/responses.rs index 096ef1f4..782a0703 100644 --- a/tests/common/contexts/settings/responses.rs +++ b/tests/common/contexts/settings/responses.rs @@ -1,4 +1,5 @@ use serde::Deserialize; +use url::Url; use super::Settings; @@ -15,7 +16,7 @@ pub struct PublicSettingsResponse { #[derive(Deserialize, PartialEq, Debug)] pub struct Public { pub website_name: String, - pub tracker_url: String, + pub tracker_url: Url, pub tracker_mode: String, pub email_on_signup: String, } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index c81bf70a..f78c7c1b 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,5 +1,6 @@ use std::env; +use torrust_index::config::v1::tracker::ApiToken; use torrust_index::web::api::Version; use url::Url; @@ -98,9 +99,12 @@ impl TestEnv { settings.database.connect_url = connect_url.to_string(); } - "***".clone_into(&mut settings.tracker.token); + settings.tracker.token = ApiToken::new("***"); + "***".clone_into(&mut settings.mail.password); + "***".clone_into(&mut settings.auth.secret_key); + Some(settings) } None => None, diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 03ef94fb..bc6f0369 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -172,7 +172,7 @@ mod for_guests { let torrent_details_response: TorrentDetailsResponse = serde_json::from_str(&response.body).unwrap(); - let tracker_url = env.server_settings().unwrap().tracker.url; + let tracker_url = env.server_settings().unwrap().tracker.url.to_string(); let encoded_tracker_url = urlencoding::encode(&tracker_url); let expected_torrent = TorrentDetails { @@ -194,9 +194,9 @@ mod for_guests { path: vec![test_torrent.file_info.files[0].clone()], // Using one file torrent for testing: content_size = first file size length: test_torrent.file_info.content_size, - md5sum: None, + md5sum: None, // DevSkim: ignore DS126858 }], - trackers: vec![tracker_url.clone()], + trackers: vec![tracker_url.clone().to_string()], magnet_link: format!( // cspell:disable-next-line "magnet:?xt=urn:btih:{}&dn={}&tr={}", diff --git a/tests/e2e/web/api/v1/contexts/torrent/steps.rs b/tests/e2e/web/api/v1/contexts/torrent/steps.rs index 60f37f9b..27b21c7d 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/steps.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/steps.rs @@ -28,7 +28,7 @@ pub async fn upload_torrent(uploader: &LoggedInUserData, torrent: &TorrentIndexI let res = serde_json::from_str::(&response.body); if res.is_err() { - println!("Error deserializing response: {res:?}"); + println!("Error deserializing response: {res:?}. Body: {0}", response.body); } TorrentListedInIndex::from(torrent.clone(), res.unwrap().data.torrent_id) From ec1d39a0699fec3a46c88831b0ceca70a3a265d9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 24 May 2024 08:50:39 +0100 Subject: [PATCH 188/309] refactor: [#599] extract types for config::v1::Settings --- src/bootstrap/logging.rs | 16 ++++++++++++---- src/config/v1/mod.rs | 19 ++++++++++++++++++- tests/environments/isolated.rs | 3 ++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index 8546720f..4fc3ead9 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -6,14 +6,15 @@ //! - `Info` //! - `Debug` //! - `Trace` -use std::str::FromStr; use std::sync::Once; use log::{info, LevelFilter}; +use crate::config::v1::LogLevel; + static INIT: Once = Once::new(); -pub fn setup(log_level: &Option) { +pub fn setup(log_level: &Option) { let level = config_level_or_default(log_level); if level == log::LevelFilter::Off { @@ -25,10 +26,17 @@ pub fn setup(log_level: &Option) { }); } -fn config_level_or_default(log_level: &Option) -> LevelFilter { +fn config_level_or_default(log_level: &Option) -> LevelFilter { match log_level { None => log::LevelFilter::Info, - Some(level) => LevelFilter::from_str(level).unwrap(), + Some(level) => match level { + LogLevel::Off => LevelFilter::Off, + LogLevel::Error => LevelFilter::Error, + LogLevel::Warn => LevelFilter::Warn, + LogLevel::Info => LevelFilter::Info, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Trace => LevelFilter::Trace, + }, } } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index fea3e2ae..f5e95bc1 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -26,7 +26,7 @@ use super::validator::{ValidationError, Validator}; pub struct Settings { /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, /// `Debug` and `Trace`. Default is `Info`. - pub log_level: Option, + pub log_level: Option, /// The website customizable values. pub website: Website, /// The tracker configuration. @@ -71,3 +71,20 @@ impl Validator for Settings { self.tracker.validate() } } + +#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 7a389930..d74ee48e 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,5 +1,6 @@ use tempfile::TempDir; use torrust_index::config; +use torrust_index::config::v1::LogLevel; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; use url::Url; @@ -72,7 +73,7 @@ impl Default for TestEnv { /// Provides a configuration with ephemeral data for testing. fn ephemeral(temp_dir: &TempDir) -> config::Settings { let mut configuration = config::Settings { - log_level: Some("off".to_owned()), // Change to `debug` for tests debugging + log_level: Some(LogLevel::Off), // Change to `debug` for tests debugging ..config::Settings::default() }; From 56a8c08a8c95585c476419705e9e80820ff4dad6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 24 May 2024 12:12:51 +0100 Subject: [PATCH 189/309] feat: [#603] add default values to configration --- src/config/v1/api.rs | 16 +++++- src/config/v1/auth.rs | 22 +++++++-- src/config/v1/database.rs | 9 +++- src/config/v1/image_cache.rs | 37 ++++++++++++-- src/config/v1/mail.rs | 51 +++++++++++++++++--- src/config/v1/mod.rs | 10 ++++ src/config/v1/net.rs | 23 +++++++-- src/config/v1/tracker.rs | 47 +++++++++++++----- src/config/v1/tracker_statistics_importer.rs | 16 +++++- src/config/v1/website.rs | 9 +++- 10 files changed, 205 insertions(+), 35 deletions(-) diff --git a/src/config/v1/api.rs b/src/config/v1/api.rs index 70f3ddab..4b4db9fd 100644 --- a/src/config/v1/api.rs +++ b/src/config/v1/api.rs @@ -4,16 +4,28 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Api { /// The default page size for torrent lists. + #[serde(default = "Api::default_default_torrent_page_size")] pub default_torrent_page_size: u8, /// The maximum page size for torrent lists. + #[serde(default = "Api::default_max_torrent_page_size")] pub max_torrent_page_size: u8, } impl Default for Api { fn default() -> Self { Self { - default_torrent_page_size: 10, - max_torrent_page_size: 30, + default_torrent_page_size: Api::default_default_torrent_page_size(), + max_torrent_page_size: Api::default_max_torrent_page_size(), } } } + +impl Api { + fn default_default_torrent_page_size() -> u8 { + 10 + } + + fn default_max_torrent_page_size() -> u8 { + 30 + } +} diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index b23c536d..6b7f0786 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -6,12 +6,16 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// Whether or not to require an email on signup. + #[serde(default = "EmailOnSignup::default")] pub email_on_signup: EmailOnSignup, /// The minimum password length. + #[serde(default = "Auth::default_min_password_length")] pub min_password_length: usize, /// The maximum password length. + #[serde(default = "Auth::default_max_password_length")] pub max_password_length: usize, /// The secret key used to sign JWT tokens. + #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, } @@ -19,9 +23,9 @@ impl Default for Auth { fn default() -> Self { Self { email_on_signup: EmailOnSignup::default(), - min_password_length: 6, - max_password_length: 64, - secret_key: SecretKey::new("MaxVerstappenWC2021"), + min_password_length: Self::default_min_password_length(), + max_password_length: Self::default_max_password_length(), + secret_key: Self::default_secret_key(), } } } @@ -30,6 +34,18 @@ impl Auth { pub fn override_secret_key(&mut self, secret_key: &str) { self.secret_key = SecretKey::new(secret_key); } + + fn default_min_password_length() -> usize { + 6 + } + + fn default_max_password_length() -> usize { + 64 + } + + fn default_secret_key() -> SecretKey { + SecretKey::new("MaxVerstappenWC2021") + } } /// Whether the email is required on signup or not. diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs index 5efa824a..7dfd5a31 100644 --- a/src/config/v1/database.rs +++ b/src/config/v1/database.rs @@ -8,13 +8,20 @@ pub struct Database { /// /// Sqlite: `sqlite://data.db?mode=rwc`. /// Mysql: `mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing`. + #[serde(default = "Database::default_connect_url")] pub connect_url: Url, } impl Default for Database { fn default() -> Self { Self { - connect_url: Url::parse("sqlite://data.db?mode=rwc").unwrap(), + connect_url: Self::default_connect_url(), } } } + +impl Database { + fn default_connect_url() -> Url { + Url::parse("sqlite://data.db?mode=rwc").unwrap() + } +} diff --git a/src/config/v1/image_cache.rs b/src/config/v1/image_cache.rs index 4f2990b0..88c5ebec 100644 --- a/src/config/v1/image_cache.rs +++ b/src/config/v1/image_cache.rs @@ -11,27 +11,54 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ImageCache { /// Maximum time in seconds to wait for downloading the image form the original source. + #[serde(default = "ImageCache::default_max_request_timeout_ms")] pub max_request_timeout_ms: u64, /// Cache size in bytes. + #[serde(default = "ImageCache::default_capacity")] pub capacity: usize, /// Maximum size in bytes for a single image. + #[serde(default = "ImageCache::default_entry_size_limit")] pub entry_size_limit: usize, /// Users have a cache quota per period. For example: 100MB per day. /// This is the period in seconds (1 day in seconds). + #[serde(default = "ImageCache::default_user_quota_period_seconds")] pub user_quota_period_seconds: u64, /// Users have a cache quota per period. For example: 100MB per day. /// This is the maximum size in bytes (100MB in bytes). + #[serde(default = "ImageCache::default_user_quota_bytes")] pub user_quota_bytes: usize, } impl Default for ImageCache { fn default() -> Self { Self { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, + max_request_timeout_ms: Self::default_max_request_timeout_ms(), + capacity: Self::default_capacity(), + entry_size_limit: Self::default_entry_size_limit(), + user_quota_period_seconds: Self::default_user_quota_period_seconds(), + user_quota_bytes: Self::default_user_quota_bytes(), } } } + +impl ImageCache { + fn default_max_request_timeout_ms() -> u64 { + 1000 + } + + fn default_capacity() -> usize { + 128_000_000 + } + + fn default_entry_size_limit() -> usize { + 4_000_000 + } + + fn default_user_quota_period_seconds() -> u64 { + 3600 + } + + fn default_user_quota_bytes() -> usize { + 64_000_000 + } +} diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 45ed6145..885a4383 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -5,31 +5,68 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Mail { /// Whether or not to enable email verification on signup. + #[serde(default = "Mail::default_email_verification_enabled")] pub email_verification_enabled: bool, /// The email address to send emails from. + #[serde(default = "Mail::default_from")] pub from: Mailbox, /// The email address to reply to. + #[serde(default = "Mail::default_reply_to")] pub reply_to: Mailbox, /// The username to use for SMTP authentication. + #[serde(default = "Mail::default_username")] pub username: String, /// The password to use for SMTP authentication. + #[serde(default = "Mail::default_password")] pub password: String, /// The SMTP server to use. + #[serde(default = "Mail::default_server")] pub server: String, /// The SMTP port to use. + #[serde(default = "Mail::default_port")] pub port: u16, } impl Default for Mail { fn default() -> Self { Self { - email_verification_enabled: false, - from: "example@email.com".parse().expect("valid mailbox"), - reply_to: "noreply@email.com".parse().expect("valid mailbox"), - username: String::default(), - password: String::default(), - server: String::default(), - port: 25, + email_verification_enabled: Self::default_email_verification_enabled(), + from: Self::default_from(), + reply_to: Self::default_reply_to(), + username: Self::default_username(), + password: Self::default_password(), + server: Self::default_server(), + port: Self::default_port(), } } } + +impl Mail { + fn default_email_verification_enabled() -> bool { + false + } + + fn default_from() -> Mailbox { + "example@email.com".parse().expect("valid mailbox") + } + + fn default_reply_to() -> Mailbox { + "noreply@email.com".parse().expect("valid mailbox") + } + + fn default_username() -> String { + String::default() + } + + fn default_password() -> String { + String::default() + } + + fn default_server() -> String { + String::default() + } + + fn default_port() -> u16 { + 25 + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index f5e95bc1..fa51a8f5 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -26,24 +26,34 @@ use super::validator::{ValidationError, Validator}; pub struct Settings { /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, /// `Debug` and `Trace`. Default is `Info`. + #[serde(default)] pub log_level: Option, /// The website customizable values. + #[serde(default)] pub website: Website, /// The tracker configuration. + #[serde(default)] pub tracker: Tracker, /// The network configuration. + #[serde(default)] pub net: Network, /// The authentication configuration. + #[serde(default)] pub auth: Auth, /// The database configuration. + #[serde(default)] pub database: Database, /// The SMTP configuration. + #[serde(default)] pub mail: Mail, /// The image proxy cache configuration. + #[serde(default)] pub image_cache: ImageCache, /// The API configuration. + #[serde(default)] pub api: Api, /// The tracker statistics importer job configuration. + #[serde(default)] pub tracker_statistics_importer: TrackerStatisticsImporter, } diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index 28b101fc..4c1552a2 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -12,20 +12,37 @@ use crate::config::Tsl; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Network { /// The port to listen on. Default to `3001`. + #[serde(default = "Network::default_port")] pub port: u16, /// The base URL for the API. For example: `http://localhost`. /// If not set, the base URL will be inferred from the request. + #[serde(default = "Network::default_base_url")] pub base_url: Option, /// TSL configuration. + #[serde(default = "Network::default_tsl")] pub tsl: Option, } impl Default for Network { fn default() -> Self { Self { - port: 3001, - base_url: None, - tsl: None, + port: Self::default_port(), + base_url: Self::default_base_url(), + tsl: Self::default_tsl(), } } } + +impl Network { + fn default_port() -> u16 { + 3001 + } + + fn default_base_url() -> Option { + None + } + + fn default_tsl() -> Option { + None + } +} diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index bc8a2eb0..67c31bfe 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -10,25 +10,24 @@ use crate::config::TrackerMode; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Tracker { /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. + #[serde(default = "Tracker::default_url")] pub url: Url, /// The mode of the tracker. For example: `Public`. /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. + #[serde(default = "Tracker::default_mode")] pub mode: TrackerMode, /// The url of the tracker API. For example: `http://localhost:1212/`. + #[serde(default = "Tracker::default_api_url")] pub api_url: Url, /// The token used to authenticate with the tracker API. + #[serde(default = "Tracker::default_token")] pub token: ApiToken, /// The amount of seconds the tracker API token is valid. + #[serde(default = "Tracker::default_token_valid_seconds")] pub token_valid_seconds: u64, } -impl Tracker { - pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) { - self.token = tracker_api_token.clone(); - } -} - impl Validator for Tracker { fn validate(&self) -> Result<(), ValidationError> { let tracker_mode = self.mode.clone(); @@ -45,15 +44,41 @@ impl Validator for Tracker { impl Default for Tracker { fn default() -> Self { Self { - url: Url::parse("udp://localhost:6969").unwrap(), - mode: TrackerMode::default(), - api_url: Url::parse("http://localhost:1212/").unwrap(), - token: ApiToken::new("MyAccessToken"), - token_valid_seconds: 7_257_600, + url: Self::default_url(), + mode: Self::default_mode(), + api_url: Self::default_api_url(), + token: Self::default_token(), + token_valid_seconds: Self::default_token_valid_seconds(), } } } +impl Tracker { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) { + self.token = tracker_api_token.clone(); + } + + fn default_url() -> Url { + Url::parse("udp://localhost:6969").unwrap() + } + + fn default_mode() -> TrackerMode { + TrackerMode::default() + } + + fn default_api_url() -> Url { + Url::parse("http://localhost:1212/").unwrap() + } + + fn default_token() -> ApiToken { + ApiToken::new("MyAccessToken") + } + + fn default_token_valid_seconds() -> u64 { + 7_257_600 + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ApiToken(String); diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v1/tracker_statistics_importer.rs index 9c43a4a4..531629b1 100644 --- a/src/config/v1/tracker_statistics_importer.rs +++ b/src/config/v1/tracker_statistics_importer.rs @@ -4,16 +4,28 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TrackerStatisticsImporter { /// The interval in seconds to get statistics from the tracker. + #[serde(default = "TrackerStatisticsImporter::default_torrent_info_update_interval")] pub torrent_info_update_interval: u64, /// The port the Importer API is listening on. Default to `3002`. + #[serde(default = "TrackerStatisticsImporter::default_port")] pub port: u16, } impl Default for TrackerStatisticsImporter { fn default() -> Self { Self { - torrent_info_update_interval: 3600, - port: 3002, + torrent_info_update_interval: Self::default_torrent_info_update_interval(), + port: Self::default_port(), } } } + +impl TrackerStatisticsImporter { + fn default_torrent_info_update_interval() -> u64 { + 3600 + } + + fn default_port() -> u16 { + 3002 + } +} diff --git a/src/config/v1/website.rs b/src/config/v1/website.rs index a7a6fbef..489ce265 100644 --- a/src/config/v1/website.rs +++ b/src/config/v1/website.rs @@ -4,13 +4,20 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Website { /// The name of the website. + #[serde(default = "Website::default_name")] pub name: String, } impl Default for Website { fn default() -> Self { Self { - name: "Torrust".to_string(), + name: Self::default_name(), } } } + +impl Website { + fn default_name() -> String { + "Torrust".to_string() + } +} From 5725d7dc4ca63541f0f553a6d253c08dda0efbd7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 24 May 2024 12:25:05 +0100 Subject: [PATCH 190/309] refactor: [#603] remove default values from config toml templates Not it's possible to specify only values when you wan to overrride default values. --- .../default/config/index.container.mysql.toml | 41 +-------------- .../config/index.container.sqlite3.toml | 49 ++---------------- .../config/index.development.sqlite3.toml | 50 ++----------------- .../config/index.e2e.container.mysql.toml | 39 +-------------- .../config/index.e2e.container.sqlite3.toml | 39 +-------------- 5 files changed, 14 insertions(+), 204 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 7bd934a4..3ca737b2 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -4,48 +4,9 @@ log_level = "info" -[website] -name = "Torrust" - -[tracker] -url = "udp://tracker:6969" -mode = "Public" -api_url = "http://tracker:1212" -token = "MyAccessToken" -token_valid_seconds = 7257600 - -[net] -port = 3001 - -[auth] -email_on_signup = "Optional" -min_password_length = 6 -max_password_length = 64 -secret_key = "MaxVerstappenWC2021" - [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" [mail] -email_verification_enabled = false -from = "example@email.com" -reply_to = "noreply@email.com" -username = "" -password = "" -server = "mailcatcher" port = 1025 - -[image_cache] -max_request_timeout_ms = 1000 -capacity = 128000000 -entry_size_limit = 4000000 -user_quota_period_seconds = 3600 -user_quota_bytes = 64000000 - -[api] -default_torrent_page_size = 10 -max_torrent_page_size = 30 - -[tracker_statistics_importer] -torrent_info_update_interval = 3600 -port = 3002 \ No newline at end of file +server = "mailcatcher" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 77d06a93..95e0c600 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,51 +1,12 @@ -log_level = "info" - -[website] -name = "Torrust" - -# Please override the tracker token setting the -# `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# environmental variable! +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` -[tracker] -url = "udp://tracker:6969" -mode = "Public" -api_url = "http://tracker:1212" -token = "MyAccessToken" -token_valid_seconds = 7257600 - -[net] -port = 3001 - -[auth] -email_on_signup = "Optional" -min_password_length = 6 -max_password_length = 64 -secret_key = "MaxVerstappenWC2021" +log_level = "info" [database] connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" [mail] -email_verification_enabled = false -from = "example@email.com" -reply_to = "noreply@email.com" -username = "" -password = "" -server = "mailcatcher" port = 1025 - -[image_cache] -max_request_timeout_ms = 1000 -capacity = 128000000 -entry_size_limit = 4000000 -user_quota_period_seconds = 3600 -user_quota_bytes = 64000000 - -[api] -default_torrent_page_size = 10 -max_torrent_page_size = 30 - -[tracker_statistics_importer] -torrent_info_update_interval = 3600 -port = 3002 \ No newline at end of file +server = "mailcatcher" diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index c0b04253..8cafd3ea 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,52 +1,10 @@ -log_level = "info" - -[website] -name = "Torrust" - -[tracker] -api_url = "http://localhost:1212/" -mode = "Public" -token = "MyAccessToken" -token_valid_seconds = 7257600 -url = "udp://localhost:6969" +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` -[net] -port = 3001 +log_level = "info" # Uncomment if you want to enable TSL for development #[net.tsl] #ssl_cert_path = "./storage/index/lib/tls/localhost.crt" #ssl_key_path = "./storage/index/lib/tls/localhost.key" - -[auth] -email_on_signup = "Optional" -max_password_length = 64 -min_password_length = 6 -secret_key = "MaxVerstappenWC2021" - -[database] -connect_url = "sqlite://data.db?mode=rwc" - -[mail] -email_verification_enabled = false -from = "example@email.com" -password = "" -port = 25 -reply_to = "noreply@email.com" -server = "" -username = "" - -[image_cache] -capacity = 128000000 -entry_size_limit = 4000000 -max_request_timeout_ms = 1000 -user_quota_bytes = 64000000 -user_quota_period_seconds = 3600 - -[api] -default_torrent_page_size = 10 -max_torrent_page_size = 30 - -[tracker_statistics_importer] -port = 3002 -torrent_info_update_interval = 3600 diff --git a/share/default/config/index.e2e.container.mysql.toml b/share/default/config/index.e2e.container.mysql.toml index beabe832..037bdcff 100644 --- a/share/default/config/index.e2e.container.mysql.toml +++ b/share/default/config/index.e2e.container.mysql.toml @@ -4,48 +4,13 @@ log_level = "info" -[website] -name = "Torrust" - [tracker] -url = "udp://tracker:6969" -mode = "Public" api_url = "http://tracker:1212" -token = "MyAccessToken" -token_valid_seconds = 7257600 - -[net] -port = 3001 - -[auth] -email_on_signup = "Optional" -min_password_length = 6 -max_password_length = 64 -secret_key = "MaxVerstappenWC2021" +url = "udp://tracker:6969" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" [mail] -email_verification_enabled = false -from = "example@email.com" -reply_to = "noreply@email.com" -username = "" -password = "" -server = "mailcatcher" port = 1025 - -[image_cache] -max_request_timeout_ms = 1000 -capacity = 128000000 -entry_size_limit = 4000000 -user_quota_period_seconds = 3600 -user_quota_bytes = 64000000 - -[api] -default_torrent_page_size = 10 -max_torrent_page_size = 30 - -[tracker_statistics_importer] -torrent_info_update_interval = 3600 -port = 3002 \ No newline at end of file +server = "mailcatcher" diff --git a/share/default/config/index.e2e.container.sqlite3.toml b/share/default/config/index.e2e.container.sqlite3.toml index e3409ac4..ca6d1944 100644 --- a/share/default/config/index.e2e.container.sqlite3.toml +++ b/share/default/config/index.e2e.container.sqlite3.toml @@ -4,48 +4,13 @@ log_level = "info" -[website] -name = "Torrust" - [tracker] -url = "udp://tracker:6969" -mode = "Public" api_url = "http://tracker:1212" -token = "MyAccessToken" -token_valid_seconds = 7257600 - -[net] -port = 3001 - -[auth] -email_on_signup = "Optional" -min_password_length = 6 -max_password_length = 64 -secret_key = "MaxVerstappenWC2021" +url = "udp://tracker:6969" [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" [mail] -email_verification_enabled = false -from = "example@email.com" -reply_to = "noreply@email.com" -username = "" -password = "" -server = "mailcatcher" port = 1025 - -[image_cache] -max_request_timeout_ms = 1000 -capacity = 128000000 -entry_size_limit = 4000000 -user_quota_period_seconds = 3600 -user_quota_bytes = 64000000 - -[api] -default_torrent_page_size = 10 -max_torrent_page_size = 30 - -[tracker_statistics_importer] -torrent_info_update_interval = 3600 -port = 3002 \ No newline at end of file +server = "mailcatcher" From 76a1837761abb624396a145621676f1ff59c2246 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 27 May 2024 09:36:28 +0100 Subject: [PATCH 191/309] feat: [#605] remove deprecated env vars You can now use the Figmenat conventions to override config values: - TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY --- src/config/mod.rs | 75 +------------------------------------------- src/config/v1/mod.rs | 8 ----- 2 files changed, 1 insertion(+), 82 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 9a2bc633..ee4c88cb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -15,7 +15,6 @@ use tokio::sync::RwLock; use torrust_index_located_error::LocatedError; use url::Url; -use self::v1::tracker::ApiToken; use crate::web::api::server::DynError; pub type Settings = v1::Settings; @@ -43,21 +42,11 @@ pub const ENV_VAR_CONFIG_TOML: &str = "TORRUST_INDEX_CONFIG_TOML"; /// The `index.toml` file location. pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_INDEX_CONFIG_TOML_PATH"; -/// Token needed to communicate with the Torrust Tracker. -/// Deprecated. Use `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN`. -pub const ENV_VAR_TRACKER_API_ADMIN_TOKEN: &str = "TORRUST_INDEX_TRACKER_API_TOKEN"; - -/// Secret key used to encrypt and decrypt -/// Deprecated. Use `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY`. -pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY"; - /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { config_toml: Option, config_toml_path: String, - tracker_api_token: Option, - auth_secret_key: Option, } impl Info { @@ -71,8 +60,6 @@ impl Info { pub fn new(default_config_toml_path: String) -> Result { let env_var_config_toml = ENV_VAR_CONFIG_TOML.to_string(); let env_var_config_toml_path = ENV_VAR_CONFIG_TOML_PATH.to_string(); - let env_var_tracker_api_admin_token = ENV_VAR_TRACKER_API_ADMIN_TOKEN.to_string(); - let env_var_auth_secret_key = ENV_VAR_AUTH_SECRET_KEY.to_string(); let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) { println!("Loading configuration from environment variable {config_toml} ..."); @@ -89,17 +76,9 @@ impl Info { default_config_toml_path }; - let tracker_api_token = env::var(env_var_tracker_api_admin_token) - .ok() - .map(|token| ApiToken::new(&token)); - - let auth_secret_key = env::var(env_var_auth_secret_key).ok(); - Ok(Self { config_toml, config_toml_path, - tracker_api_token, - auth_secret_key, }) } } @@ -278,19 +257,7 @@ impl Configuration { .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) }; - //println!("figment: {figment:#?}"); - - let mut settings: Settings = figment.extract()?; - - if let Some(ref token) = info.tracker_api_token { - // todo: remove when using only Figment env var name: `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` - settings.override_tracker_api_token(token); - }; - - if let Some(ref secret_key) = info.auth_secret_key { - // todo: remove when using only Figment env var name: `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - settings.override_auth_secret_key(secret_key); - }; + let settings: Settings = figment.extract()?; Ok(settings) } @@ -459,8 +426,6 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), - tracker_api_token: None, - auth_secret_key: None, }; let settings = Configuration::load_settings(&info).expect("Failed to load configuration from info"); @@ -471,23 +436,6 @@ mod tests { }); } - #[tokio::test] - async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file_deprecated() { - let info = Info { - config_toml: Some(default_config_toml()), - config_toml_path: String::new(), - tracker_api_token: Some(ApiToken::new("OVERRIDDEN API TOKEN")), - auth_secret_key: None, - }; - - let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); - - assert_eq!( - configuration.get_all().await.tracker.token, - ApiToken::new("OVERRIDDEN API TOKEN") - ); - } - #[tokio::test] async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() { figment::Jail::expect_with(|jail| { @@ -499,8 +447,6 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), - tracker_api_token: None, - auth_secret_key: None, }; let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); @@ -511,23 +457,6 @@ mod tests { }); } - #[tokio::test] - async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file_deprecated() { - let info = Info { - config_toml: Some(default_config_toml()), - config_toml_path: String::new(), - tracker_api_token: None, - auth_secret_key: Some("OVERRIDDEN AUTH SECRET KEY".to_string()), - }; - - let configuration = Configuration::load(&info).expect("Failed to load configuration from info"); - - assert_eq!( - configuration.get_all().await.auth.secret_key, - SecretKey::new("OVERRIDDEN AUTH SECRET KEY") - ); - } - #[tokio::test] async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() { figment::Jail::expect_with(|jail| { @@ -539,8 +468,6 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), - tracker_api_token: None, - auth_secret_key: None, }; let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index fa51a8f5..d788494b 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -58,14 +58,6 @@ pub struct Settings { } impl Settings { - pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) { - self.tracker.override_tracker_api_token(tracker_api_token); - } - - pub fn override_auth_secret_key(&mut self, auth_secret_key: &str) { - self.auth.override_secret_key(auth_secret_key); - } - pub fn remove_secrets(&mut self) { self.tracker.token = ApiToken::new("***"); if let Some(_password) = self.database.connect_url.password() { From b66e65c8f73925d165aeb40380aabadfcde83822 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 27 May 2024 21:41:56 +0200 Subject: [PATCH 192/309] chore: update dependencies --- Cargo.lock | 99 +++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3724c563..334eb0ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -515,7 +515,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -666,7 +666,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -677,7 +677,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -862,12 +862,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "finl_unicode" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" - [[package]] name = "flate2" version = "1.0.30" @@ -1003,7 +997,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1283,9 +1277,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" dependencies = [ "bytes", "futures-channel", @@ -1490,7 +1484,7 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.7", + "rustls 0.23.8", "rustls-pemfile", "serde", "serde_json", @@ -1647,11 +1641,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1784,7 +1777,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1807,9 +1800,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1877,7 +1870,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1936,7 +1929,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1973,7 +1966,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2042,9 +2035,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -2057,7 +2050,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", "version_check", "yansi", ] @@ -2322,9 +2315,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.7" +version = "0.23.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b" +checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" dependencies = [ "log", "once_cell", @@ -2474,9 +2467,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -2502,13 +2495,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2580,7 +2573,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2943,13 +2936,13 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] @@ -2986,9 +2979,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.65" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -3100,7 +3093,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3189,7 +3182,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3218,7 +3211,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.7", + "rustls 0.23.8", "rustls-pki-types", "tokio", ] @@ -3433,7 +3426,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3576,6 +3569,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "unicode-script" version = "0.5.6" @@ -3730,7 +3729,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -3764,7 +3763,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4071,14 +4070,14 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zstd" From 716a4753bc499e51c3a2a840879f8f067f78c4bb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 3 Jun 2024 09:20:28 +0100 Subject: [PATCH 193/309] chore(deps): update dependencies ```output $ cargo update Updating crates.io index Locking 12 packages to latest compatible versions Updating addr2line v0.21.0 -> v0.22.0 (latest: v0.23.0) Updating async-compression v0.4.10 -> v0.4.11 Updating backtrace v0.3.71 -> v0.3.72 Updating gimli v0.28.1 -> v0.29.0 (latest: v0.30.0) Updating globwalk v0.8.1 -> v0.9.1 Updating hyper-util v0.1.4 -> v0.1.5 Updating object v0.32.2 -> v0.35.0 (latest: v0.36.0) Updating proc-macro2 v1.0.84 -> v1.0.85 Updating tera v1.19.1 -> v1.20.0 Updating tokio v1.37.0 -> v1.38.0 Updating tokio-macros v2.2.0 -> v2.3.0 Updating winnow v0.6.8 -> v0.6.9 ``` --- Cargo.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 334eb0ca..826366de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -168,9 +168,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "brotli", "flate2", @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -1055,9 +1055,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "globset" @@ -1074,11 +1074,11 @@ dependencies = [ [[package]] name = "globwalk" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "ignore", "walkdir", ] @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-channel", @@ -1741,9 +1741,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -2035,9 +2035,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -3035,9 +3035,9 @@ dependencies = [ [[package]] name = "tera" -version = "1.19.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "globwalk", "lazy_static", @@ -3158,9 +3158,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -3176,9 +3176,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", @@ -4006,9 +4006,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] From a2ce9902ba87309b3887f4062382bd0024373124 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 29 May 2024 22:56:09 +0200 Subject: [PATCH 194/309] feat: [#448] added mockall dependency --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 73 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 826366de..ddb17296 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -752,6 +752,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "either" version = "1.12.0" @@ -930,6 +936,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "futures" version = "0.3.30" @@ -1622,6 +1634,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "multer" version = "3.1.0" @@ -2033,6 +2072,32 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.85" @@ -3049,6 +3114,12 @@ dependencies = [ "unic-segment", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "text-colorizer" version = "1.0.0" @@ -3304,6 +3375,7 @@ dependencies = [ "lazy_static", "lettre", "log", + "mockall", "pbkdf2", "pin-project-lite", "rand", diff --git a/Cargo.toml b/Cargo.toml index 461ca72a..64096caf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ hyper = "1" hyper-util = { version = "0.1.3", features = ["http1", "http2", "tokio"] } indexmap = "2" jsonwebtoken = "9" +mockall = "0.12.1" lazy_static = "1.4.0" lettre = { version = "0", features = [ "builder", From b1d5536ab8c98bf5e6f255ca3c0a56f9b31a39ca Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 31 May 2024 19:02:48 +0200 Subject: [PATCH 195/309] feat: [#448] new user repository trait --- src/services/user.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/services/user.rs b/src/services/user.rs index 05f9ecfc..dc397c17 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -3,8 +3,11 @@ use std::sync::Arc; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher}; +use async_trait::async_trait; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use log::{debug, info}; +#[cfg(test)] +use mockall::automock; use pbkdf2::password_hash::rand_core::OsRng; use crate::config::{Configuration, EmailOnSignup}; @@ -26,7 +29,7 @@ fn no_email() -> String { pub struct RegistrationService { configuration: Arc, mailer: Arc, - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, } @@ -35,7 +38,7 @@ impl RegistrationService { pub fn new( configuration: Arc, mailer: Arc, - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, ) -> Self { Self { @@ -184,7 +187,7 @@ impl RegistrationService { } pub struct BanService { - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, banned_user_list: Arc, } @@ -192,7 +195,7 @@ pub struct BanService { impl BanService { #[must_use] pub fn new( - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, banned_user_list: Arc, ) -> Self { @@ -233,6 +236,15 @@ impl BanService { } } +#[cfg_attr(test, automock)] +#[async_trait] +pub trait Repository: Sync + Send { + async fn get_compact(&self, user_id: &UserId) -> Result; + async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error>; + async fn delete(&self, user_id: &UserId) -> Result<(), Error>; + async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result; +} + pub struct DbUserRepository { database: Arc>, } @@ -242,13 +254,16 @@ impl DbUserRepository { pub fn new(database: Arc>) -> Self { Self { database } } +} +#[async_trait] +impl Repository for DbUserRepository { /// It returns the compact user. /// /// # Errors /// /// It returns an error if there is a database error. - pub async fn get_compact(&self, user_id: &UserId) -> Result { + async fn get_compact(&self, user_id: &UserId) -> Result { // todo: persistence layer should have its own errors instead of // returning a `ServiceError`. self.database @@ -262,7 +277,7 @@ impl DbUserRepository { /// # Errors /// /// It returns an error if there is a database error. - pub async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error> { + async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error> { self.database.grant_admin_role(*user_id).await } @@ -271,7 +286,7 @@ impl DbUserRepository { /// # Errors /// /// It returns an error if there is a database error. - pub async fn delete(&self, user_id: &UserId) -> Result<(), Error> { + async fn delete(&self, user_id: &UserId) -> Result<(), Error> { self.database.delete_user(*user_id).await } @@ -280,7 +295,7 @@ impl DbUserRepository { /// # Errors /// /// It returns an error if there is a database error. - pub async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result { + async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result { self.database.insert_user_and_get_id(username, email, password_hash).await } } From 9ebd7b57c2904b48808d82359e07629f12a657a3 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 31 May 2024 19:13:35 +0200 Subject: [PATCH 196/309] feat: [#448] new authorization service --- src/app.rs | 16 ++- src/common.rs | 7 +- src/services/authorization.rs | 199 ++++++++++++++++++++++++++++++++++ src/services/mod.rs | 1 + 4 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 src/services/authorization.rs diff --git a/src/app.rs b/src/app.rs index 761fbb3a..2272dd4f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,8 +16,8 @@ use crate::services::torrent::{ DbCanonicalInfoHashGroupRepository, DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository, }; -use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository}; -use crate::services::{proxy, settings, torrent}; +use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository, Repository}; +use crate::services::{authorization, proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::web::api::server::signals::Halted; use crate::web::api::server::v1::auth::Authentication; @@ -74,7 +74,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running // Repositories let category_repository = Arc::new(DbCategoryRepository::new(database.clone())); let tag_repository = Arc::new(DbTagRepository::new(database.clone())); - let user_repository = Arc::new(DbUserRepository::new(database.clone())); + let user_repository: Arc> = Arc::new(Box::new(DbUserRepository::new(database.clone()))); let user_authentication_repository = Arc::new(DbUserAuthenticationRepository::new(database.clone())); let user_profile_repository = Arc::new(DbUserProfileRepository::new(database.clone())); let torrent_repository = Arc::new(DbTorrentRepository::new(database.clone())); @@ -87,15 +87,19 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let banned_user_list = Arc::new(DbBannedUserList::new(database.clone())); // Services + let authorization_service = Arc::new(authorization::Service::new(user_repository.clone())); let tracker_service = Arc::new(tracker::service::Service::new(configuration.clone(), database.clone()).await); let tracker_statistics_importer = Arc::new(StatisticsImporter::new(configuration.clone(), tracker_service.clone(), database.clone()).await); let mailer_service = Arc::new(mailer::Service::new(configuration.clone()).await); let image_cache_service: Arc = Arc::new(ImageCacheService::new(configuration.clone()).await); - let category_service = Arc::new(category::Service::new(category_repository.clone(), user_repository.clone())); - let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), user_repository.clone())); + let category_service = Arc::new(category::Service::new( + category_repository.clone(), + authorization_service.clone(), + )); + let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), authorization_service.clone())); let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone())); - let settings_service = Arc::new(settings::Service::new(configuration.clone(), user_repository.clone())); + let settings_service = Arc::new(settings::Service::new(configuration.clone(), authorization_service.clone())); let torrent_index = Arc::new(torrent::Index::new( configuration.clone(), tracker_statistics_importer.clone(), diff --git a/src/common.rs b/src/common.rs index 755a775b..7e08e125 100644 --- a/src/common.rs +++ b/src/common.rs @@ -10,11 +10,12 @@ use crate::services::torrent::{ DbCanonicalInfoHashGroupRepository, DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository, }; -use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository}; +use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, Repository}; use crate::services::{proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::web::api::server::v1::auth::Authentication; use crate::{mailer, tracker}; + pub type Username = String; pub struct AppData { @@ -30,7 +31,7 @@ pub struct AppData { // Repositories pub category_repository: Arc, pub tag_repository: Arc, - pub user_repository: Arc, + pub user_repository: Arc>, pub user_authentication_repository: Arc, pub user_profile_repository: Arc, pub torrent_repository: Arc, @@ -66,7 +67,7 @@ impl AppData { // Repositories category_repository: Arc, tag_repository: Arc, - user_repository: Arc, + user_repository: Arc>, user_authentication_repository: Arc, user_profile_repository: Arc, torrent_repository: Arc, diff --git a/src/services/authorization.rs b/src/services/authorization.rs new file mode 100644 index 00000000..f49c0716 --- /dev/null +++ b/src/services/authorization.rs @@ -0,0 +1,199 @@ +//! Authorization service. +use std::sync::Arc; + +use super::user::Repository; +use crate::errors::ServiceError; +use crate::models::user::{UserCompact, UserId}; + +pub enum ACTION { + AddCategory, + DeleteCategory, + GetSettings, + GetSettingsSecret, + AddTag, + DeleteTag, +} + +pub struct Service { + user_repository: Arc>, +} + +impl Service { + #[must_use] + pub fn new(user_repository: Arc>) -> Self { + Self { user_repository } + } + + /// # Errors + /// + /// Will return an error if: + /// + /// - There is not any user with the provided `UserId` (when the user id is some). + /// - The user is not authorized to perform the action. + pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> Result<(), ServiceError> { + match action { + ACTION::AddCategory + | ACTION::DeleteCategory + | ACTION::GetSettings + | ACTION::GetSettingsSecret + | ACTION::AddTag + | ACTION::DeleteTag => match maybe_user_id { + Some(user_id) => { + let user = self.get_user(user_id).await?; + + if !user.administrator { + return Err(ServiceError::Unauthorized); + } + + Ok(()) + } + None => Err(ServiceError::Unauthorized), + }, + } + } + + async fn get_user(&self, user_id: UserId) -> Result { + self.user_repository.get_compact(&user_id).await + } +} +#[allow(unused_imports)] +#[cfg(test)] +mod test { + use std::str::FromStr; + use std::sync::Arc; + + use mockall::predicate; + + use crate::databases::database; + use crate::errors::ServiceError; + use crate::models::user::{User, UserCompact}; + use crate::services::authorization::{Service, ACTION}; + use crate::services::user::{MockRepository, Repository}; + use crate::web::api::client::v1::random::string; + + #[tokio::test] + async fn a_guest_user_should_not_be_able_to_add_categories() { + let test_user_id = 1; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(|_| Err(ServiceError::UserNotFound)); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!( + service.authorize(ACTION::AddCategory, Some(test_user_id)).await, + Err(ServiceError::UserNotFound) + ); + } + + #[tokio::test] + async fn a_registered_user_should_not_be_able_to_add_categories() { + let test_user_id = 2; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(move |_| { + Ok(UserCompact { + user_id: test_user_id, + username: "non_admin_user".to_string(), + administrator: false, + }) + }); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!( + service.authorize(ACTION::AddCategory, Some(test_user_id)).await, + Err(ServiceError::Unauthorized) + ); + } + + #[tokio::test] + async fn an_admin_user_should_be_able_to_add_categories() { + let test_user_id = 3; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(move |_| { + Ok(UserCompact { + user_id: test_user_id, + username: "admin_user".to_string(), + administrator: true, + }) + }); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!(service.authorize(ACTION::AddCategory, Some(test_user_id)).await, Ok(())); + } + + #[tokio::test] + async fn a_guest_user_should_not_be_able_to_delete_categories() { + let test_user_id = 4; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(|_| Err(ServiceError::UserNotFound)); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!( + service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, + Err(ServiceError::UserNotFound) + ); + } + + #[tokio::test] + async fn a_registered_user_should_not_be_able_to_delete_categories() { + let test_user_id = 5; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(move |_| { + Ok(UserCompact { + user_id: test_user_id, + username: "non_admin_user".to_string(), + administrator: false, + }) + }); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!( + service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, + Err(ServiceError::Unauthorized) + ); + } + + #[tokio::test] + async fn an_admin_user_should_be_able_to_delete_categories() { + let test_user_id = 6; + + let mut mock_repository = MockRepository::new(); + mock_repository + .expect_get_compact() + .with(predicate::eq(test_user_id)) + .times(1) + .returning(move |_| { + Ok(UserCompact { + user_id: test_user_id, + username: "admin_user".to_string(), + administrator: true, + }) + }); + + let service = Service::new(Arc::new(Box::new(mock_repository))); + assert_eq!(service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, Ok(())); + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index b2431aec..567c35a9 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,6 +1,7 @@ //! App services. pub mod about; pub mod authentication; +pub mod authorization; pub mod category; pub mod hasher; pub mod proxy; From 478e2349f159da7d08b60382fd80a3f236067637 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 3 Jun 2024 18:56:16 +0200 Subject: [PATCH 197/309] feat: [#448] new authorization service implemented in the other services --- src/services/authentication.rs | 7 ++++--- src/services/category.rs | 28 ++++++++++------------------ src/services/proxy.rs | 6 +++--- src/services/settings.rs | 28 ++++++++++------------------ src/services/tag.rs | 26 ++++++++------------------ src/services/torrent.rs | 6 +++--- 6 files changed, 38 insertions(+), 63 deletions(-) diff --git a/src/services/authentication.rs b/src/services/authentication.rs index e04342a4..170cac82 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -5,17 +5,18 @@ use argon2::{Argon2, PasswordHash, PasswordVerifier}; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; use pbkdf2::Pbkdf2; -use super::user::{DbUserProfileRepository, DbUserRepository}; +use super::user::DbUserProfileRepository; use crate::config::Configuration; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::models::user::{UserAuthentication, UserClaims, UserCompact, UserId}; +use crate::services::user::Repository; use crate::utils::clock; pub struct Service { configuration: Arc, json_web_token: Arc, - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, user_authentication_repository: Arc, } @@ -24,7 +25,7 @@ impl Service { pub fn new( configuration: Arc, json_web_token: Arc, - user_repository: Arc, + user_repository: Arc>, user_profile_repository: Arc, user_authentication_repository: Arc, ) -> Self { diff --git a/src/services/category.rs b/src/services/category.rs index ec3e5ca2..b5b4523f 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -1,7 +1,7 @@ //! Category service. use std::sync::Arc; -use super::user::DbUserRepository; +use super::authorization::{self, ACTION}; use crate::databases::database::{Category, Database, Error as DatabaseError}; use crate::errors::ServiceError; use crate::models::category::CategoryId; @@ -9,15 +9,15 @@ use crate::models::user::UserId; pub struct Service { category_repository: Arc, - user_repository: Arc, + authorization_service: Arc, } impl Service { #[must_use] - pub fn new(category_repository: Arc, user_repository: Arc) -> Service { + pub fn new(category_repository: Arc, authorization_service: Arc) -> Service { Service { category_repository, - user_repository, + authorization_service, } } @@ -32,13 +32,9 @@ impl Service { /// * The category already exists. /// * There is a database error. pub async fn add_category(&self, category_name: &str, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::AddCategory, Some(*user_id)) + .await?; let trimmed_name = category_name.trim(); @@ -70,13 +66,9 @@ impl Service { /// * The user does not have the required permissions. /// * There is a database error. pub async fn delete_category(&self, category_name: &str, user_id: &UserId) -> Result<(), ServiceError> { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::DeleteCategory, Some(*user_id)) + .await?; match self.category_repository.delete(category_name).await { Ok(()) => Ok(()), diff --git a/src/services/proxy.rs b/src/services/proxy.rs index 9ea5ef3d..7ac2a475 100644 --- a/src/services/proxy.rs +++ b/src/services/proxy.rs @@ -10,18 +10,18 @@ use std::sync::Arc; use bytes::Bytes; -use super::user::DbUserRepository; use crate::cache::image::manager::{Error, ImageCacheService}; use crate::models::user::UserId; +use crate::services::user::Repository; pub struct Service { image_cache_service: Arc, - user_repository: Arc, + user_repository: Arc>, } impl Service { #[must_use] - pub fn new(image_cache_service: Arc, user_repository: Arc) -> Self { + pub fn new(image_cache_service: Arc, user_repository: Arc>) -> Self { Self { image_cache_service, user_repository, diff --git a/src/services/settings.rs b/src/services/settings.rs index a4b0e92e..d587fb9b 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -1,22 +1,22 @@ //! Settings service. use std::sync::Arc; -use super::user::DbUserRepository; +use super::authorization::{self, ACTION}; use crate::config::{Configuration, ConfigurationPublic, Settings}; use crate::errors::ServiceError; use crate::models::user::UserId; pub struct Service { configuration: Arc, - user_repository: Arc, + authorization_service: Arc, } impl Service { #[must_use] - pub fn new(configuration: Arc, user_repository: Arc) -> Service { + pub fn new(configuration: Arc, authorization_service: Arc) -> Service { Service { configuration, - user_repository, + authorization_service, } } @@ -26,13 +26,9 @@ impl Service { /// /// It returns an error if the user does not have the required permissions. pub async fn get_all(&self, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::GetSettings, Some(*user_id)) + .await?; let torrust_index_configuration = self.configuration.get_all().await; @@ -45,13 +41,9 @@ impl Service { /// /// It returns an error if the user does not have the required permissions. pub async fn get_all_masking_secrets(&self, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::GetSettingsSecret, Some(*user_id)) + .await?; let mut torrust_index_configuration = self.configuration.get_all().await; diff --git a/src/services/tag.rs b/src/services/tag.rs index fcbf56c3..e8684b52 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -1,7 +1,7 @@ //! Tag service. use std::sync::Arc; -use super::user::DbUserRepository; +use super::authorization::{self, ACTION}; use crate::databases::database::{Database, Error as DatabaseError, Error}; use crate::errors::ServiceError; use crate::models::torrent_tag::{TagId, TorrentTag}; @@ -9,15 +9,15 @@ use crate::models::user::UserId; pub struct Service { tag_repository: Arc, - user_repository: Arc, + authorization_service: Arc, } impl Service { #[must_use] - pub fn new(tag_repository: Arc, user_repository: Arc) -> Service { + pub fn new(tag_repository: Arc, authorization_service: Arc) -> Service { Service { tag_repository, - user_repository, + authorization_service, } } @@ -30,13 +30,7 @@ impl Service { /// * The user does not have the required permissions. /// * There is a database error. pub async fn add_tag(&self, tag_name: &str, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service.authorize(ACTION::AddTag, Some(*user_id)).await?; let trimmed_name = tag_name.trim(); @@ -62,13 +56,9 @@ impl Service { /// * The user does not have the required permissions. /// * There is a database error. pub async fn delete_tag(&self, tag_id: &TagId, user_id: &UserId) -> Result<(), ServiceError> { - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - // todo: extract authorization service - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::DeleteTag, Some(*user_id)) + .await?; match self.tag_repository.delete(tag_id).await { Ok(()) => Ok(()), diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 606f8550..b98ef90b 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -6,7 +6,6 @@ use serde_derive::{Deserialize, Serialize}; use url::Url; use super::category::DbCategoryRepository; -use super::user::DbUserRepository; use crate::config::{Configuration, TrackerMode}; use crate::databases::database::{Database, Error, Sorting}; use crate::errors::ServiceError; @@ -17,6 +16,7 @@ use crate::models::torrent::{Metadata, TorrentId, TorrentListing}; use crate::models::torrent_file::{DbTorrent, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::user::UserId; +use crate::services::user::Repository; use crate::tracker::statistics_importer::StatisticsImporter; use crate::utils::parse_torrent::decode_and_validate_torrent_file; use crate::{tracker, AsCSV}; @@ -25,7 +25,7 @@ pub struct Index { configuration: Arc, tracker_statistics_importer: Arc, tracker_service: Arc, - user_repository: Arc, + user_repository: Arc>, category_repository: Arc, torrent_repository: Arc, torrent_info_hash_repository: Arc, @@ -81,7 +81,7 @@ impl Index { configuration: Arc, tracker_statistics_importer: Arc, tracker_service: Arc, - user_repository: Arc, + user_repository: Arc>, category_repository: Arc, torrent_repository: Arc, torrent_info_hash_repository: Arc, From df848dedf5196abc516e7fcc6c45860a9325bf5f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 3 Jun 2024 17:43:01 +0100 Subject: [PATCH 198/309] feat: [#618] scaffolding for changing pass endpoint This only adds the enpoint which alwyas returns a 404 response. --- src/web/api/server/v1/contexts/user/forms.rs | 9 ++++++++ .../api/server/v1/contexts/user/handlers.rs | 21 ++++++++++++++++++- src/web/api/server/v1/contexts/user/routes.rs | 8 ++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/web/api/server/v1/contexts/user/forms.rs b/src/web/api/server/v1/contexts/user/forms.rs index 6365c4da..fdcfd0cb 100644 --- a/src/web/api/server/v1/contexts/user/forms.rs +++ b/src/web/api/server/v1/contexts/user/forms.rs @@ -22,3 +22,12 @@ pub struct LoginForm { pub struct JsonWebToken { pub token: String, // // todo: rename to `encoded` or `value` } + +// Profile + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChangePasswordForm { + pub password: String, + pub new_password: String, + pub confirm_password: String, +} diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index 58326e9d..e6b36ae5 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -7,9 +7,10 @@ use axum::response::{IntoResponse, Response}; use axum::Json; use serde::Deserialize; -use super::forms::{JsonWebToken, LoginForm, RegistrationForm}; +use super::forms::{ChangePasswordForm, JsonWebToken, LoginForm, RegistrationForm}; use super::responses::{self}; use crate::common::AppData; +use crate::errors::ServiceError; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; @@ -123,6 +124,24 @@ pub async fn renew_token_handler( } } +/// It changes the user's password. +/// +/// # Errors +/// +/// It returns an error if: +/// +/// - The user account is not found. +#[allow(clippy::unused_async)] +pub async fn change_password_handler( + State(_app_data): State>, + extract::Json(change_password_form): extract::Json, +) -> Response { + + println!("change pass form: {change_password_form:#?}"); + + ServiceError::AccountNotFound.into_response() +} + /// It bans a user from the index. /// /// # Errors diff --git a/src/web/api/server/v1/contexts/user/routes.rs b/src/web/api/server/v1/contexts/user/routes.rs index 04ae9980..9daabc18 100644 --- a/src/web/api/server/v1/contexts/user/routes.rs +++ b/src/web/api/server/v1/contexts/user/routes.rs @@ -7,7 +7,8 @@ use axum::routing::{delete, get, post}; use axum::Router; use super::handlers::{ - ban_handler, email_verification_handler, login_handler, registration_handler, renew_token_handler, verify_token_handler, + ban_handler, change_password_handler, email_verification_handler, login_handler, registration_handler, renew_token_handler, + verify_token_handler, }; use crate::common::AppData; @@ -28,6 +29,11 @@ pub fn router(app_data: Arc) -> Router { .route("/login", post(login_handler).with_state(app_data.clone())) .route("/token/verify", post(verify_token_handler).with_state(app_data.clone())) .route("/token/renew", post(renew_token_handler).with_state(app_data.clone())) + // Profile + .route( + "/:user/change-password", + post(change_password_handler).with_state(app_data.clone()), + ) // User ban // code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user. .route("/ban/:user", delete(ban_handler).with_state(app_data)) From 44030f8eccc73b48fc3da898e22c24bbf0bacd8d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 3 Jun 2024 19:25:58 +0100 Subject: [PATCH 199/309] feat: [#618] allow users to change the password TODO: - Validate new password. - Extract duplicate code for pass validation. - Tests --- src/app.rs | 5 ++ src/common.rs | 3 + src/databases/database.rs | 3 + src/databases/mysql.rs | 17 +++++ src/databases/sqlite.rs | 17 +++++ src/services/authentication.rs | 9 +++ src/services/user.rs | 67 ++++++++++++++++++- src/web/api/server/signals.rs | 2 +- src/web/api/server/v1/contexts/user/forms.rs | 2 +- .../api/server/v1/contexts/user/handlers.rs | 15 +++-- 10 files changed, 131 insertions(+), 9 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2272dd4f..93934f6e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -120,6 +120,10 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running user_repository.clone(), user_profile_repository.clone(), )); + let profile_service = Arc::new(user::ProfileService::new( + configuration.clone(), + user_authentication_repository.clone(), + )); let ban_service = Arc::new(user::BanService::new( user_repository.clone(), user_profile_repository.clone(), @@ -164,6 +168,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running settings_service, torrent_index, registration_service, + profile_service, ban_service, )); diff --git a/src/common.rs b/src/common.rs index 7e08e125..2ce59f7d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -49,6 +49,7 @@ pub struct AppData { pub settings_service: Arc, pub torrent_service: Arc, pub registration_service: Arc, + pub profile_service: Arc, pub ban_service: Arc, } @@ -85,6 +86,7 @@ impl AppData { settings_service: Arc, torrent_service: Arc, registration_service: Arc, + profile_service: Arc, ban_service: Arc, ) -> AppData { AppData { @@ -118,6 +120,7 @@ impl AppData { settings_service, torrent_service, registration_service, + profile_service, ban_service, } } diff --git a/src/databases/database.rs b/src/databases/database.rs index ce3843b0..aed86ecb 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -131,6 +131,9 @@ pub trait Database: Sync + Send { /// Add new user and return the newly inserted `user_id`. async fn insert_user_and_get_id(&self, username: &str, email: &str, password: &str) -> Result; + /// Change user's password. + async fn change_user_password(&self, user_id: i64, new_password: &str) -> Result<(), Error>; + /// Get `User` from `user_id`. async fn get_user_from_id(&self, user_id: i64) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index beec1a80..14d69ea2 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -114,6 +114,23 @@ impl Database for Mysql { } } + /// Change user's password. + async fn change_user_password(&self, user_id: i64, new_password: &str) -> Result<(), database::Error> { + query("UPDATE torrust_user_authentication SET password_hash = ? WHERE user_id = ?") + .bind(new_password) + .bind(user_id) + .execute(&self.pool) + .await + .map_err(|_| database::Error::Error) + .and_then(|v| { + if v.rows_affected() > 0 { + Ok(()) + } else { + Err(database::Error::UserNotFound) + } + }) + } + async fn get_user_from_id(&self, user_id: i64) -> Result { query_as::<_, User>("SELECT * FROM torrust_users WHERE user_id = ?") .bind(user_id) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index a70beac6..ceb6e410 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -115,6 +115,23 @@ impl Database for Sqlite { } } + /// Change user's password. + async fn change_user_password(&self, user_id: i64, new_password: &str) -> Result<(), database::Error> { + query("UPDATE torrust_user_authentication SET password_hash = ? WHERE user_id = ?") + .bind(new_password) + .bind(user_id) + .execute(&self.pool) + .await + .map_err(|_| database::Error::Error) + .and_then(|v| { + if v.rows_affected() > 0 { + Ok(()) + } else { + Err(database::Error::UserNotFound) + } + }) + } + async fn get_user_from_id(&self, user_id: i64) -> Result { query_as::<_, User>("SELECT * FROM torrust_users WHERE user_id = ?") .bind(user_id) diff --git a/src/services/authentication.rs b/src/services/authentication.rs index 170cac82..39c73255 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -183,6 +183,15 @@ impl DbUserAuthenticationRepository { pub async fn get_user_authentication_from_id(&self, user_id: &UserId) -> Result { self.database.get_user_authentication_from_id(*user_id).await } + + /// It changes the user's password. + /// + /// # Errors + /// + /// It returns an error if there is a database error. + pub async fn change_password(&self, user_id: UserId, password_hash: &str) -> Result<(), Error> { + self.database.change_user_password(user_id, password_hash).await + } } /// Verify if the user supplied and the database supplied passwords match diff --git a/src/services/user.rs b/src/services/user.rs index dc397c17..5b0c7364 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -10,6 +10,7 @@ use log::{debug, info}; use mockall::automock; use pbkdf2::password_hash::rand_core::OsRng; +use super::authentication::DbUserAuthenticationRepository; use crate::config::{Configuration, EmailOnSignup}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; @@ -17,7 +18,7 @@ use crate::mailer; use crate::mailer::VerifyClaims; use crate::models::user::{UserCompact, UserId, UserProfile, Username}; use crate::utils::validation::validate_email_address; -use crate::web::api::server::v1::contexts::user::forms::RegistrationForm; +use crate::web::api::server::v1::contexts::user::forms::{ChangePasswordForm, RegistrationForm}; /// Since user email could be optional, we need a way to represent "no email" /// in the database. This function returns the string that should be used for @@ -186,6 +187,70 @@ impl RegistrationService { } } +pub struct ProfileService { + configuration: Arc, + user_authentication_repository: Arc, +} + +impl ProfileService { + #[must_use] + pub fn new(configuration: Arc, user_repository: Arc) -> Self { + Self { + configuration, + user_authentication_repository: user_repository, + } + } + + /// It registers a new user. + /// + /// # Errors + /// + /// This function will return a: + /// + /// * `ServiceError::PasswordsDontMatch` if the supplied passwords do not match. + /// * `ServiceError::PasswordTooShort` if the supplied password is too short. + /// * `ServiceError::PasswordTooLong` if the supplied password is too long. + /// * An error if unable to successfully hash the password. + /// * An error if unable to change the password in the database. + pub async fn change_password(&self, user_id: UserId, change_password_form: &ChangePasswordForm) -> Result<(), ServiceError> { + info!("changing user password for user ID: {user_id}"); + + let settings = self.configuration.settings.read().await; + + // todo: + // - Validate current password + // - Remove duplicate code for password validation and hashing + + if change_password_form.password != change_password_form.confirm_password { + return Err(ServiceError::PasswordsDontMatch); + } + + let password_length = change_password_form.password.len(); + + if password_length <= settings.auth.min_password_length { + return Err(ServiceError::PasswordTooShort); + } + + if password_length >= settings.auth.max_password_length { + return Err(ServiceError::PasswordTooLong); + } + + let salt = SaltString::generate(&mut OsRng); + + // Argon2 with default params (Argon2id v19) + let argon2 = Argon2::default(); + + // Hash password to PHC string ($argon2id$v=19$...) + let new_password_hash = argon2 + .hash_password(change_password_form.password.as_bytes(), &salt)? + .to_string(); + + self.user_authentication_repository.change_password(user_id, &new_password_hash).await?; + + Ok(()) + } +} + pub struct BanService { user_repository: Arc>, user_profile_repository: Arc, diff --git a/src/web/api/server/signals.rs b/src/web/api/server/signals.rs index 872d6094..5979f58a 100644 --- a/src/web/api/server/signals.rs +++ b/src/web/api/server/signals.rs @@ -23,7 +23,7 @@ pub async fn graceful_shutdown(handle: axum_server::Handle, rx_halt: tokio::sync shutdown_signal_with_message(rx_halt, message).await; info!("Sending graceful shutdown signal"); - handle.graceful_shutdown(Some(Duration::from_secs(90))); + handle.graceful_shutdown(Some(Duration::from_secs(2))); println!("!! shuting down in 90 seconds !!"); diff --git a/src/web/api/server/v1/contexts/user/forms.rs b/src/web/api/server/v1/contexts/user/forms.rs index fdcfd0cb..28238539 100644 --- a/src/web/api/server/v1/contexts/user/forms.rs +++ b/src/web/api/server/v1/contexts/user/forms.rs @@ -27,7 +27,7 @@ pub struct JsonWebToken { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ChangePasswordForm { + pub current_password: String, pub password: String, - pub new_password: String, pub confirm_password: String, } diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index e6b36ae5..54b3f229 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -10,7 +10,6 @@ use serde::Deserialize; use super::forms::{ChangePasswordForm, JsonWebToken, LoginForm, RegistrationForm}; use super::responses::{self}; use crate::common::AppData; -use crate::errors::ServiceError; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; @@ -133,13 +132,17 @@ pub async fn renew_token_handler( /// - The user account is not found. #[allow(clippy::unused_async)] pub async fn change_password_handler( - State(_app_data): State>, + State(app_data): State>, + ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(change_password_form): extract::Json, ) -> Response { - - println!("change pass form: {change_password_form:#?}"); - - ServiceError::AccountNotFound.into_response() + match app_data.profile_service.change_password(user_id, &change_password_form).await { + Ok(()) => Json(OkResponseData { + data: format!("Password changed for user with ID: {user_id}"), + }) + .into_response(), + Err(error) => error.into_response(), + } } /// It bans a user from the index. From a4de33ab4c335c9c5a757bb7ab8fd0f0ff2fe818 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 4 Jun 2024 10:57:49 +0100 Subject: [PATCH 200/309] test: [#618] add tests for changing the user's password --- tests/common/client.rs | 18 ++++---- tests/common/contexts/user/asserts.rs | 4 +- tests/common/contexts/user/fixtures.rs | 10 ++++- tests/common/contexts/user/forms.rs | 7 ++++ .../e2e/web/api/v1/contexts/user/contract.rs | 41 ++++++++++++++++++- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/tests/common/client.rs b/tests/common/client.rs index 687b5044..c9c22d2e 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -8,7 +8,9 @@ use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; use super::contexts::tag::forms::{AddTagForm, DeleteTagForm}; use super::contexts::torrent::forms::UpdateTorrentFrom; use super::contexts::torrent::requests::InfoHash; -use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username}; +use super::contexts::user::forms::{ + ChangePasswordForm, LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username, +}; use super::http::{Query, ReqwestQuery}; use super::responses::{self, BinaryResponse, TextResponse}; @@ -158,6 +160,12 @@ impl Client { self.http_client.post("/user/login", ®istration_form).await } + pub async fn change_password(&self, username: Username, change_password_form: ChangePasswordForm) -> TextResponse { + self.http_client + .post(&format!("/user/{}/change-password", &username.value), &change_password_form) + .await + } + pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> TextResponse { self.http_client.post("/user/token/verify", &token_verification_form).await } @@ -351,13 +359,9 @@ impl Http { } fn base_url(&self, path: &str) -> String { - let url = format!( + format!( "http://{}/{}{path}", // DevSkim: ignore DS137138 &self.connection_info.bind_address, &self.connection_info.base_path - ); - - println!("URL: {url}"); - - url + ) } } diff --git a/tests/common/contexts/user/asserts.rs b/tests/common/contexts/user/asserts.rs index dfa5352b..959d0931 100644 --- a/tests/common/contexts/user/asserts.rs +++ b/tests/common/contexts/user/asserts.rs @@ -12,13 +12,13 @@ pub fn assert_added_user_response(response: &TextResponse) { assert_json_ok_response(response); } -pub fn assert_successful_login_response(response: &TextResponse, registered_user: &RegistrationForm) { +pub fn assert_successful_login_response(response: &TextResponse, username: &str) { let successful_login_response: SuccessfulLoginResponse = serde_json::from_str(&response.body) .unwrap_or_else(|_| panic!("response {:#?} should be a SuccessfulLoginResponse", response.body)); let logged_in_user = successful_login_response.data; - assert_eq!(logged_in_user.username, registered_user.username); + assert_eq!(logged_in_user.username, username); assert_json_ok_response(response); } diff --git a/tests/common/contexts/user/fixtures.rs b/tests/common/contexts/user/fixtures.rs index fea39e7f..4d5ce357 100644 --- a/tests/common/contexts/user/fixtures.rs +++ b/tests/common/contexts/user/fixtures.rs @@ -2,13 +2,19 @@ use rand::Rng; use crate::common::contexts::user::forms::RegistrationForm; +/// Default password used in tests +pub const DEFAULT_PASSWORD: &str = "password"; + +/// Sample valid password used in tests +pub const VALID_PASSWORD: &str = "12345678"; + pub fn random_user_registration_form() -> RegistrationForm { let user_id = random_user_id(); RegistrationForm { username: format!("username_{user_id}"), email: Some(format!("email_{user_id}@email.com")), - password: "password".to_string(), - confirm_password: "password".to_string(), + password: DEFAULT_PASSWORD.to_string(), + confirm_password: DEFAULT_PASSWORD.to_string(), } } diff --git a/tests/common/contexts/user/forms.rs b/tests/common/contexts/user/forms.rs index 359252a8..bc2e9a1b 100644 --- a/tests/common/contexts/user/forms.rs +++ b/tests/common/contexts/user/forms.rs @@ -35,3 +35,10 @@ impl Username { Self { value } } } + +#[derive(Serialize)] +pub struct ChangePasswordForm { + pub current_password: String, + pub password: String, + pub confirm_password: String, +} diff --git a/tests/e2e/web/api/v1/contexts/user/contract.rs b/tests/e2e/web/api/v1/contexts/user/contract.rs index 809a2cb9..14ea0490 100644 --- a/tests/e2e/web/api/v1/contexts/user/contract.rs +++ b/tests/e2e/web/api/v1/contexts/user/contract.rs @@ -58,7 +58,10 @@ mod authentication { use crate::common::contexts::user::asserts::{ assert_successful_login_response, assert_token_renewal_response, assert_token_verified_response, }; - use crate::common::contexts::user::forms::{LoginForm, TokenRenewalForm, TokenVerificationForm}; + use crate::common::contexts::user::fixtures::{DEFAULT_PASSWORD, VALID_PASSWORD}; + use crate::common::contexts::user::forms::{ + ChangePasswordForm, LoginForm, TokenRenewalForm, TokenVerificationForm, Username, + }; use crate::e2e::environment::TestEnv; use crate::e2e::web::api::v1::contexts::user::steps::{new_logged_in_user, new_registered_user}; @@ -78,7 +81,41 @@ mod authentication { }) .await; - assert_successful_login_response(&response, ®istered_user); + assert_successful_login_response(&response, ®istered_user.username); + } + + #[tokio::test] + async fn it_should_allow_logged_in_users_to_change_their_passwords() { + let mut env = TestEnv::new(); + env.start(api::Version::V1).await; + + let logged_in_user = new_logged_in_user(&env).await; + + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_user.token); + + let new_password = VALID_PASSWORD.to_string(); + + let response = client + .change_password( + Username::new(logged_in_user.username.clone()), + ChangePasswordForm { + current_password: DEFAULT_PASSWORD.to_string(), + password: new_password.clone(), + confirm_password: new_password.clone(), + }, + ) + .await; + + assert_eq!(response.status, 200); + + let response = client + .login_user(LoginForm { + login: logged_in_user.username.clone(), + password: new_password, + }) + .await; + + assert_successful_login_response(&response, &logged_in_user.username); } #[tokio::test] From 659fd92174aeff17dbe0c33990b311b620f63748 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 4 Jun 2024 11:25:51 +0100 Subject: [PATCH 201/309] refactor: [#618] extract duplicate code for password validation and hashing. --- src/services/user.rs | 105 ++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/src/services/user.rs b/src/services/user.rs index 5b0c7364..d260fc45 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -95,29 +95,18 @@ impl RegistrationService { } } - if registration_form.password != registration_form.confirm_password { - return Err(ServiceError::PasswordsDontMatch); - } - - let password_length = registration_form.password.len(); - - if password_length <= settings.auth.min_password_length { - return Err(ServiceError::PasswordTooShort); - } - - if password_length >= settings.auth.max_password_length { - return Err(ServiceError::PasswordTooLong); - } - - let salt = SaltString::generate(&mut OsRng); + let password_constraints = PasswordConstraints { + min_password_length: settings.auth.min_password_length, + max_password_length: settings.auth.max_password_length, + }; - // Argon2 with default params (Argon2id v19) - let argon2 = Argon2::default(); + validate_password( + ®istration_form.password, + ®istration_form.confirm_password, + &password_constraints, + )?; - // Hash password to PHC string ($argon2id$v=19$...) - let password_hash = argon2 - .hash_password(registration_form.password.as_bytes(), &salt)? - .to_string(); + let password_hash = hash_password(®istration_form.password)?; let user_id = self .user_repository @@ -217,35 +206,24 @@ impl ProfileService { let settings = self.configuration.settings.read().await; - // todo: - // - Validate current password - // - Remove duplicate code for password validation and hashing + // todo: guard that current password matches the one provided in change password form - if change_password_form.password != change_password_form.confirm_password { - return Err(ServiceError::PasswordsDontMatch); - } - - let password_length = change_password_form.password.len(); - - if password_length <= settings.auth.min_password_length { - return Err(ServiceError::PasswordTooShort); - } - - if password_length >= settings.auth.max_password_length { - return Err(ServiceError::PasswordTooLong); - } - - let salt = SaltString::generate(&mut OsRng); + let password_constraints = PasswordConstraints { + min_password_length: settings.auth.min_password_length, + max_password_length: settings.auth.max_password_length, + }; - // Argon2 with default params (Argon2id v19) - let argon2 = Argon2::default(); + validate_password( + &change_password_form.password, + &change_password_form.confirm_password, + &password_constraints, + )?; - // Hash password to PHC string ($argon2id$v=19$...) - let new_password_hash = argon2 - .hash_password(change_password_form.password.as_bytes(), &salt)? - .to_string(); + let password_hash = hash_password(&change_password_form.password)?; - self.user_authentication_repository.change_password(user_id, &new_password_hash).await?; + self.user_authentication_repository + .change_password(user_id, &password_hash) + .await?; Ok(()) } @@ -429,3 +407,38 @@ impl DbBannedUserList { self.database.ban_user(*user_id, &reason, date_expiry).await } } + +struct PasswordConstraints { + pub min_password_length: usize, + pub max_password_length: usize, +} + +fn validate_password(password: &str, confirm_password: &str, password_rules: &PasswordConstraints) -> Result<(), ServiceError> { + if password != confirm_password { + return Err(ServiceError::PasswordsDontMatch); + } + + let password_length = password.len(); + + if password_length <= password_rules.min_password_length { + return Err(ServiceError::PasswordTooShort); + } + + if password_length >= password_rules.max_password_length { + return Err(ServiceError::PasswordTooLong); + } + + Ok(()) +} + +fn hash_password(password: &str) -> Result { + let salt = SaltString::generate(&mut OsRng); + + // Argon2 with default params (Argon2id v19) + let argon2 = Argon2::default(); + + // Hash password to PHC string ($argon2id$v=19$...) + let password_hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string(); + + Ok(password_hash) +} From 95e50190b578254782bd47663a1f4c71b5238d8b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 4 Jun 2024 12:07:24 +0100 Subject: [PATCH 202/309] feat: [#618] check current password before changing it This is an extra security check. Before changing the password the user must provide the current one. --- src/errors.rs | 3 +++ src/services/authentication.rs | 14 +++++------ src/services/user.rs | 19 +++++++++++---- src/web/api/server/signals.rs | 2 +- .../e2e/web/api/v1/contexts/user/contract.rs | 23 +++++++++++++++++++ 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 9723b4f7..00c79ecf 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -30,6 +30,8 @@ pub enum ServiceError { #[display(fmt = "Invalid username/email or password")] WrongPasswordOrUsername, + #[display(fmt = "Invalid password")] + InvalidPassword, #[display(fmt = "Username not found")] UsernameNotFound, #[display(fmt = "User not found")] @@ -273,6 +275,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::EmailInvalid => StatusCode::BAD_REQUEST, ServiceError::NotAUrl => StatusCode::BAD_REQUEST, ServiceError::WrongPasswordOrUsername => StatusCode::FORBIDDEN, + ServiceError::InvalidPassword => StatusCode::FORBIDDEN, ServiceError::UsernameNotFound => StatusCode::NOT_FOUND, ServiceError::UserNotFound => StatusCode::NOT_FOUND, ServiceError::AccountNotFound => StatusCode::NOT_FOUND, diff --git a/src/services/authentication.rs b/src/services/authentication.rs index 39c73255..cd9dd9d3 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -65,7 +65,7 @@ impl Service { .await .map_err(|_| ServiceError::InternalServerError)?; - verify_password(password.as_bytes(), &user_authentication)?; + verify_password(password.as_bytes(), &user_authentication).map_err(|_| ServiceError::WrongPasswordOrUsername)?; let settings = self.configuration.settings.read().await; @@ -191,7 +191,7 @@ impl DbUserAuthenticationRepository { /// It returns an error if there is a database error. pub async fn change_password(&self, user_id: UserId, password_hash: &str) -> Result<(), Error> { self.database.change_user_password(user_id, password_hash).await - } + } } /// Verify if the user supplied and the database supplied passwords match @@ -199,27 +199,27 @@ impl DbUserAuthenticationRepository { /// # Errors /// /// This function will return an error if unable to parse password hash from the stored user authentication value. -/// This function will return a `ServiceError::WrongPasswordOrUsername` if unable to match the password with either `argon2id` or `pbkdf2-sha256`. -fn verify_password(password: &[u8], user_authentication: &UserAuthentication) -> Result<(), ServiceError> { +/// This function will return a `ServiceError::InvalidPassword` if unable to match the password with either `argon2id` or `pbkdf2-sha256`. +pub fn verify_password(password: &[u8], user_authentication: &UserAuthentication) -> Result<(), ServiceError> { // wrap string of the hashed password into a PasswordHash struct for verification let parsed_hash = PasswordHash::new(&user_authentication.password_hash)?; match parsed_hash.algorithm.as_str() { "argon2id" => { if Argon2::default().verify_password(password, &parsed_hash).is_err() { - return Err(ServiceError::WrongPasswordOrUsername); + return Err(ServiceError::InvalidPassword); } Ok(()) } "pbkdf2-sha256" => { if Pbkdf2.verify_password(password, &parsed_hash).is_err() { - return Err(ServiceError::WrongPasswordOrUsername); + return Err(ServiceError::InvalidPassword); } Ok(()) } - _ => Err(ServiceError::WrongPasswordOrUsername), + _ => Err(ServiceError::InvalidPassword), } } diff --git a/src/services/user.rs b/src/services/user.rs index d260fc45..f18ecc52 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -17,6 +17,7 @@ use crate::errors::ServiceError; use crate::mailer; use crate::mailer::VerifyClaims; use crate::models::user::{UserCompact, UserId, UserProfile, Username}; +use crate::services::authentication::verify_password; use crate::utils::validation::validate_email_address; use crate::web::api::server::v1::contexts::user::forms::{ChangePasswordForm, RegistrationForm}; @@ -100,7 +101,7 @@ impl RegistrationService { max_password_length: settings.auth.max_password_length, }; - validate_password( + validate_password_constrains( ®istration_form.password, ®istration_form.confirm_password, &password_constraints, @@ -196,6 +197,7 @@ impl ProfileService { /// /// This function will return a: /// + /// * `ServiceError::InvalidPassword` if the current password supplied is invalid. /// * `ServiceError::PasswordsDontMatch` if the supplied passwords do not match. /// * `ServiceError::PasswordTooShort` if the supplied password is too short. /// * `ServiceError::PasswordTooLong` if the supplied password is too long. @@ -206,14 +208,19 @@ impl ProfileService { let settings = self.configuration.settings.read().await; - // todo: guard that current password matches the one provided in change password form + let user_authentication = self + .user_authentication_repository + .get_user_authentication_from_id(&user_id) + .await?; + + verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?; let password_constraints = PasswordConstraints { min_password_length: settings.auth.min_password_length, max_password_length: settings.auth.max_password_length, }; - validate_password( + validate_password_constrains( &change_password_form.password, &change_password_form.confirm_password, &password_constraints, @@ -413,7 +420,11 @@ struct PasswordConstraints { pub max_password_length: usize, } -fn validate_password(password: &str, confirm_password: &str, password_rules: &PasswordConstraints) -> Result<(), ServiceError> { +fn validate_password_constrains( + password: &str, + confirm_password: &str, + password_rules: &PasswordConstraints, +) -> Result<(), ServiceError> { if password != confirm_password { return Err(ServiceError::PasswordsDontMatch); } diff --git a/src/web/api/server/signals.rs b/src/web/api/server/signals.rs index 5979f58a..872d6094 100644 --- a/src/web/api/server/signals.rs +++ b/src/web/api/server/signals.rs @@ -23,7 +23,7 @@ pub async fn graceful_shutdown(handle: axum_server::Handle, rx_halt: tokio::sync shutdown_signal_with_message(rx_halt, message).await; info!("Sending graceful shutdown signal"); - handle.graceful_shutdown(Some(Duration::from_secs(2))); + handle.graceful_shutdown(Some(Duration::from_secs(90))); println!("!! shuting down in 90 seconds !!"); diff --git a/tests/e2e/web/api/v1/contexts/user/contract.rs b/tests/e2e/web/api/v1/contexts/user/contract.rs index 14ea0490..3124fc28 100644 --- a/tests/e2e/web/api/v1/contexts/user/contract.rs +++ b/tests/e2e/web/api/v1/contexts/user/contract.rs @@ -118,6 +118,29 @@ mod authentication { assert_successful_login_response(&response, &logged_in_user.username); } + #[tokio::test] + async fn it_should_fail_changing_the_password_if_the_user_does_not_provide_the_current_password() { + let mut env = TestEnv::new(); + env.start(api::Version::V1).await; + + let logged_in_user = new_logged_in_user(&env).await; + + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_user.token); + + let response = client + .change_password( + Username::new(logged_in_user.username.clone()), + ChangePasswordForm { + current_password: "INVALID PASSWORD".to_string(), + password: VALID_PASSWORD.to_string(), + confirm_password: VALID_PASSWORD.to_string(), + }, + ) + .await; + + assert_eq!(response.status, 403); + } + #[tokio::test] async fn it_should_allow_a_logged_in_user_to_verify_an_authentication_token() { let mut env = TestEnv::new(); From 213fcd34fee0b7dd7b5502ba18c84979899c6028 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 7 Jun 2024 15:20:10 +0100 Subject: [PATCH 203/309] chore(deps): [#621] add cargo dependency: tracing-subscriber We will migrate logging from `log` to `tracing` crate. --- Cargo.lock | 84 ++++++++++++++++++++++++++++++- Cargo.toml | 1 + packages/located-error/Cargo.toml | 3 +- 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddb17296..efc6b7f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,6 +1705,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.5" @@ -1837,6 +1847,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2674,6 +2690,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3167,6 +3192,16 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -3403,6 +3438,7 @@ dependencies = [ "tower-http", "trace", "tracing", + "tracing-subscriber", "url", "urlencoding", "uuid", @@ -3413,8 +3449,9 @@ dependencies = [ name = "torrust-index-located-error" version = "3.0.0-alpha.3-develop" dependencies = [ - "log", "thiserror", + "tracing", + "tracing-subscriber", ] [[package]] @@ -3508,6 +3545,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -3737,6 +3813,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 64096caf..f8acf76b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ tracing = "0.1.40" url = { version = "2.5.0", features = ["serde"] } urlencoding = "2" uuid = { version = "1", features = ["v4"] } +tracing-subscriber = { version = "0.3.18", features = ["json"] } [dev-dependencies] tempfile = "3" diff --git a/packages/located-error/Cargo.toml b/packages/located-error/Cargo.toml index d8065bca..6cc26bbe 100644 --- a/packages/located-error/Cargo.toml +++ b/packages/located-error/Cargo.toml @@ -15,7 +15,8 @@ rust-version.workspace = true version.workspace = true [dependencies] -log = { version = "0", features = ["release_max_level_info"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["json"] } [dev-dependencies] thiserror = "1.0" From f6bace6c6636097622361bfaba6f49c242d3dd9d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 7 Jun 2024 17:53:53 +0100 Subject: [PATCH 204/309] feat: move loggin from log to tracing crate Current outpuit: ```output $ cargo run Compiling torrust-index v3.0.0-alpha.3-develop (/home/josecelano/Documents/github/committer/me/torrust/torrust-index) Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.31s Running `target/debug/torrust-index` Loading configuration from default configuration file: `./share/default/config/index.development.sqlite3.toml` ... 2024-06-07T15:35:08.192302724+01:00 [torrust_index::bootstrap::logging][INFO] logging initialized. 2024-06-07T15:35:08.252784599+01:00 [torrust_index::web::api::server][INFO] TLS not enabled 2024-06-07T15:35:08.252892290+01:00 [torrust_index::console::cronjobs::tracker_statistics_importer][INFO] Tracker statistics importer launcher started 2024-06-07T15:35:08.252979221+01:00 [torrust_index::console::cronjobs::tracker_statistics_importer][INFO] Tracker statistics importer cronjob starting ... 2024-06-07T15:35:08.252977224+01:00 [torrust_index::web::api::server][INFO] Starting API server with net config: 0.0.0.0:3001 ... 2024-06-07T15:35:08.253260311+01:00 [torrust_index::console::cronjobs::tracker_statistics_importer][INFO] Tracker statistics importer API server listening on http://127.0.0.1:3002 2024-06-07T15:35:08.254122817+01:00 [torrust_index::console::cronjobs::tracker_statistics_importer][INFO] Running tracker statistics importer every 2000 milliseconds ... 2024-06-07T15:35:08.254518031+01:00 [torrust_index::web::api::server][INFO] API server listening on http://0.0.0.0:3001 2024-06-07T15:35:08.284476791+01:00 [Tracker Stats Importer][INFO] Importing 1 torrents statistics from tracker udp://localhost:6969 ... ``` New output: ```output $ cargo run Blocking waiting for file lock on build directory Compiling torrust-index v3.0.0-alpha.3-develop (/home/josecelano/Documents/github/committer/me/torrust/torrust-index) Finished `dev` profile [unoptimized + debuginfo] target(s) in 52.26s Running `target/debug/torrust-index` Loading configuration from default configuration file: `./share/default/config/index.development.sqlite3.toml` ... 2024-06-07T16:50:05.192713Z INFO torrust_index::bootstrap::logging: logging initialized. 2024-06-07T16:50:05.352161Z INFO torrust_index::web::api::server: TLS not enabled 2024-06-07T16:50:05.352303Z INFO torrust_index::console::cronjobs::tracker_statistics_importer: Tracker statistics importer launcher started 2024-06-07T16:50:05.352318Z INFO torrust_index::web::api::server: Starting API server with net config: 0.0.0.0:3001 ... 2024-06-07T16:50:05.352363Z INFO torrust_index::console::cronjobs::tracker_statistics_importer: Tracker statistics importer cronjob starting ... 2024-06-07T16:50:05.352828Z INFO torrust_index::console::cronjobs::tracker_statistics_importer: Tracker statistics importer API server listening on http://127.0.0.1:3002 2024-06-07T16:50:05.353605Z INFO torrust_index::console::cronjobs::tracker_statistics_importer: Running tracker statistics importer every 2000 milliseconds ... 2024-06-07T16:50:05.356876Z INFO torrust_index::web::api::server: API server listening on http://0.0.0.0:3001 2024-06-07T16:50:05.428304Z INFO Tracker Stats Importer: Importing 1 torrents statistics from tracker udp://localhost:6969 ... ``` --- packages/located-error/src/lib.rs | 2 +- src/bootstrap/logging.rs | 74 ++++++++++++------- src/console/commands/seeder/api.rs | 2 +- src/console/commands/seeder/app.rs | 5 +- src/console/commands/seeder/logging.rs | 20 +---- .../cronjobs/tracker_statistics_importer.rs | 2 +- src/databases/mysql.rs | 10 +-- src/databases/sqlite.rs | 10 +-- src/mailer.rs | 2 +- src/models/torrent_file.rs | 2 +- src/services/torrent.rs | 2 +- src/services/user.rs | 2 +- src/tracker/service.rs | 2 +- src/tracker/statistics_importer.rs | 2 +- src/web/api/server/mod.rs | 2 +- src/web/api/server/signals.rs | 2 +- .../server/v1/contexts/torrent/handlers.rs | 2 +- tests/environments/app_starter.rs | 2 +- 18 files changed, 75 insertions(+), 70 deletions(-) diff --git a/packages/located-error/src/lib.rs b/packages/located-error/src/lib.rs index bf861868..2844d69d 100644 --- a/packages/located-error/src/lib.rs +++ b/packages/located-error/src/lib.rs @@ -90,7 +90,7 @@ where source: Arc::new(self.0), location: Box::new(*std::panic::Location::caller()), }; - log::debug!("{e}"); + tracing::debug!("{e}"); e } } diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index 4fc3ead9..4c02d6ee 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -8,55 +8,73 @@ //! - `Trace` use std::sync::Once; -use log::{info, LevelFilter}; +use tracing::info; +use tracing::level_filters::LevelFilter; use crate::config::v1::LogLevel; static INIT: Once = Once::new(); pub fn setup(log_level: &Option) { - let level = config_level_or_default(log_level); + let tracing_level = config_level_or_default(log_level); - if level == log::LevelFilter::Off { + if tracing_level == LevelFilter::OFF { return; } INIT.call_once(|| { - stdout_config(level); + tracing_stdout_init(tracing_level, &TraceStyle::Default); }); } fn config_level_or_default(log_level: &Option) -> LevelFilter { match log_level { - None => log::LevelFilter::Info, + None => LevelFilter::INFO, Some(level) => match level { - LogLevel::Off => LevelFilter::Off, - LogLevel::Error => LevelFilter::Error, - LogLevel::Warn => LevelFilter::Warn, - LogLevel::Info => LevelFilter::Info, - LogLevel::Debug => LevelFilter::Debug, - LogLevel::Trace => LevelFilter::Trace, + LogLevel::Off => LevelFilter::OFF, + LogLevel::Error => LevelFilter::ERROR, + LogLevel::Warn => LevelFilter::WARN, + LogLevel::Info => LevelFilter::INFO, + LogLevel::Debug => LevelFilter::DEBUG, + LogLevel::Trace => LevelFilter::TRACE, }, } } -fn stdout_config(level: LevelFilter) { - if let Err(_err) = fern::Dispatch::new() - .format(|out, message, record| { - out.finish(format_args!( - "{} [{}][{}] {}", - chrono::Local::now().format("%+"), - record.target(), - record.level(), - message - )); - }) - .level(level) - .chain(std::io::stdout()) - .apply() - { - panic!("Failed to initialize logging.") - } +fn tracing_stdout_init(filter: LevelFilter, style: &TraceStyle) { + let builder = tracing_subscriber::fmt().with_max_level(filter); + + let () = match style { + TraceStyle::Default => builder.init(), + TraceStyle::Pretty(display_filename) => builder.pretty().with_file(*display_filename).init(), + TraceStyle::Compact => builder.compact().init(), + TraceStyle::Json => builder.json().init(), + }; info!("logging initialized."); } + +#[derive(Debug)] +pub enum TraceStyle { + Default, + Pretty(bool), + Compact, + Json, +} + +impl std::fmt::Display for TraceStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let style = match self { + TraceStyle::Default => "Default Style", + TraceStyle::Pretty(path) => match path { + true => "Pretty Style with File Paths", + false => "Pretty Style without File Paths", + }, + + TraceStyle::Compact => "Compact Style", + TraceStyle::Json => "Json Format", + }; + + f.write_str(style) + } +} diff --git a/src/console/commands/seeder/api.rs b/src/console/commands/seeder/api.rs index 5ddee1d7..ed35dbc6 100644 --- a/src/console/commands/seeder/api.rs +++ b/src/console/commands/seeder/api.rs @@ -1,6 +1,6 @@ //! Action that a user can perform on a Index website. -use log::debug; use thiserror::Error; +use tracing::debug; use crate::web::api::client::v1::client::Client; use crate::web::api::client::v1::contexts::category::forms::AddCategoryForm; diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index 84ab55cf..d8e133f0 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -133,9 +133,10 @@ use std::time::Duration; use anyhow::Context; use clap::Parser; -use log::{debug, info, LevelFilter}; use reqwest::Url; use text_colorizer::Colorize; +use tracing::level_filters::LevelFilter; +use tracing::{debug, info}; use uuid::Uuid; use super::api::Error; @@ -171,7 +172,7 @@ struct Args { /// /// Will not return any errors for the time being. pub async fn run() -> anyhow::Result<()> { - logging::setup(LevelFilter::Info); + logging::setup(LevelFilter::INFO); let args = Args::parse(); diff --git a/src/console/commands/seeder/logging.rs b/src/console/commands/seeder/logging.rs index 85634719..3ba046f3 100644 --- a/src/console/commands/seeder/logging.rs +++ b/src/console/commands/seeder/logging.rs @@ -1,26 +1,12 @@ //! Logging setup for the `seeder`. -use log::{debug, LevelFilter}; +use tracing::debug; +use tracing::level_filters::LevelFilter; /// # Panics /// /// pub fn setup(level: LevelFilter) { - if let Err(_err) = fern::Dispatch::new() - .format(|out, message, record| { - out.finish(format_args!( - "{} [{}][{}] {}", - chrono::Local::now().format("%+"), - record.target(), - record.level(), - message - )); - }) - .level(level) - .chain(std::io::stdout()) - .apply() - { - panic!("Failed to initialize logging.") - } + tracing_subscriber::fmt().with_max_level(level).init(); debug!("logging initialized."); } diff --git a/src/console/cronjobs/tracker_statistics_importer.rs b/src/console/cronjobs/tracker_statistics_importer.rs index 7f618e61..2794c4cf 100644 --- a/src/console/cronjobs/tracker_statistics_importer.rs +++ b/src/console/cronjobs/tracker_statistics_importer.rs @@ -17,11 +17,11 @@ use axum::extract::State; use axum::routing::{get, post}; use axum::{Json, Router}; use chrono::{DateTime, Utc}; -use log::{debug, error, info}; use serde_json::{json, Value}; use text_colorizer::Colorize; use tokio::net::TcpListener; use tokio::task::JoinHandle; +use tracing::{debug, error, info}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::utils::clock::seconds_ago_utc; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 14d69ea2..89245ee9 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -509,7 +509,7 @@ impl Database for Mysql { .map(|v| i64::try_from(v.last_insert_id()).expect("last ID is larger than i64")) .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("Duplicate entry") && err.message().contains("info_hash") { database::Error::TorrentAlreadyExists } else { @@ -530,7 +530,7 @@ impl Database for Mysql { .await .map(|_| ()) .map_err(|err| { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); database::Error::Error }); @@ -683,7 +683,7 @@ impl Database for Mysql { .await .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("Duplicate entry") && err.message().contains("title") { database::Error::TorrentTitleAlreadyExists } else { @@ -931,7 +931,7 @@ impl Database for Mysql { .await .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("Duplicate entry") && err.message().contains("title") { database::Error::TorrentTitleAlreadyExists } else { @@ -989,7 +989,7 @@ impl Database for Mysql { .map(|v| i64::try_from(v.last_insert_id()).expect("last ID is larger than i64")) .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("Duplicate entry") && err.message().contains("name") { database::Error::TagAlreadyExists } else { diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index ceb6e410..f9482838 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -499,7 +499,7 @@ impl Database for Sqlite { .map(|v| v.last_insert_rowid()) .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("UNIQUE") && err.message().contains("info_hash") { database::Error::TorrentAlreadyExists } else { @@ -520,7 +520,7 @@ impl Database for Sqlite { .await .map(|_| ()) .map_err(|err| { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); database::Error::Error }); @@ -677,7 +677,7 @@ impl Database for Sqlite { .await .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("UNIQUE") && err.message().contains("title") { database::Error::TorrentTitleAlreadyExists } else { @@ -923,7 +923,7 @@ impl Database for Sqlite { .await .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("UNIQUE") && err.message().contains("title") { database::Error::TorrentTitleAlreadyExists } else { @@ -981,7 +981,7 @@ impl Database for Sqlite { .map(|v| v.last_insert_rowid()) .map_err(|e| match e { sqlx::Error::Database(err) => { - log::error!("DB error: {:?}", err); + tracing::error!("DB error: {:?}", err); if err.message().contains("UNIQUE") && err.message().contains("name") { database::Error::TagAlreadyExists } else { diff --git a/src/mailer.rs b/src/mailer.rs index 955aefbe..36134b14 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -152,7 +152,7 @@ impl Service { fn build_letter(verification_url: &str, username: &str, builder: MessageBuilder) -> Result { let (plain_body, html_body) = build_content(verification_url, username).map_err(|e| { - log::error!("{e}"); + tracing::error!("{e}"); ServiceError::InternalServerError })?; diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index ac28a6dc..df88b350 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -1,8 +1,8 @@ -use log::error; use serde::{Deserialize, Serialize}; use serde_bencode::ser; use serde_bytes::ByteBuf; use sha1::{Digest, Sha1}; +use tracing::error; use url::Url; use super::info_hash::InfoHash; diff --git a/src/services/torrent.rs b/src/services/torrent.rs index b98ef90b..28aa6223 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -1,8 +1,8 @@ //! Torrent service. use std::sync::Arc; -use log::debug; use serde_derive::{Deserialize, Serialize}; +use tracing::debug; use url::Url; use super::category::DbCategoryRepository; diff --git a/src/services/user.rs b/src/services/user.rs index f18ecc52..e808548e 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -5,10 +5,10 @@ use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher}; use async_trait::async_trait; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; -use log::{debug, info}; #[cfg(test)] use mockall::automock; use pbkdf2::password_hash::rand_core::OsRng; +use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; use crate::config::{Configuration, EmailOnSignup}; diff --git a/src/tracker/service.rs b/src/tracker/service.rs index 5de57d11..f02c605a 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use derive_more::{Display, Error}; use hyper::StatusCode; -use log::{debug, error}; use serde::{Deserialize, Serialize}; +use tracing::{debug, error}; use url::Url; use super::api::{Client, ConnectionInfo}; diff --git a/src/tracker/statistics_importer.rs b/src/tracker/statistics_importer.rs index 87512a42..bc256a2c 100644 --- a/src/tracker/statistics_importer.rs +++ b/src/tracker/statistics_importer.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use std::time::Instant; use chrono::{DateTime, Utc}; -use log::{debug, error, info}; use text_colorizer::Colorize; +use tracing::{debug, error, info}; use url::Url; use super::service::{Service, TorrentInfo, TrackerAPIError}; diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index 7d42c9e5..1349b54f 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -8,10 +8,10 @@ use std::sync::Arc; use axum_server::tls_rustls::RustlsConfig; use axum_server::Handle; -use log::{error, info}; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; use torrust_index_located_error::LocatedError; +use tracing::{error, info}; use v1::routes::router; use self::signals::{Halted, Started}; diff --git a/src/web/api/server/signals.rs b/src/web/api/server/signals.rs index 872d6094..2eead7da 100644 --- a/src/web/api/server/signals.rs +++ b/src/web/api/server/signals.rs @@ -2,8 +2,8 @@ use std::net::SocketAddr; use std::time::Duration; use derive_more::Display; -use log::info; use tokio::time::sleep; +use tracing::info; /// This is the message that the "launcher" spawned task sends to the main /// application process to notify the service was successfully started. diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index ddba8df0..5b38d3e4 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use axum::extract::{self, Multipart, Path, Query, State}; use axum::response::{IntoResponse, Redirect, Response}; use axum::Json; -use log::debug; use serde::Deserialize; +use tracing::debug; use uuid::Uuid; use super::errors; diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index 3d6fe36c..921418d0 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -1,11 +1,11 @@ use std::net::SocketAddr; -use log::info; use tokio::sync::{oneshot, RwLock}; use tokio::task::JoinHandle; use torrust_index::config::Configuration; use torrust_index::web::api::Version; use torrust_index::{app, config}; +use tracing::info; /// It launches the app and provides a way to stop it. pub struct AppStarter { From 0e2b5fd78daef6147f978db464107659d5ce42de Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Jun 2024 07:19:44 +0100 Subject: [PATCH 205/309] chore(deps): update dependencies ```ouput cargo update Updating crates.io index Locking 15 packages to latest compatible versions Updating anstyle-query v1.0.3 -> v1.1.0 Updating cc v1.0.98 -> v1.0.99 Updating clap v4.5.4 -> v4.5.6 Updating clap_builder v4.5.2 -> v4.5.6 Updating clap_derive v4.5.4 -> v4.5.5 Updating clap_lex v0.7.0 -> v0.7.1 Updating regex v1.10.4 -> v1.10.5 Updating regex-automata v0.4.6 -> v0.4.7 Updating regex-syntax v0.8.3 -> v0.8.4 Updating rustls v0.23.8 -> v0.23.9 Updating toml v0.8.13 -> v0.8.14 Updating toml_edit v0.22.13 -> v0.22.14 Updating utf8parse v0.2.1 -> v0.2.2 Updating webpki-roots v0.26.1 -> v0.26.2 Updating winnow v0.6.9 -> v0.6.13 ``` --- Cargo.lock | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efc6b7f3..b8fd0039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] @@ -446,9 +446,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" dependencies = [ "clap_builder", "clap_derive", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" dependencies = [ "anstream", "anstyle", @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -520,9 +520,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" @@ -1496,7 +1496,7 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.8", + "rustls 0.23.9", "rustls-pemfile", "serde", "serde_json", @@ -2216,9 +2216,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -2228,9 +2228,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -2239,9 +2239,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" @@ -2396,9 +2396,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.8" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" +checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" dependencies = [ "log", "once_cell", @@ -3317,7 +3317,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.8", + "rustls 0.23.9", "rustls-pki-types", "tokio", ] @@ -3348,9 +3348,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -3369,9 +3369,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", @@ -3800,9 +3800,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -3940,9 +3940,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" dependencies = [ "rustls-pki-types", ] @@ -4160,9 +4160,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.9" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] From 96269cd4e2d8a37cec1950ec985e49b204daa515 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Jun 2024 16:34:08 +0100 Subject: [PATCH 206/309] test: [#628] add failing test for bug. Duplicate slash in announce URL When the tracker is running in private mode the announce URL in the torrent file includes a double slash '/'. For example: http://tracker:7070//KEY This commit adds a failing test. It needed scaffolding to run E2E tests with a tracker running in "private" mode. We have been only running E2E tests for public trackers so far. The new tests is only execute with Sqlite3. It would be easy to run it also for MySQL. It was not cinluded in order to avoid making tests to slow. It requires to start the docker E2E shared environment twice (for public and private tracker configurations). --- .../container/e2e/sqlite/e2e-env-down.sh | 5 --- .../e2e/sqlite/mode/private/e2e-env-down.sh | 5 +++ .../e2e/sqlite/mode/private/e2e-env-up.sh | 16 ++++++++ .../e2e/sqlite/mode/public/e2e-env-down.sh | 5 +++ .../sqlite/{ => mode/public}/e2e-env-up.sh | 6 +-- .../container/e2e/sqlite/run-e2e-tests.sh | 38 +++++++++++++++-- .../index.private.e2e.container.sqlite3.toml | 17 ++++++++ ... => index.public.e2e.container.mysql.toml} | 0 ...> index.public.e2e.container.sqlite3.toml} | 0 ...tracker.private.e2e.container.sqlite3.toml | 37 +++++++++++++++++ ...tracker.public.e2e.container.sqlite3.toml} | 0 src/config/mod.rs | 23 ++++++++++- tests/e2e/environment.rs | 21 ++++++++-- .../web/api/v1/contexts/torrent/contract.rs | 41 +++++++++++++++++++ 14 files changed, 197 insertions(+), 17 deletions(-) delete mode 100755 contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh create mode 100755 contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh create mode 100755 contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh create mode 100755 contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh rename contrib/dev-tools/container/e2e/sqlite/{ => mode/public}/e2e-env-up.sh (79%) create mode 100644 share/default/config/index.private.e2e.container.sqlite3.toml rename share/default/config/{index.e2e.container.mysql.toml => index.public.e2e.container.mysql.toml} (100%) rename share/default/config/{index.e2e.container.sqlite3.toml => index.public.e2e.container.sqlite3.toml} (100%) create mode 100644 share/default/config/tracker.private.e2e.container.sqlite3.toml rename share/default/config/{tracker.e2e.container.sqlite3.toml => tracker.public.e2e.container.sqlite3.toml} (100%) diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh deleted file mode 100755 index da45b824..00000000 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ -TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ - docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh new file mode 100755 index 00000000..6be1fc22 --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.private.e2e.container.sqlite3.toml) \ +TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.private.e2e.container.sqlite3.toml) \ + docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh new file mode 100755 index 00000000..130cae58 --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.private.e2e.container.sqlite3.toml) \ + docker compose build + +USER_ID=${USER_ID:-1000} \ + TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.private.e2e.container.sqlite3.toml) \ + TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ + TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ + TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ + TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.private.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ + docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh new file mode 100755 index 00000000..ad55685f --- /dev/null +++ b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.sqlite3.toml) \ +TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ + docker compose down diff --git a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh similarity index 79% rename from contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh rename to contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh index 3f744cb4..75999892 100755 --- a/contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh @@ -1,15 +1,15 @@ #!/bin/bash -TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.sqlite3.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.sqlite3.toml) \ + TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.sqlite3.toml) \ TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ - TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 42b2f7d1..7f94c216 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -25,8 +25,38 @@ cp .env.local .env || exit 1 # TEST USING SQLITE echo "Running E2E tests using SQLite ..." +# TEST USING A PUBLIC TRACKER +echo "Running E2E tests with a public tracker ..." + +# Start E2E testing environment +./contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh || exit 1 + +# Wait for conatiners to be healthy +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1 +./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1 + +# Just to make sure that everything is up and running +docker ps + +# Run E2E tests with shared app instance +TORRUST_INDEX_E2E_SHARED=true \ + TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.public.e2e.container.sqlite3.toml" \ + TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ + cargo test || + { + ./contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh + exit 1 + } + +# Stop E2E testing environment +./contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh || exit 1 + +# TEST USING A PUBLIC TRACKER +echo "Running E2E tests with a private tracker ..." + # Start E2E testing environment -./contrib/dev-tools/container/e2e/sqlite/e2e-env-up.sh || exit 1 +./contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh || exit 1 # Wait for conatiners to be healthy ./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1 @@ -38,13 +68,13 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.e2e.container.sqlite3.toml" \ + TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.private.e2e.container.sqlite3.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="sqlite://./storage/index/lib/database/e2e_testing_sqlite3.db?mode=rwc" \ cargo test || { - ./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh + ./contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh exit 1 } # Stop E2E testing environment -./contrib/dev-tools/container/e2e/sqlite/e2e-env-down.sh || exit 1 +./contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-down.sh || exit 1 diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml new file mode 100644 index 00000000..358936b6 --- /dev/null +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -0,0 +1,17 @@ +# Please override the following settings with environmental variable! +# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` +# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` + +log_level = "info" + +[tracker] +api_url = "http://tracker:1212" +mode = "Private" +url = "http://tracker:7070" + +[database] +connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" + +[mail] +port = 1025 +server = "mailcatcher" diff --git a/share/default/config/index.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml similarity index 100% rename from share/default/config/index.e2e.container.mysql.toml rename to share/default/config/index.public.e2e.container.mysql.toml diff --git a/share/default/config/index.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml similarity index 100% rename from share/default/config/index.e2e.container.sqlite3.toml rename to share/default/config/index.public.e2e.container.sqlite3.toml diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml new file mode 100644 index 00000000..6809284b --- /dev/null +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -0,0 +1,37 @@ +announce_interval = 120 +db_driver = "Sqlite3" +db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" +external_ip = "0.0.0.0" +inactive_peer_cleanup_interval = 600 +log_level = "info" +max_peer_timeout = 900 +min_announce_interval = 120 +mode = "private" +on_reverse_proxy = false +persistent_torrent_completed_stat = false +remove_peerless_torrents = true +tracker_usage_statistics = true + +[[udp_trackers]] +bind_address = "0.0.0.0:6969" +enabled = false + +[[http_trackers]] +bind_address = "0.0.0.0:7070" +enabled = false +ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" +ssl_enabled = false +ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" + +[http_api] +bind_address = "0.0.0.0:1212" +enabled = true +ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" +ssl_enabled = false +ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" + +[http_api.access_tokens] +admin = "MyAccessToken" + +[health_check_api] +bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml similarity index 100% rename from share/default/config/tracker.e2e.container.sqlite3.toml rename to share/default/config/tracker.public.e2e.container.sqlite3.toml diff --git a/src/config/mod.rs b/src/config/mod.rs index ee4c88cb..8ff9c08a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,6 +3,7 @@ pub mod v1; pub mod validator; use std::env; +use std::str::FromStr; use std::sync::Arc; use camino::Utf8PathBuf; @@ -122,15 +123,17 @@ impl From for Error { Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. -Enum variants (Index -> Tracker): +Enum variants: + In Index In Tracker - `Public` -> `Public` - `Private` -> `Private` - `Whitelisted` -> `Listed` - `PrivateWhitelisted` -> `PrivateListed` -Enum serialized values (Index -> Tracker): +Enum serialized values: + In Index In Tracker - `Public` -> `public` - `Private` -> `private` - `Whitelisted` -> `listed` @@ -163,6 +166,22 @@ impl Default for TrackerMode { } } +impl FromStr for TrackerMode { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "Public" => Ok(TrackerMode::Public), + "Private" => Ok(TrackerMode::Private), + "Whitelisted" => Ok(TrackerMode::Whitelisted), + "PrivateWhitelisted" => Ok(TrackerMode::PrivateWhitelisted), + _ => Err(format!( + "{s} is not a valid tracker mode. Valid values: 'Public', 'Private', 'Whitelisted', 'PrivateWhitelisted' " + )), + } + } +} + impl TrackerMode { #[must_use] pub fn is_open(&self) -> bool { diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index f78c7c1b..224ca55a 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,6 +1,8 @@ use std::env; +use std::str::FromStr; use torrust_index::config::v1::tracker::ApiToken; +use torrust_index::config::TrackerMode; use torrust_index::web::api::Version; use url::Url; @@ -75,10 +77,23 @@ impl TestEnv { } } - /// Some test requires the real tracker to be running, so they can only - /// be run in shared mode. + /// Some test requires a real tracker running. pub fn provides_a_tracker(&self) -> bool { - self.is_shared() + self.is_shared() && self.server_settings().is_some() + } + + /// Some test requires a real tracker running in `private` mode. + pub fn provides_a_private_tracker(&self) -> bool { + if !self.is_shared() { + return false; + }; + + match self.server_settings() { + Some(settings) => { + TrackerMode::from_str(&settings.tracker.mode).expect("it should be a valid tracker mode") == TrackerMode::Private + } + None => false, + } } /// Returns the server starting settings if the servers was already started. diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index bc6f0369..ed3b4f33 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -765,6 +765,47 @@ mod for_authenticated_users { } } + mod downloading_a_torrent { + + use regex::Regex; + use torrust_index::utils::parse_torrent::decode_torrent; + use torrust_index::web::api; + use url::Url; + + use crate::common::client::Client; + use crate::e2e::environment::TestEnv; + use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index; + use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user; + + #[tokio::test] + async fn it_should_include_the_tracker_key_when_the_tracker_is_running_in_private_mode() { + let mut env = TestEnv::new(); + env.start(api::Version::V1).await; + + if !env.provides_a_private_tracker() { + println!("test skipped. It requires a private tracker to be running."); + return; + } + + let uploader = new_logged_in_user(&env).await; + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); + + // Upload + let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await; + + // Download + let response = client.download_torrent(&test_torrent.file_info_hash()).await; + + let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); + + let announce_url = Url::parse(&torrent.announce.unwrap()).unwrap(); + + let re = Regex::new(r"^http://tracker:7070/[a-zA-Z0-9]{32}$").unwrap(); // DevSkim: ignore DS137138 + + assert!(re.is_match(announce_url.as_ref()), "Invalid announce URL: '{announce_url}'."); + } + } + mod and_non_admins { use torrust_index::web::api; From 564ef13133392d7831697db6455004544cf7a2b9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Jun 2024 16:51:06 +0100 Subject: [PATCH 207/309] fix: [#628] bug, duplicate slash in tracker announce URL Runnint the tracker in private mode the announce URL in the torrent file should be: http://tracker:7070/TRACKER_USER_KEY instead of: http://tracker:7070//TRACKER_USER_KEY --- src/tracker/service.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tracker/service.rs b/src/tracker/service.rs index f02c605a..900206db 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -376,7 +376,9 @@ impl Service { /// It builds the announce url appending the user tracker key. /// Eg: fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> Url { - Url::parse(&format!("{}/{}", self.tracker_url, tracker_key.key)).unwrap() + self.tracker_url + .join(&tracker_key.key) + .expect("a tracker key should be added to the tracker base URL") } fn invalid_token_body() -> String { From f0a5006251f8f80477e7fba2537db78f45bbab3f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Jun 2024 17:21:56 +0100 Subject: [PATCH 208/309] fix: [#628] rename config files for MySQL E2E test env too --- contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh | 4 ++-- contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 6 +++--- contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh index 8e0c05f4..00a4728e 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh @@ -1,5 +1,5 @@ #!/bin/bash -TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ -TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ +TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.mysql.toml) \ +TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ docker compose down diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index a865d4a2..eb5706b6 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -1,15 +1,15 @@ #!/bin/bash -TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ +TORRUST_INDEX_CONFIG=$(cat ./share/default/config/index.public.e2e.container.mysql.toml) \ docker compose build USER_ID=${USER_ID:-1000} \ - TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.e2e.container.mysql.toml) \ + TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.mysql.toml) \ TORRUST_INDEX_DATABASE="torrust_index_e2e_testing" \ TORRUST_INDEX_DATABASE_DRIVER="mysql" \ TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ - TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.e2e.container.sqlite3.toml) \ + TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 79cea17a..8ef3b43f 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -40,7 +40,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ - TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.e2e.container.mysql.toml" \ + TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.public.e2e.container.mysql.toml" \ TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ cargo test || { From a3e162c20ecdf72cfb43b6e4a5ebfab57bead8e7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Jun 2024 17:41:59 +0100 Subject: [PATCH 209/309] refactor: simplify config files removing default values --- .../default/config/index.container.mysql.toml | 4 -- .../config/index.container.sqlite3.toml | 4 -- .../config/index.development.sqlite3.toml | 4 -- .../index.private.e2e.container.sqlite3.toml | 4 -- .../index.public.e2e.container.mysql.toml | 4 -- .../index.public.e2e.container.sqlite3.toml | 4 -- .../config/tracker.container.mysql.toml | 37 ------------------- .../config/tracker.container.sqlite3.toml | 37 ------------------- ...tracker.private.e2e.container.sqlite3.toml | 27 -------------- .../tracker.public.e2e.container.sqlite3.toml | 31 ---------------- 10 files changed, 156 deletions(-) delete mode 100644 share/default/config/tracker.container.mysql.toml delete mode 100644 share/default/config/tracker.container.sqlite3.toml diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 3ca737b2..5564fdaf 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" [database] diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 95e0c600..534578ed 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" [database] diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 8cafd3ea..8e188d5e 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" # Uncomment if you want to enable TSL for development diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index 358936b6..fe781f5b 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 037bdcff..16eb9f04 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index ca6d1944..c9e764bf 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,7 +1,3 @@ -# Please override the following settings with environmental variable! -# tracker::token -> `TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN` -# auth::secret_key -> `TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY` - log_level = "info" [tracker] diff --git a/share/default/config/tracker.container.mysql.toml b/share/default/config/tracker.container.mysql.toml deleted file mode 100644 index 49fa7c16..00000000 --- a/share/default/config/tracker.container.mysql.toml +++ /dev/null @@ -1,37 +0,0 @@ -announce_interval = 120 -db_driver = "MySQL" -db_path = "mysql://db_user:db_user_secret_password@mysql:3306/torrust_tracker" -external_ip = "0.0.0.0" -inactive_peer_cleanup_interval = 600 -log_level = "info" -max_peer_timeout = 900 -min_announce_interval = 120 -mode = "public" -on_reverse_proxy = false -persistent_torrent_completed_stat = false -remove_peerless_torrents = true -tracker_usage_statistics = true - -[[udp_trackers]] -bind_address = "0.0.0.0:6969" -enabled = false - -[[http_trackers]] -bind_address = "0.0.0.0:7070" -enabled = false -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api] -bind_address = "0.0.0.0:1212" -enabled = true -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api.access_tokens] -admin = "MyAccessToken" - -[health_check_api] -bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.container.sqlite3.toml b/share/default/config/tracker.container.sqlite3.toml deleted file mode 100644 index 7649f778..00000000 --- a/share/default/config/tracker.container.sqlite3.toml +++ /dev/null @@ -1,37 +0,0 @@ -announce_interval = 120 -db_driver = "Sqlite3" -db_path = "/var/lib/torrust/tracker/database/sqlite3.db" -external_ip = "0.0.0.0" -inactive_peer_cleanup_interval = 600 -log_level = "info" -max_peer_timeout = 900 -min_announce_interval = 120 -mode = "public" -on_reverse_proxy = false -persistent_torrent_completed_stat = false -remove_peerless_torrents = true -tracker_usage_statistics = true - -[[udp_trackers]] -bind_address = "0.0.0.0:6969" -enabled = false - -[[http_trackers]] -bind_address = "0.0.0.0:7070" -enabled = false -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api] -bind_address = "0.0.0.0:1212" -enabled = true -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api.access_tokens] -admin = "MyAccessToken" - -[health_check_api] -bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 6809284b..78457ecd 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -1,37 +1,10 @@ -announce_interval = 120 db_driver = "Sqlite3" db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" -external_ip = "0.0.0.0" -inactive_peer_cleanup_interval = 600 -log_level = "info" -max_peer_timeout = 900 -min_announce_interval = 120 mode = "private" -on_reverse_proxy = false -persistent_torrent_completed_stat = false -remove_peerless_torrents = true -tracker_usage_statistics = true [[udp_trackers]] -bind_address = "0.0.0.0:6969" enabled = false -[[http_trackers]] -bind_address = "0.0.0.0:7070" -enabled = false -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - [http_api] bind_address = "0.0.0.0:1212" -enabled = true -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api.access_tokens] -admin = "MyAccessToken" -[health_check_api] -bind_address = "127.0.0.1:1313" diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index df840dfb..2c4d1c0c 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -1,37 +1,6 @@ -announce_interval = 120 db_driver = "Sqlite3" db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" -external_ip = "0.0.0.0" -inactive_peer_cleanup_interval = 600 -log_level = "info" -max_peer_timeout = 900 -min_announce_interval = 120 mode = "public" -on_reverse_proxy = false -persistent_torrent_completed_stat = false -remove_peerless_torrents = true -tracker_usage_statistics = true - -[[udp_trackers]] -bind_address = "0.0.0.0:6969" -enabled = false - -[[http_trackers]] -bind_address = "0.0.0.0:7070" -enabled = false -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" [http_api] bind_address = "0.0.0.0:1212" -enabled = true -ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" -ssl_enabled = false -ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" - -[http_api.access_tokens] -admin = "MyAccessToken" - -[health_check_api] -bind_address = "127.0.0.1:1313" From 784ac54d755509f761b44e9fd135b78a9832dfbc Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Jun 2024 18:03:01 +0100 Subject: [PATCH 210/309] fix comment --- contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh index 7f94c216..024e7ae3 100755 --- a/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh @@ -52,7 +52,7 @@ TORRUST_INDEX_E2E_SHARED=true \ # Stop E2E testing environment ./contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-down.sh || exit 1 -# TEST USING A PUBLIC TRACKER +# TEST USING A PRIVATE TRACKER echo "Running E2E tests with a private tracker ..." # Start E2E testing environment From eb987e9c14a492a5af31c4917ec9b7bc02cd52b4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 16:50:00 +0100 Subject: [PATCH 211/309] feat!: [#591] config v2, tracker mode from tracker Normalize TrackerMode enum to use the same values as in the Tracker. ```rust pub enum TrackerMode { /// Will track every new info hash and serve every peer. #[serde(rename = "public")] Public, /// Will only track whitelisted info hashes. #[serde(rename = "listed")] Listed, /// Will only serve authenticated peers #[serde(rename = "private")] Private, /// Will only track whitelisted info hashes and serve authenticated peers #[serde(rename = "private_listed")] PrivateListed, } ``` That will enable to use the TrackerMode defined in https://crates.io/crates/torrust-tracker-primitives for TrackerMode in the future when a new version of that crate is released. --- .../index.private.e2e.container.sqlite3.toml | 2 +- src/config/mod.rs | 80 +++++++++---------- src/lib.rs | 4 +- .../api/server/v1/contexts/settings/mod.rs | 6 +- tests/common/contexts/settings/mod.rs | 2 +- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index fe781f5b..cc934a98 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -2,7 +2,7 @@ log_level = "info" [tracker] api_url = "http://tracker:1212" -mode = "Private" +mode = "private" url = "http://tracker:7070" [database] diff --git a/src/config/mod.rs b/src/config/mod.rs index 8ff9c08a..ccdff762 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,9 +2,9 @@ pub mod v1; pub mod validator; -use std::env; use std::str::FromStr; use std::sync::Arc; +use std::{env, fmt}; use camino::Utf8PathBuf; use figment::providers::{Env, Format, Serialized, Toml}; @@ -119,45 +119,29 @@ impl From for Error { } } -/* todo: +// todo: use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. -Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. - -Enum variants: - - In Index In Tracker -- `Public` -> `Public` -- `Private` -> `Private` -- `Whitelisted` -> `Listed` -- `PrivateWhitelisted` -> `PrivateListed` - -Enum serialized values: - - In Index In Tracker -- `Public` -> `public` -- `Private` -> `private` -- `Whitelisted` -> `listed` -- `PrivateWhitelisted` -> `private_listed` - -It's a breaking change for the toml config file en the API. - -*/ - -/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) -/// crate for more information. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +/// The mode the tracker will run in. +/// +/// Refer to [Torrust Tracker Configuration](https://docs.rs/torrust-tracker-configuration) +/// to know how to configure the tracker to run in each mode. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub enum TrackerMode { /// Will track every new info hash and serve every peer. + #[serde(rename = "public")] Public, - /// Will only serve authenticated peers. - Private, - /// Will only track whitelisted info hashes. - Whitelisted, + #[serde(rename = "listed")] + Listed, + + /// Will only serve authenticated peers + #[serde(rename = "private")] + Private, - /// Will only track whitelisted info hashes and serve authenticated peers. - PrivateWhitelisted, + /// Will only track whitelisted info hashes and serve authenticated peers + #[serde(rename = "private_listed")] + PrivateListed, } impl Default for TrackerMode { @@ -166,18 +150,28 @@ impl Default for TrackerMode { } } +impl fmt::Display for TrackerMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + TrackerMode::Public => "public", + TrackerMode::Listed => "listed", + TrackerMode::Private => "private", + TrackerMode::PrivateListed => "private_listed", + }; + write!(f, "{display_str}") + } +} + impl FromStr for TrackerMode { type Err = String; fn from_str(s: &str) -> Result { - match s { - "Public" => Ok(TrackerMode::Public), - "Private" => Ok(TrackerMode::Private), - "Whitelisted" => Ok(TrackerMode::Whitelisted), - "PrivateWhitelisted" => Ok(TrackerMode::PrivateWhitelisted), - _ => Err(format!( - "{s} is not a valid tracker mode. Valid values: 'Public', 'Private', 'Whitelisted', 'PrivateWhitelisted' " - )), + match s.to_lowercase().as_str() { + "public" => Ok(TrackerMode::Public), + "listed" => Ok(TrackerMode::Listed), + "private" => Ok(TrackerMode::Private), + "private_listed" => Ok(TrackerMode::PrivateListed), + _ => Err(format!("Unknown tracker mode: {s}")), } } } @@ -185,7 +179,7 @@ impl FromStr for TrackerMode { impl TrackerMode { #[must_use] pub fn is_open(&self) -> bool { - matches!(self, TrackerMode::Public | TrackerMode::Whitelisted) + matches!(self, TrackerMode::Public | TrackerMode::Listed) } #[must_use] @@ -336,7 +330,7 @@ mod tests { [tracker] url = "udp://localhost:6969" - mode = "Public" + mode = "public" api_url = "http://localhost:1212/" token = "MyAccessToken" token_valid_seconds = 7257600 diff --git a/src/lib.rs b/src/lib.rs index c3148e86..0a26c192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,7 @@ //! ```toml //! [tracker] //! url = "udp://localhost:6969" -//! mode = "Public" +//! mode = "public" //! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 @@ -171,7 +171,7 @@ //! //! [tracker] //! url = "udp://localhost:6969" -//! mode = "Public" +//! mode = "public" //! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index fe9aeab3..089137e6 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -35,7 +35,7 @@ //! }, //! "tracker": { //! "url": "udp://localhost:6969", -//! "mode": "Public", +//! "mode": "public", //! "api_url": "http://localhost:1212/", //! "token": "MyAccessToken", //! "token_valid_seconds": 7257600 @@ -102,7 +102,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"Public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! @@ -158,7 +158,7 @@ //! "data": { //! "website_name": "Torrust", //! "tracker_url": "udp://localhost:6969", -//! "tracker_mode": "Public", +//! "tracker_mode": "public", //! "email_on_signup": "Optional" //! } //! } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index b72ca7d5..a3e1b080 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -113,7 +113,7 @@ impl From for Tracker { fn from(tracker: DomainTracker) -> Self { Self { url: tracker.url, - mode: format!("{:?}", tracker.mode), + mode: tracker.mode.to_string(), api_url: tracker.api_url, token: tracker.token, token_valid_seconds: tracker.token_valid_seconds, From bb75303e8a4d61541e45a52cf5e7a7116ee5919f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:07:36 +0100 Subject: [PATCH 212/309] feat!: [#591] lowercase for auth::enail_on_signup emnum variants --- src/config/mod.rs | 2 +- src/config/v1/auth.rs | 26 +++++++++++++++++++ src/lib.rs | 2 +- .../api/server/v1/contexts/settings/mod.rs | 6 ++--- src/web/api/server/v1/contexts/user/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 2 +- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index ccdff762..9c75a557 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -339,7 +339,7 @@ mod tests { port = 3001 [auth] - email_on_signup = "Optional" + email_on_signup = "optional" min_password_length = 6 max_password_length = 64 secret_key = "MaxVerstappenWC2021" diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 6b7f0786..29bfaa59 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::str::FromStr; use serde::{Deserialize, Serialize}; @@ -50,6 +51,7 @@ impl Auth { /// Whether the email is required on signup or not. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] pub enum EmailOnSignup { /// The email is required on signup. Required, @@ -65,6 +67,30 @@ impl Default for EmailOnSignup { } } +impl fmt::Display for EmailOnSignup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + EmailOnSignup::Required => "required", + EmailOnSignup::Optional => "optional", + EmailOnSignup::None => "none", + }; + write!(f, "{display_str}") + } +} + +impl FromStr for EmailOnSignup { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "required" => Ok(EmailOnSignup::Required), + "optional" => Ok(EmailOnSignup::Optional), + "none" => Ok(EmailOnSignup::None), + _ => Err(format!("Unknown config 'email_on_signup' option (required, optional, none): {s}")), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SecretKey(String); diff --git a/src/lib.rs b/src/lib.rs index 0a26c192..fa7fc351 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ //! port = 3001 //! //! [auth] -//! email_on_signup = "Optional" +//! email_on_signup = "optional" //! min_password_length = 6 //! max_password_length = 64 //! secret_key = "MaxVerstappenWC2021" diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index 089137e6..b30cd96b 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -45,7 +45,7 @@ //! "base_url": null //! }, //! "auth": { -//! "email_on_signup": "Optional", +//! "email_on_signup": "optional", //! "min_password_length": 6, //! "max_password_length": 64, //! "secret_key": "MaxVerstappenWC2021" @@ -102,7 +102,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! @@ -159,7 +159,7 @@ //! "website_name": "Torrust", //! "tracker_url": "udp://localhost:6969", //! "tracker_mode": "public", -//! "email_on_signup": "Optional" +//! "email_on_signup": "optional" //! } //! } //! ``` diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index a13c2bb8..2e3ce8d0 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,7 +45,7 @@ //! //! ```toml //! [auth] -//! email_on_signup = "Optional" +//! email_on_signup = "optional" //! min_password_length = 6 //! max_password_length = 64 //! ``` diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index a3e1b080..8ca66a66 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -133,7 +133,7 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - email_on_signup: format!("{:?}", auth.email_on_signup), + email_on_signup: auth.email_on_signup.to_string(), min_password_length: auth.min_password_length, max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), From 82fc32cd9f3147d4f987a1e49b983296e857fea0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:09:32 +0100 Subject: [PATCH 213/309] feat!: [#591] rename enum variant EmailOnSignup::None to Ignored --- src/config/v1/auth.rs | 10 ++++++---- src/services/user.rs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 29bfaa59..0a685757 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -58,7 +58,7 @@ pub enum EmailOnSignup { /// The email is optional on signup. Optional, /// The email is not allowed on signup. It will only be ignored if provided. - None, // code-review: rename to `Ignored`? + Ignored, } impl Default for EmailOnSignup { @@ -72,7 +72,7 @@ impl fmt::Display for EmailOnSignup { let display_str = match self { EmailOnSignup::Required => "required", EmailOnSignup::Optional => "optional", - EmailOnSignup::None => "none", + EmailOnSignup::Ignored => "ignored", }; write!(f, "{display_str}") } @@ -85,8 +85,10 @@ impl FromStr for EmailOnSignup { match s.to_lowercase().as_str() { "required" => Ok(EmailOnSignup::Required), "optional" => Ok(EmailOnSignup::Optional), - "none" => Ok(EmailOnSignup::None), - _ => Err(format!("Unknown config 'email_on_signup' option (required, optional, none): {s}")), + "none" => Ok(EmailOnSignup::Ignored), + _ => Err(format!( + "Unknown config 'email_on_signup' option (required, optional, ignored): {s}" + )), } } } diff --git a/src/services/user.rs b/src/services/user.rs index e808548e..4d521029 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -86,7 +86,7 @@ impl RegistrationService { } registration_form.email.clone() } - EmailOnSignup::None => None, + EmailOnSignup::Ignored => None, EmailOnSignup::Optional => registration_form.email.clone(), }; From 50ebb9a754948506637c3120931ddc86a8fa2770 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:34:22 +0100 Subject: [PATCH 214/309] feat!: [#591] move log_level into a new logging section in config Old: ```toml log_level = "info" [website] name = "Torrust" ``` New: ```toml [logging] log_level = "info" [website] name = "Torrust" ``` And the value is not Optional anymore. It was a Optional but when it was None it defaulted to LogLevel::Info. In practice that means is mandatory but with the `Info` default value. --- .../default/config/index.container.mysql.toml | 1 + .../config/index.container.sqlite3.toml | 1 + .../config/index.development.sqlite3.toml | 1 + .../index.private.e2e.container.sqlite3.toml | 1 + .../index.public.e2e.container.mysql.toml | 1 + .../index.public.e2e.container.sqlite3.toml | 1 + src/app.rs | 2 +- src/bootstrap/logging.rs | 20 +---- src/config/mod.rs | 6 +- src/config/v1/logging.rs | 76 +++++++++++++++++++ src/config/v1/mod.rs | 24 +----- .../tracker_statistics_importer/app.rs | 2 +- tests/common/contexts/settings/mod.rs | 19 ++++- tests/environments/isolated.rs | 9 +-- 14 files changed, 117 insertions(+), 47 deletions(-) create mode 100644 src/config/v1/logging.rs diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 5564fdaf..b6d3585d 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [database] diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 534578ed..319ebd16 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [database] diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 8e188d5e..c2b8b582 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" # Uncomment if you want to enable TSL for development diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index cc934a98..f06dece3 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 16eb9f04..88ccd69b 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index c9e764bf..1ef9fb14 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/src/app.rs b/src/app.rs index 93934f6e..44cc33fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,7 +38,7 @@ pub struct Running { /// It panics if there is an error connecting to the database. #[allow(clippy::too_many_lines)] pub async fn run(configuration: Configuration, api_version: &Version) -> Running { - let log_level = configuration.settings.read().await.log_level.clone(); + let log_level = configuration.settings.read().await.logging.log_level.clone(); logging::setup(&log_level); diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index 4c02d6ee..e4697bd7 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -11,12 +11,12 @@ use std::sync::Once; use tracing::info; use tracing::level_filters::LevelFilter; -use crate::config::v1::LogLevel; +use crate::config::v1::logging::LogLevel; static INIT: Once = Once::new(); -pub fn setup(log_level: &Option) { - let tracing_level = config_level_or_default(log_level); +pub fn setup(log_level: &LogLevel) { + let tracing_level: LevelFilter = log_level.clone().into(); if tracing_level == LevelFilter::OFF { return; @@ -27,20 +27,6 @@ pub fn setup(log_level: &Option) { }); } -fn config_level_or_default(log_level: &Option) -> LevelFilter { - match log_level { - None => LevelFilter::INFO, - Some(level) => match level { - LogLevel::Off => LevelFilter::OFF, - LogLevel::Error => LevelFilter::ERROR, - LogLevel::Warn => LevelFilter::WARN, - LogLevel::Info => LevelFilter::INFO, - LogLevel::Debug => LevelFilter::DEBUG, - LogLevel::Trace => LevelFilter::TRACE, - }, - } -} - fn tracing_stdout_init(filter: LevelFilter, style: &TraceStyle) { let builder = tracing_subscriber::fmt().with_max_level(filter); diff --git a/src/config/mod.rs b/src/config/mod.rs index 9c75a557..275732da 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -27,6 +27,7 @@ pub type Mail = v1::mail::Mail; pub type Network = v1::net::Network; pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; pub type Tracker = v1::tracker::Tracker; +pub type Logging = v1::logging::Logging; pub type Website = v1::website::Website; pub type EmailOnSignup = v1::auth::EmailOnSignup; @@ -325,7 +326,10 @@ mod tests { #[cfg(test)] fn default_config_toml() -> String { - let config = r#"[website] + let config = r#"[logging] + log_level = "info" + + [website] name = "Torrust" [tracker] diff --git a/src/config/v1/logging.rs b/src/config/v1/logging.rs new file mode 100644 index 00000000..93387286 --- /dev/null +++ b/src/config/v1/logging.rs @@ -0,0 +1,76 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; +use tracing::level_filters::LevelFilter; + +/// Core configuration for the API +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Logging { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. + #[serde(default = "Logging::default_log_level")] + pub log_level: LogLevel, +} + +impl Default for Logging { + fn default() -> Self { + Self { + log_level: Logging::default_log_level(), + } + } +} + +impl Logging { + fn default_log_level() -> LogLevel { + LogLevel::Info + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} + +impl Default for LogLevel { + fn default() -> Self { + Self::Info + } +} + +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + LogLevel::Off => "off", + LogLevel::Error => "error", + LogLevel::Warn => "warn", + LogLevel::Info => "info", + LogLevel::Debug => "debug", + LogLevel::Trace => "trace", + }; + write!(f, "{display_str}") + } +} + +impl From for LevelFilter { + fn from(log_level: LogLevel) -> Self { + match log_level { + LogLevel::Off => LevelFilter::OFF, + LogLevel::Error => LevelFilter::ERROR, + LogLevel::Warn => LevelFilter::WARN, + LogLevel::Info => LevelFilter::INFO, + LogLevel::Debug => LevelFilter::DEBUG, + LogLevel::Trace => LevelFilter::TRACE, + } + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index d788494b..ada1648c 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -2,12 +2,14 @@ pub mod api; pub mod auth; pub mod database; pub mod image_cache; +pub mod logging; pub mod mail; pub mod net; pub mod tracker; pub mod tracker_statistics_importer; pub mod website; +use logging::Logging; use serde::{Deserialize, Serialize}; use self::api::Api; @@ -24,10 +26,9 @@ use super::validator::{ValidationError, Validator}; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. + /// The logging configuration. #[serde(default)] - pub log_level: Option, + pub logging: Logging, /// The website customizable values. #[serde(default)] pub website: Website, @@ -73,20 +74,3 @@ impl Validator for Settings { self.tracker.validate() } } - -#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] -#[serde(rename_all = "lowercase")] -pub enum LogLevel { - /// A level lower than all log levels. - Off, - /// Corresponds to the `Error` log level. - Error, - /// Corresponds to the `Warn` log level. - Warn, - /// Corresponds to the `Info` log level. - Info, - /// Corresponds to the `Debug` log level. - Debug, - /// Corresponds to the `Trace` log level. - Trace, -} diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index edb43304..f9d9d4f9 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -90,7 +90,7 @@ pub async fn import() { let configuration = initialize_configuration(); - let log_level = configuration.settings.read().await.log_level.clone(); + let log_level = configuration.settings.read().await.logging.log_level.clone(); logging::setup(&log_level); diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 8ca66a66..6e138f8d 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -3,14 +3,15 @@ pub mod responses; use serde::{Deserialize, Serialize}; use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ - Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, + Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, + Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Settings { + pub logging: Logging, pub website: Website, pub tracker: Tracker, pub net: Network, @@ -22,6 +23,11 @@ pub struct Settings { pub tracker_statistics_importer: TrackerStatisticsImporter, } +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Logging { + pub log_level: String, +} + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Website { pub name: String, @@ -90,6 +96,7 @@ pub struct TrackerStatisticsImporter { impl From for Settings { fn from(settings: DomainSettings) -> Self { Settings { + logging: Logging::from(settings.logging), website: Website::from(settings.website), tracker: Tracker::from(settings.tracker), net: Network::from(settings.net), @@ -103,6 +110,14 @@ impl From for Settings { } } +impl From for Logging { + fn from(logging: DomainLogging) -> Self { + Self { + log_level: logging.log_level.to_string(), + } + } +} + impl From for Website { fn from(website: DomainWebsite) -> Self { Self { name: website.name } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index d74ee48e..91fb1345 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,6 +1,6 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::LogLevel; +use torrust_index::config::v1::logging::LogLevel; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; use url::Url; @@ -72,10 +72,9 @@ impl Default for TestEnv { /// Provides a configuration with ephemeral data for testing. fn ephemeral(temp_dir: &TempDir) -> config::Settings { - let mut configuration = config::Settings { - log_level: Some(LogLevel::Off), // Change to `debug` for tests debugging - ..config::Settings::default() - }; + let mut configuration = config::Settings::default(); + + configuration.logging.log_level = LogLevel::Off; // Change to `debug` for tests debugging // Ephemeral API port configuration.net.port = FREE_PORT; From cd8248af42d2e26c801677e9fd9791c01ff157b3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 18:39:35 +0100 Subject: [PATCH 215/309] feat!: [#591] extract new type for password constrains in configuration From: ```toml [auth] email_on_signup = "Optional" max_password_length = 64 min_password_length = 6 secret_key = "MaxVerstappenWC2021" ``` To: ```toml [auth] email_on_signup = "Optional" secret_key = "MaxVerstappenWC2021" [auth.password_constraints] max_password_length = 64 min_password_length = 6 ``` --- src/config/mod.rs | 7 ++- src/config/v1/auth.rs | 55 ++++++++++++++----- src/lib.rs | 4 +- src/services/user.rs | 21 +++---- .../api/client/v1/contexts/settings/mod.rs | 23 ++++++-- src/web/api/server/v1/contexts/user/mod.rs | 5 +- tests/common/contexts/settings/mod.rs | 23 ++++++-- 7 files changed, 94 insertions(+), 44 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 275732da..1bc8b407 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,6 +30,7 @@ pub type Tracker = v1::tracker::Tracker; pub type Logging = v1::logging::Logging; pub type Website = v1::website::Website; pub type EmailOnSignup = v1::auth::EmailOnSignup; +pub type PasswordConstraints = v1::auth::PasswordConstraints; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -344,10 +345,12 @@ mod tests { [auth] email_on_signup = "optional" - min_password_length = 6 - max_password_length = 64 secret_key = "MaxVerstappenWC2021" + [auth.password_constraints] + min_password_length = 6 + max_password_length = 64 + [database] connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 0a685757..007d3084 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -7,25 +7,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// Whether or not to require an email on signup. - #[serde(default = "EmailOnSignup::default")] + #[serde(default = "Auth::default_email_on_signup")] pub email_on_signup: EmailOnSignup, - /// The minimum password length. - #[serde(default = "Auth::default_min_password_length")] - pub min_password_length: usize, - /// The maximum password length. - #[serde(default = "Auth::default_max_password_length")] - pub max_password_length: usize, /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, + /// The password constraints + #[serde(default = "Auth::default_password_constraints")] + pub password_constraints: PasswordConstraints, } impl Default for Auth { fn default() -> Self { Self { email_on_signup: EmailOnSignup::default(), - min_password_length: Self::default_min_password_length(), - max_password_length: Self::default_max_password_length(), + password_constraints: Self::default_password_constraints(), secret_key: Self::default_secret_key(), } } @@ -36,17 +32,17 @@ impl Auth { self.secret_key = SecretKey::new(secret_key); } - fn default_min_password_length() -> usize { - 6 - } - - fn default_max_password_length() -> usize { - 64 + fn default_email_on_signup() -> EmailOnSignup { + EmailOnSignup::default() } fn default_secret_key() -> SecretKey { SecretKey::new("MaxVerstappenWC2021") } + + fn default_password_constraints() -> PasswordConstraints { + PasswordConstraints::default() + } } /// Whether the email is required on signup or not. @@ -119,6 +115,35 @@ impl fmt::Display for SecretKey { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PasswordConstraints { + /// The minimum password length. + #[serde(default = "PasswordConstraints::default_min_password_length")] + pub min_password_length: usize, + /// The maximum password length. + #[serde(default = "PasswordConstraints::default_max_password_length")] + pub max_password_length: usize, +} + +impl Default for PasswordConstraints { + fn default() -> Self { + Self { + min_password_length: Self::default_min_password_length(), + max_password_length: Self::default_max_password_length(), + } + } +} + +impl PasswordConstraints { + fn default_min_password_length() -> usize { + 6 + } + + fn default_max_password_length() -> usize { + 64 + } +} + #[cfg(test)] mod tests { use super::SecretKey; diff --git a/src/lib.rs b/src/lib.rs index fa7fc351..0d7cd435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,9 +181,11 @@ //! //! [auth] //! email_on_signup = "optional" +//! secret_key = "MaxVerstappenWC2021" +//! +//! [auth.password_constraints] //! min_password_length = 6 //! max_password_length = 64 -//! secret_key = "MaxVerstappenWC2021" //! //! [database] //! connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/services/user.rs b/src/services/user.rs index 4d521029..6d0e4c4e 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -11,7 +11,7 @@ use pbkdf2::password_hash::rand_core::OsRng; use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; -use crate::config::{Configuration, EmailOnSignup}; +use crate::config::{Configuration, EmailOnSignup, PasswordConstraints}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::mailer; @@ -97,11 +97,11 @@ impl RegistrationService { } let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( ®istration_form.password, ®istration_form.confirm_password, &password_constraints, @@ -216,11 +216,11 @@ impl ProfileService { verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?; let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( &change_password_form.password, &change_password_form.confirm_password, &password_constraints, @@ -415,12 +415,7 @@ impl DbBannedUserList { } } -struct PasswordConstraints { - pub min_password_length: usize, - pub max_password_length: usize, -} - -fn validate_password_constrains( +fn validate_password_constraints( password: &str, confirm_password: &str, password_rules: &PasswordConstraints, diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index d762a98f..3a8ea60f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -6,8 +6,8 @@ use url::Url; use crate::config::v1::tracker::ApiToken; use crate::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -46,9 +46,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -135,9 +140,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: format!("{:?}", auth.email_on_signup), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } } diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index 2e3ce8d0..1d2eb2a3 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,9 +45,8 @@ //! //! ```toml //! [auth] -//! email_on_signup = "optional" -//! min_password_length = 6 -//! max_password_length = 64 +//! email_on_signup = "Optional" +//! secret_key = "MaxVerstappenWC2021" //! ``` //! //! Refer to the [`RegistrationForm`](crate::web::api::server::v1::contexts::user::forms::RegistrationForm) diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 6e138f8d..74235bd2 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, - Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Mail as DomainMail, Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; @@ -51,9 +51,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -149,9 +154,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: auth.email_on_signup.to_string(), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } } From 1bc00ce07eadfaa366d6c7e7cb44c38e4b84c1f2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 15:53:22 +0100 Subject: [PATCH 216/309] feat!: [#591] extract new types for SMTP server configuration From: ```toml [mail] email_verification_enabled = false from = "example@email.com" password = "" port = 25 reply_to = "noreply@email.com" server = "" username = "" ``` To: ```toml [mail] email_verification_enabled = false from = "example@email.com" reply_to = "noreply@email.com" [mail.smtp] port = 25 server = "" [mail.smtp.credentials] password = "" username = "" ``` --- .../default/config/index.container.mysql.toml | 3 +- .../config/index.container.sqlite3.toml | 2 +- .../index.private.e2e.container.sqlite3.toml | 2 +- .../index.public.e2e.container.mysql.toml | 2 +- .../index.public.e2e.container.sqlite3.toml | 2 +- src/bootstrap/logging.rs | 2 +- src/config/mod.rs | 33 ++++++-- src/config/v1/mail.rs | 84 ++++++++++++++----- src/config/v1/mod.rs | 2 +- src/lib.rs | 10 ++- src/mailer.rs | 15 ++-- .../api/client/v1/contexts/settings/mod.rs | 45 ++++++++-- tests/common/contexts/settings/mod.rs | 46 +++++++--- tests/e2e/environment.rs | 5 +- tests/environments/isolated.rs | 3 +- 15 files changed, 188 insertions(+), 68 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index b6d3585d..9a53efb6 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -4,6 +4,7 @@ log_level = "info" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" + diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 319ebd16..5abf23f4 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -4,6 +4,6 @@ log_level = "info" [database] connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index f06dece3..8747c579 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -9,6 +9,6 @@ url = "http://tracker:7070" [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 88ccd69b..0382c12c 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -8,6 +8,6 @@ url = "udp://tracker:6969" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 1ef9fb14..e04d0ef6 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -8,6 +8,6 @@ url = "udp://tracker:6969" [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index e4697bd7..d15c676b 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -11,7 +11,7 @@ use std::sync::Once; use tracing::info; use tracing::level_filters::LevelFilter; -use crate::config::v1::logging::LogLevel; +use crate::config::LogLevel; static INIT: Once = Once::new(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 1bc8b407..2e2b75ce 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,18 +19,33 @@ use url::Url; use crate::web::api::server::DynError; pub type Settings = v1::Settings; + pub type Api = v1::api::Api; + pub type Auth = v1::auth::Auth; +pub type EmailOnSignup = v1::auth::EmailOnSignup; +pub type SecretKey = v1::auth::SecretKey; +pub type PasswordConstraints = v1::auth::PasswordConstraints; + pub type Database = v1::database::Database; + pub type ImageCache = v1::image_cache::ImageCache; + pub type Mail = v1::mail::Mail; +pub type Smtp = v1::mail::Smtp; +pub type Credentials = v1::mail::Credentials; + pub type Network = v1::net::Network; + pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; + pub type Tracker = v1::tracker::Tracker; +pub type ApiToken = v1::tracker::ApiToken; + pub type Logging = v1::logging::Logging; +pub type LogLevel = v1::logging::LogLevel; + pub type Website = v1::website::Website; -pub type EmailOnSignup = v1::auth::EmailOnSignup; -pub type PasswordConstraints = v1::auth::PasswordConstraints; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -321,9 +336,7 @@ mod tests { use url::Url; - use crate::config::v1::auth::SecretKey; - use crate::config::v1::tracker::ApiToken; - use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; + use crate::config::{ApiToken, Configuration, ConfigurationPublic, Info, SecretKey, Settings}; #[cfg(test)] fn default_config_toml() -> String { @@ -358,10 +371,14 @@ mod tests { email_verification_enabled = false from = "example@email.com" reply_to = "noreply@email.com" - username = "" - password = "" - server = "" + + [mail.smtp] port = 25 + server = "" + + [mail.smtp.credentials] + password = "" + username = "" [image_cache] max_request_timeout_ms = 1000 diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 885a4383..7675ceac 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -13,18 +13,9 @@ pub struct Mail { /// The email address to reply to. #[serde(default = "Mail::default_reply_to")] pub reply_to: Mailbox, - /// The username to use for SMTP authentication. - #[serde(default = "Mail::default_username")] - pub username: String, - /// The password to use for SMTP authentication. - #[serde(default = "Mail::default_password")] - pub password: String, - /// The SMTP server to use. - #[serde(default = "Mail::default_server")] - pub server: String, - /// The SMTP port to use. - #[serde(default = "Mail::default_port")] - pub port: u16, + /// The SMTP server configuration. + #[serde(default = "Mail::default_smtp")] + pub smtp: Smtp, } impl Default for Mail { @@ -33,10 +24,7 @@ impl Default for Mail { email_verification_enabled: Self::default_email_verification_enabled(), from: Self::default_from(), reply_to: Self::default_reply_to(), - username: Self::default_username(), - password: Self::default_password(), - server: Self::default_server(), - port: Self::default_port(), + smtp: Self::default_smtp(), } } } @@ -54,14 +42,36 @@ impl Mail { "noreply@email.com".parse().expect("valid mailbox") } - fn default_username() -> String { - String::default() + fn default_smtp() -> Smtp { + Smtp::default() } +} - fn default_password() -> String { - String::default() +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Smtp { + /// The SMTP port to use. + #[serde(default = "Smtp::default_port")] + pub port: u16, + /// The SMTP server to use. + #[serde(default = "Smtp::default_server")] + pub server: String, + /// The SMTP server credentials. + #[serde(default = "Smtp::default_credentials")] + pub credentials: Credentials, +} + +impl Default for Smtp { + fn default() -> Self { + Self { + server: Self::default_server(), + port: Self::default_port(), + credentials: Self::default_credentials(), + } } +} +impl Smtp { fn default_server() -> String { String::default() } @@ -69,4 +79,38 @@ impl Mail { fn default_port() -> u16 { 25 } + + fn default_credentials() -> Credentials { + Credentials::default() + } +} + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Credentials { + /// The password to use for SMTP authentication. + #[serde(default = "Credentials::default_password")] + pub password: String, + /// The username to use for SMTP authentication. + #[serde(default = "Credentials::default_username")] + pub username: String, +} + +impl Default for Credentials { + fn default() -> Self { + Self { + username: Self::default_username(), + password: Self::default_password(), + } + } +} + +impl Credentials { + fn default_username() -> String { + String::default() + } + + fn default_password() -> String { + String::default() + } } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index ada1648c..1820c923 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -64,7 +64,7 @@ impl Settings { if let Some(_password) = self.database.connect_url.password() { let _ = self.database.connect_url.set_password(Some("***")); } - "***".clone_into(&mut self.mail.password); + "***".clone_into(&mut self.mail.smtp.credentials.password); self.auth.secret_key = SecretKey::new("***"); } } diff --git a/src/lib.rs b/src/lib.rs index 0d7cd435..c06d407f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -194,10 +194,14 @@ //! email_verification_enabled = false //! from = "example@email.com" //! reply_to = "noreply@email.com" -//! username = "" -//! password = "" -//! server = "" +//! +//! [mail.smtp] //! port = 25 +//! server = "" +//! +//! [mail.smtp.credentials] +//! password = "" +//! username = "" //! //! [image_cache] //! max_request_timeout_ms = 1000 diff --git a/src/mailer.rs b/src/mailer.rs index 36134b14..0eb5f35b 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -70,19 +70,22 @@ impl Service { async fn get_mailer(cfg: &Configuration) -> Mailer { let settings = cfg.settings.read().await; - if !settings.mail.username.is_empty() && !settings.mail.password.is_empty() { + if !settings.mail.smtp.credentials.username.is_empty() && !settings.mail.smtp.credentials.password.is_empty() { // SMTP authentication - let creds = Credentials::new(settings.mail.username.clone(), settings.mail.password.clone()); + let creds = Credentials::new( + settings.mail.smtp.credentials.username.clone(), + settings.mail.smtp.credentials.password.clone(), + ); - AsyncSmtpTransport::::builder_dangerous(&settings.mail.server) - .port(settings.mail.port) + AsyncSmtpTransport::::builder_dangerous(&settings.mail.smtp.server) + .port(settings.mail.smtp.port) .credentials(creds) .authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain]) .build() } else { // SMTP without authentication - AsyncSmtpTransport::::builder_dangerous(&settings.mail.server) - .port(settings.mail.port) + AsyncSmtpTransport::::builder_dangerous(&settings.mail.smtp.server) + .port(settings.mail.smtp.port) .build() } } diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 3a8ea60f..e9ba4974 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -5,9 +5,10 @@ use url::Url; use crate::config::v1::tracker::ApiToken; use crate::config::{ - Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, - Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Api as DomainApi, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, + ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, + PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, Smtp as DomainSmtp, Tracker as DomainTracker, + TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -66,10 +67,20 @@ pub struct Mail { pub email_verification_enabled: bool, pub from: String, pub reply_to: String, - pub username: String, - pub password: String, + pub smtp: Smtp, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Smtp { pub server: String, pub port: u16, + pub credentials: Credentials, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Credentials { + pub username: String, + pub password: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -169,10 +180,26 @@ impl From for Mail { email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), - username: mail.username, - password: mail.password, - server: mail.server, - port: mail.port, + smtp: Smtp::from(mail.smtp), + } + } +} + +impl From for Smtp { + fn from(smtp: DomainSmtp) -> Self { + Self { + server: smtp.server, + port: smtp.port, + credentials: Credentials::from(smtp.credentials), + } + } +} + +impl From for Credentials { + fn from(credentials: DomainCredentials) -> Self { + Self { + username: credentials.username, + password: credentials.password, } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 74235bd2..29763e13 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -1,11 +1,11 @@ pub mod responses; use serde::{Deserialize, Serialize}; -use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ - Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, - Mail as DomainMail, Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, - Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Api as DomainApi, ApiToken, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, + ImageCache as DomainImageCache, Logging as DomainLogging, Mail as DomainMail, Network as DomainNetwork, + PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, Smtp as DomainSmtp, Tracker as DomainTracker, + TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; @@ -71,10 +71,20 @@ pub struct Mail { pub email_verification_enabled: bool, pub from: String, pub reply_to: String, - pub username: String, - pub password: String, + pub smtp: Smtp, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Smtp { pub server: String, pub port: u16, + pub credentials: Credentials, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Credentials { + pub username: String, + pub password: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -183,10 +193,26 @@ impl From for Mail { email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), - username: mail.username, - password: mail.password, - server: mail.server, - port: mail.port, + smtp: mail.smtp.into(), + } + } +} + +impl From for Smtp { + fn from(smtp: DomainSmtp) -> Self { + Self { + server: smtp.server, + port: smtp.port, + credentials: smtp.credentials.into(), + } + } +} + +impl From for Credentials { + fn from(credentials: DomainCredentials) -> Self { + Self { + username: credentials.username, + password: credentials.password, } } } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 224ca55a..65b7a116 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,8 +1,7 @@ use std::env; use std::str::FromStr; -use torrust_index::config::v1::tracker::ApiToken; -use torrust_index::config::TrackerMode; +use torrust_index::config::{ApiToken, TrackerMode}; use torrust_index::web::api::Version; use url::Url; @@ -116,7 +115,7 @@ impl TestEnv { settings.tracker.token = ApiToken::new("***"); - "***".clone_into(&mut settings.mail.password); + "***".clone_into(&mut settings.mail.smtp.credentials.password); "***".clone_into(&mut settings.auth.secret_key); diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 91fb1345..34635f99 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,7 +1,6 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::logging::LogLevel; -use torrust_index::config::FREE_PORT; +use torrust_index::config::{LogLevel, FREE_PORT}; use torrust_index::web::api::Version; use url::Url; From 52f3d9c16dccf4df4df688a40e542a1e44ac2238 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:11:30 +0100 Subject: [PATCH 217/309] feat!: [#591] inject full socket address in configuration not only the port. Old: ```toml [net] port = 3001 ``` New: ```toml [net] bind_address = "0.0.0.0:3001" ``` --- src/app.rs | 4 ++-- src/config/mod.rs | 2 +- src/config/v1/net.rs | 21 +++++++++++++++---- src/lib.rs | 2 +- .../api/client/v1/contexts/settings/mod.rs | 6 ++++-- tests/common/contexts/settings/mod.rs | 6 ++++-- tests/environments/isolated.rs | 4 +++- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index 44cc33fc..58d1bee1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,8 +57,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; let importer_port = settings.tracker_statistics_importer.port; // From [net] config - let net_ip = "0.0.0.0".to_string(); - let net_port = settings.net.port; + let net_ip = settings.net.bind_address.ip().to_string(); + let net_port = settings.net.bind_address.port(); let opt_net_tsl = settings.net.tsl.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that diff --git a/src/config/mod.rs b/src/config/mod.rs index 2e2b75ce..84c7681f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -354,7 +354,7 @@ mod tests { token_valid_seconds = 7257600 [net] - port = 3001 + bind_address = "0.0.0.0:3001" [auth] email_on_signup = "optional" diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index 4c1552a2..3df9fc23 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -1,3 +1,5 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use serde::{Deserialize, Serialize}; use url::Url; @@ -11,13 +13,16 @@ use crate::config::Tsl; /// via port 443, which is a very common setup. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Network { - /// The port to listen on. Default to `3001`. - #[serde(default = "Network::default_port")] - pub port: u16, /// The base URL for the API. For example: `http://localhost`. /// If not set, the base URL will be inferred from the request. #[serde(default = "Network::default_base_url")] pub base_url: Option, + /// The address the tracker will bind to. + /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to + /// listen to all interfaces, use `0.0.0.0`. If you want the operating + /// system to choose a random port, use port `0`. + #[serde(default = "Network::default_bind_address")] + pub bind_address: SocketAddr, /// TSL configuration. #[serde(default = "Network::default_tsl")] pub tsl: Option, @@ -26,7 +31,7 @@ pub struct Network { impl Default for Network { fn default() -> Self { Self { - port: Self::default_port(), + bind_address: Self::default_bind_address(), base_url: Self::default_base_url(), tsl: Self::default_tsl(), } @@ -34,6 +39,14 @@ impl Default for Network { } impl Network { + fn default_bind_address() -> SocketAddr { + SocketAddr::new(Self::default_ip(), Self::default_port()) + } + + fn default_ip() -> IpAddr { + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) + } + fn default_port() -> u16 { 3001 } diff --git a/src/lib.rs b/src/lib.rs index c06d407f..d42990d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,7 +177,7 @@ //! token_valid_seconds = 7257600 //! //! [net] -//! port = 3001 +//! bind_address = "0.0.0.0:3001" //! //! [auth] //! email_on_signup = "optional" diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index e9ba4974..4c1b371f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -1,5 +1,7 @@ pub mod responses; +use std::net::SocketAddr; + use serde::{Deserialize, Serialize}; use url::Url; @@ -40,8 +42,8 @@ pub struct Tracker { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Network { - pub port: u16, pub base_url: Option, + pub bind_address: SocketAddr, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -141,8 +143,8 @@ impl From for Tracker { impl From for Network { fn from(net: DomainNetwork) -> Self { Self { - port: net.port, base_url: net.base_url.map(|url_without_port| url_without_port.to_string()), + bind_address: net.bind_address, } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 29763e13..22f33aa3 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -1,5 +1,7 @@ pub mod responses; +use std::net::SocketAddr; + use serde::{Deserialize, Serialize}; use torrust_index::config::{ Api as DomainApi, ApiToken, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, @@ -44,8 +46,8 @@ pub struct Tracker { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Network { - pub port: u16, pub base_url: Option, + pub bind_address: SocketAddr, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -154,7 +156,7 @@ impl From for Tracker { impl From for Network { fn from(net: DomainNetwork) -> Self { Self { - port: net.port, + bind_address: net.bind_address, base_url: net.base_url.as_ref().map(std::string::ToString::to_string), } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 34635f99..c9fbeadb 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,3 +1,5 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use tempfile::TempDir; use torrust_index::config; use torrust_index::config::{LogLevel, FREE_PORT}; @@ -76,7 +78,7 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { configuration.logging.log_level = LogLevel::Off; // Change to `debug` for tests debugging // Ephemeral API port - configuration.net.port = FREE_PORT; + configuration.net.bind_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), FREE_PORT); // Ephemeral Importer API port configuration.tracker_statistics_importer.port = FREE_PORT; From cb8935b8638223f6af8dfbcd5df03b051ed735d1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:22:29 +0100 Subject: [PATCH 218/309] feat!: [#591] use full socket addr in net config instead of only the port Old: ```toml [net] port = 3001 ``` New: ```toml [net] bind_address = "0.0.0.0:3001" ``` --- src/app.rs | 5 ++--- src/web/api/mod.rs | 5 ++--- src/web/api/server/mod.rs | 10 +++------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index 58d1bee1..954f7cb4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,8 +57,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; let importer_port = settings.tracker_statistics_importer.port; // From [net] config - let net_ip = settings.net.bind_address.ip().to_string(); - let net_port = settings.net.bind_address.port(); + let config_bind_address = settings.net.bind_address; let opt_net_tsl = settings.net.tsl.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that @@ -181,7 +180,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running ); // Start API server - let running_api = web::api::start(app_data, &net_ip, net_port, opt_net_tsl, api_version).await; + let running_api = web::api::start(app_data, config_bind_address, opt_net_tsl, api_version).await; // Full running application Running { diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index b1ac0601..6615a5d5 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -38,12 +38,11 @@ pub struct Running { #[must_use] pub async fn start( app_data: Arc, - net_ip: &str, - net_port: u16, + config_bind_address: SocketAddr, opt_tsl: Option, implementation: &Version, ) -> api::Running { match implementation { - Version::V1 => server::start(app_data, net_ip, net_port, opt_tsl).await, + Version::V1 => server::start(app_data, config_bind_address, opt_tsl).await, } } diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index 1349b54f..dee44ca1 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -28,11 +28,7 @@ pub type DynError = Arc; /// # Panics /// /// Panics if the API server can't be started. -pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, opt_tsl: Option) -> Running { - let config_socket_addr: SocketAddr = format!("{net_ip}:{net_port}") - .parse() - .expect("API server socket address to be valid."); - +pub async fn start(app_data: Arc, config_bind_address: SocketAddr, opt_tsl: Option) -> Running { let opt_rust_tls_config = make_rust_tls(&opt_tsl) .await .map(|tls| tls.expect("it should have a valid net tls configuration")); @@ -42,9 +38,9 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, opt_tsl: // Run the API server let join_handle = tokio::spawn(async move { - info!("Starting API server with net config: {} ...", config_socket_addr); + info!("Starting API server with net config: {} ...", config_bind_address); - start_server(config_socket_addr, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await; + start_server(config_bind_address, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await; info!("API server stopped"); From 35a125ef09fccaac68d7d5a9d3d3abf7174c7f9d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:32:18 +0100 Subject: [PATCH 219/309] refactor: [#591] order keys in config TOML files alphabetically We should produce always the same TOML file from the same configuration deterministically. --- src/config/mod.rs | 14 +++++++------- src/config/v1/api.rs | 1 + src/config/v1/auth.rs | 10 ++++++---- src/config/v1/image_cache.rs | 18 +++++++++++------- src/config/v1/mail.rs | 3 +++ src/config/v1/mod.rs | 9 +++++++++ src/config/v1/net.rs | 2 ++ src/config/v1/tracker.rs | 16 ++++++++++------ src/config/v1/tracker_statistics_importer.rs | 7 ++++--- 9 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 84c7681f..d0fbe7b2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -347,11 +347,11 @@ mod tests { name = "Torrust" [tracker] - url = "udp://localhost:6969" - mode = "public" api_url = "http://localhost:1212/" + mode = "public" token = "MyAccessToken" token_valid_seconds = 7257600 + url = "udp://localhost:6969" [net] bind_address = "0.0.0.0:3001" @@ -361,9 +361,9 @@ mod tests { secret_key = "MaxVerstappenWC2021" [auth.password_constraints] - min_password_length = 6 max_password_length = 64 - + min_password_length = 6 + [database] connect_url = "sqlite://data.db?mode=rwc" @@ -381,19 +381,19 @@ mod tests { username = "" [image_cache] - max_request_timeout_ms = 1000 capacity = 128000000 entry_size_limit = 4000000 - user_quota_period_seconds = 3600 + max_request_timeout_ms = 1000 user_quota_bytes = 64000000 + user_quota_period_seconds = 3600 [api] default_torrent_page_size = 10 max_torrent_page_size = 30 [tracker_statistics_importer] - torrent_info_update_interval = 3600 port = 3002 + torrent_info_update_interval = 3600 "# .lines() .map(str::trim_start) diff --git a/src/config/v1/api.rs b/src/config/v1/api.rs index 4b4db9fd..678c52d7 100644 --- a/src/config/v1/api.rs +++ b/src/config/v1/api.rs @@ -6,6 +6,7 @@ pub struct Api { /// The default page size for torrent lists. #[serde(default = "Api::default_default_torrent_page_size")] pub default_torrent_page_size: u8, + /// The maximum page size for torrent lists. #[serde(default = "Api::default_max_torrent_page_size")] pub max_torrent_page_size: u8, diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 007d3084..71147e4c 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -9,9 +9,11 @@ pub struct Auth { /// Whether or not to require an email on signup. #[serde(default = "Auth::default_email_on_signup")] pub email_on_signup: EmailOnSignup, + /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, + /// The password constraints #[serde(default = "Auth::default_password_constraints")] pub password_constraints: PasswordConstraints, @@ -117,19 +119,19 @@ impl fmt::Display for SecretKey { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct PasswordConstraints { - /// The minimum password length. - #[serde(default = "PasswordConstraints::default_min_password_length")] - pub min_password_length: usize, /// The maximum password length. #[serde(default = "PasswordConstraints::default_max_password_length")] pub max_password_length: usize, + /// The minimum password length. + #[serde(default = "PasswordConstraints::default_min_password_length")] + pub min_password_length: usize, } impl Default for PasswordConstraints { fn default() -> Self { Self { - min_password_length: Self::default_min_password_length(), max_password_length: Self::default_max_password_length(), + min_password_length: Self::default_min_password_length(), } } } diff --git a/src/config/v1/image_cache.rs b/src/config/v1/image_cache.rs index 88c5ebec..6f9accf1 100644 --- a/src/config/v1/image_cache.rs +++ b/src/config/v1/image_cache.rs @@ -10,23 +10,27 @@ use serde::{Deserialize, Serialize}; #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ImageCache { - /// Maximum time in seconds to wait for downloading the image form the original source. - #[serde(default = "ImageCache::default_max_request_timeout_ms")] - pub max_request_timeout_ms: u64, /// Cache size in bytes. #[serde(default = "ImageCache::default_capacity")] pub capacity: usize, + /// Maximum size in bytes for a single image. #[serde(default = "ImageCache::default_entry_size_limit")] pub entry_size_limit: usize, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the period in seconds (1 day in seconds). - #[serde(default = "ImageCache::default_user_quota_period_seconds")] - pub user_quota_period_seconds: u64, + + /// Maximum time in seconds to wait for downloading the image form the original source. + #[serde(default = "ImageCache::default_max_request_timeout_ms")] + pub max_request_timeout_ms: u64, + /// Users have a cache quota per period. For example: 100MB per day. /// This is the maximum size in bytes (100MB in bytes). #[serde(default = "ImageCache::default_user_quota_bytes")] pub user_quota_bytes: usize, + + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the period in seconds (1 day in seconds). + #[serde(default = "ImageCache::default_user_quota_period_seconds")] + pub user_quota_period_seconds: u64, } impl Default for ImageCache { diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 7675ceac..e171d41b 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -7,12 +7,15 @@ pub struct Mail { /// Whether or not to enable email verification on signup. #[serde(default = "Mail::default_email_verification_enabled")] pub email_verification_enabled: bool, + /// The email address to send emails from. #[serde(default = "Mail::default_from")] pub from: Mailbox, + /// The email address to reply to. #[serde(default = "Mail::default_reply_to")] pub reply_to: Mailbox, + /// The SMTP server configuration. #[serde(default = "Mail::default_smtp")] pub smtp: Smtp, diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 1820c923..9e73d93e 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -29,30 +29,39 @@ pub struct Settings { /// The logging configuration. #[serde(default)] pub logging: Logging, + /// The website customizable values. #[serde(default)] pub website: Website, + /// The tracker configuration. #[serde(default)] pub tracker: Tracker, + /// The network configuration. #[serde(default)] pub net: Network, + /// The authentication configuration. #[serde(default)] pub auth: Auth, + /// The database configuration. #[serde(default)] pub database: Database, + /// The SMTP configuration. #[serde(default)] pub mail: Mail, + /// The image proxy cache configuration. #[serde(default)] pub image_cache: ImageCache, + /// The API configuration. #[serde(default)] pub api: Api, + /// The tracker statistics importer job configuration. #[serde(default)] pub tracker_statistics_importer: TrackerStatisticsImporter, diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index 3df9fc23..cb41c046 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -17,12 +17,14 @@ pub struct Network { /// If not set, the base URL will be inferred from the request. #[serde(default = "Network::default_base_url")] pub base_url: Option, + /// The address the tracker will bind to. /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to /// listen to all interfaces, use `0.0.0.0`. If you want the operating /// system to choose a random port, use port `0`. #[serde(default = "Network::default_bind_address")] pub bind_address: SocketAddr, + /// TSL configuration. #[serde(default = "Network::default_tsl")] pub tsl: Option, diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index 67c31bfe..889e77ad 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -9,23 +9,27 @@ use crate::config::TrackerMode; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Tracker { - /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. - #[serde(default = "Tracker::default_url")] - pub url: Url, + /// The url of the tracker API. For example: `http://localhost:1212/`. + #[serde(default = "Tracker::default_api_url")] + pub api_url: Url, + /// The mode of the tracker. For example: `Public`. /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. #[serde(default = "Tracker::default_mode")] pub mode: TrackerMode, - /// The url of the tracker API. For example: `http://localhost:1212/`. - #[serde(default = "Tracker::default_api_url")] - pub api_url: Url, + /// The token used to authenticate with the tracker API. #[serde(default = "Tracker::default_token")] pub token: ApiToken, + /// The amount of seconds the tracker API token is valid. #[serde(default = "Tracker::default_token_valid_seconds")] pub token_valid_seconds: u64, + + /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. + #[serde(default = "Tracker::default_url")] + pub url: Url, } impl Validator for Tracker { diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v1/tracker_statistics_importer.rs index 531629b1..c9d5c306 100644 --- a/src/config/v1/tracker_statistics_importer.rs +++ b/src/config/v1/tracker_statistics_importer.rs @@ -3,12 +3,13 @@ use serde::{Deserialize, Serialize}; /// Configuration for the tracker statistics importer. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TrackerStatisticsImporter { - /// The interval in seconds to get statistics from the tracker. - #[serde(default = "TrackerStatisticsImporter::default_torrent_info_update_interval")] - pub torrent_info_update_interval: u64, /// The port the Importer API is listening on. Default to `3002`. #[serde(default = "TrackerStatisticsImporter::default_port")] pub port: u16, + + /// The interval in seconds to get statistics from the tracker. + #[serde(default = "TrackerStatisticsImporter::default_torrent_info_update_interval")] + pub torrent_info_update_interval: u64, } impl Default for TrackerStatisticsImporter { From d61d67061fd62ff813ee3dca1cc83fa25b509158 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:58:28 +0100 Subject: [PATCH 220/309] chore(deps): update dependencies ```output $ cargo update Updating crates.io index Locking 35 packages to latest compatible versions Updating backtrace v0.3.72 -> v0.3.73 Updating clap v4.5.6 -> v4.5.7 Updating clap_builder v4.5.6 -> v4.5.7 Adding displaydoc v0.2.4 Updating http-body-util v0.1.1 -> v0.1.2 Updating httparse v1.8.0 -> v1.9.3 Adding icu_collections v1.5.0 Adding icu_locid v1.5.0 Adding icu_locid_transform v1.5.0 Adding icu_locid_transform_data v1.5.0 Adding icu_normalizer v1.5.0 Adding icu_normalizer_data v1.5.0 Adding icu_properties v1.5.0 Adding icu_properties_data v1.5.0 Adding icu_provider v1.5.0 Adding icu_provider_macros v1.5.0 Adding idna v1.0.0 Removing itertools v0.12.1 Adding litemap v0.7.3 Updating object v0.35.0 -> v0.36.0 Updating rustls v0.23.9 -> v0.23.10 Updating sqlformat v0.2.3 -> v0.2.4 Adding stable_deref_trait v1.2.0 Adding synstructure v0.13.1 Adding tinystr v0.7.6 Updating url v2.5.0 -> v2.5.1 Adding utf16_iter v1.0.5 Adding utf8_iter v1.0.4 Adding write16 v1.0.0 Adding writeable v0.5.5 Adding yoke v0.7.4 Adding yoke-derive v0.7.4 Adding zerofrom v0.1.4 Adding zerofrom-derive v0.1.4 Adding zerovec v0.10.2 Adding zerovec-derive v0.10.2 ``` --- Cargo.lock | 321 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 288 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8fd0039..ad3ac507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.6" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.6" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -746,6 +746,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1227,12 +1238,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -1240,9 +1251,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" [[package]] name = "httpdate" @@ -1330,6 +1341,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1346,6 +1475,18 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +dependencies = [ + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", +] + [[package]] name = "ignore" version = "0.4.22" @@ -1402,15 +1543,6 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -1490,13 +1622,13 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna", + "idna 0.5.0", "mime", "native-tls", "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pemfile", "serde", "serde_json", @@ -1538,6 +1670,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -1790,9 +1928,9 @@ dependencies = [ [[package]] name = "object" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -2396,9 +2534,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.9" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "log", "once_cell", @@ -2803,11 +2941,10 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools", "nom", "unicode_categories", ] @@ -3011,6 +3148,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stacker" version = "0.1.15" @@ -3090,6 +3233,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -3247,6 +3401,16 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3317,7 +3481,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] @@ -3755,12 +3919,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", - "idna", + "idna 1.0.0", "percent-encoding", "serde", ] @@ -3798,6 +3962,18 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -4183,6 +4359,18 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "xml-rs" version = "0.8.20" @@ -4207,6 +4395,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -4227,12 +4439,55 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "zstd" version = "0.13.1" From 99622669fa8c6c34abf7148a3faae5aaec517500 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Jun 2024 12:54:20 +0100 Subject: [PATCH 221/309] fix: [#635] udpate tracker config Tracker toml config has introduced breaking changes. --- .../config/tracker.private.e2e.container.sqlite3.toml | 9 ++++++--- .../config/tracker.public.e2e.container.sqlite3.toml | 10 ++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 78457ecd..9cd59c01 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -1,9 +1,12 @@ -db_driver = "Sqlite3" -db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" +[core] mode = "private" +[core.database] +driver = "Sqlite3" +path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" + [[udp_trackers]] -enabled = false +bind_address = "0.0.0.0:6969" [http_api] bind_address = "0.0.0.0:1212" diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index 2c4d1c0c..9018f37c 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -1,6 +1,12 @@ -db_driver = "Sqlite3" -db_path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" +[core] mode = "public" +[core.database] +driver = "Sqlite3" +path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" + +[[http_trackers]] +bind_address = "0.0.0.0:7070" + [http_api] bind_address = "0.0.0.0:1212" From 5bd98e5fb45f4b655504b02256e015e18bc0f60c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Jun 2024 14:37:43 +0100 Subject: [PATCH 222/309] chore(deps): update dependencies ```console cargo update Updating crates.io index Locking 11 packages to latest compatible versions Updating bytemuck v1.16.0 -> v1.16.1 Updating derive_more v0.99.17 -> v0.99.18 Removing displaydoc v0.2.4 Updating httparse v1.9.3 -> v1.9.4 Adding hyper-rustls v0.27.2 Removing icu_collections v1.5.0 Removing icu_locid v1.5.0 Removing icu_locid_transform v1.5.0 Removing icu_locid_transform_data v1.5.0 Removing icu_normalizer v1.5.0 Removing icu_normalizer_data v1.5.0 Removing icu_properties v1.5.0 Removing icu_properties_data v1.5.0 Removing icu_provider v1.5.0 Removing icu_provider_macros v1.5.0 Removing idna v1.0.0 Removing litemap v0.7.3 Updating memchr v2.7.2 -> v2.7.4 Updating miniz_oxide v0.7.3 -> v0.7.4 Updating redox_syscall v0.5.1 -> v0.5.2 Updating reqwest v0.12.4 -> v0.12.5 Removing stable_deref_trait v1.2.0 Removing synstructure v0.13.1 Removing tinystr v0.7.6 Updating url v2.5.1 -> v2.5.2 Removing utf16_iter v1.0.5 Removing utf8_iter v1.0.4 Updating webpki-roots v0.26.2 -> v0.26.3 Removing write16 v1.0.0 Removing writeable v0.5.5 Removing yoke v0.7.4 Removing yoke-derive v0.7.4 Removing zerofrom v0.1.4 Removing zerofrom-derive v0.1.4 Removing zerovec v0.10.2 Removing zerovec-derive v0.10.2 Updating zstd-sys v2.0.10+zstd.1.5.6 -> v2.0.11+zstd.1.5.6 ``` --- Cargo.lock | 333 +++++++---------------------------------------------- 1 file changed, 43 insertions(+), 290 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad3ac507..4d8a18de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,9 +419,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -723,15 +723,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -746,17 +746,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -1251,9 +1240,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1282,6 +1271,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls 0.23.10", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1341,124 +1347,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1475,18 +1363,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" -dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", -] - [[package]] name = "ignore" version = "0.4.22" @@ -1622,7 +1498,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna 0.5.0", + "idna", "mime", "native-tls", "nom", @@ -1670,12 +1546,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "lock_api" version = "0.4.12" @@ -1716,9 +1586,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -1753,9 +1623,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", @@ -2009,7 +1879,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -2345,9 +2215,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] @@ -2383,9 +2253,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", @@ -2397,6 +2267,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -2412,7 +2283,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -3148,12 +3019,6 @@ dependencies = [ "urlencoding", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "stacker" version = "0.1.15" @@ -3233,17 +3098,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -3401,16 +3255,6 @@ dependencies = [ "safe_arch", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -3919,12 +3763,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna 1.0.0", + "idna", "percent-encoding", "serde", ] @@ -3962,18 +3806,6 @@ dependencies = [ "xmlwriter", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -4116,9 +3948,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] @@ -4359,18 +4191,6 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "xml-rs" version = "0.8.20" @@ -4395,30 +4215,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.34" @@ -4439,55 +4235,12 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "zstd" version = "0.13.1" @@ -4508,9 +4261,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", From 9ce44f4401a94364f8f3d832dd582db425da6599 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Jun 2024 15:01:50 +0100 Subject: [PATCH 223/309] fix: [#635] udpate tracker config The TOML files have already been changed to follow breaking changes in the Tracker config. However, there was a pending env var to rename: ``` TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER -> TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER ``` --- .env.local | 2 +- compose.yaml | 2 +- contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.env.local b/.env.local index 3122ef20..1260d0f0 100644 --- a/.env.local +++ b/.env.local @@ -3,5 +3,5 @@ TORRUST_INDEX_CONFIG_TOML= TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY=MaxVerstappenWC2021 USER_ID=1000 TORRUST_TRACKER_CONFIG_TOML= -TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER=Sqlite3 +TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER=Sqlite3 TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN=MyAccessToken diff --git a/compose.yaml b/compose.yaml index 002053a2..9e533e15 100644 --- a/compose.yaml +++ b/compose.yaml @@ -34,7 +34,7 @@ services: - USER_ID=${USER_ID} - TORRUST_TRACKER_CONFIG_TOML=${TORRUST_TRACKER_CONFIG_TOML} - TORRUST_TRACKER_DATABASE=${TORRUST_TRACKER_DATABASE:-e2e_testing_sqlite3} - - TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER=${TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER:-Sqlite3} + - TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER=${TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER:-Sqlite3} - TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN=${TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN:-MyAccessToken} networks: - server_side diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index eb5706b6..ac043345 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh index 130cae58..382b8761 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.private.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh index 75999892..40fee11d 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_DB_DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans From 44ed14b366adcb9a4b715c79aeda9cfb49225581 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 10:41:42 +0100 Subject: [PATCH 224/309] chore(deps): update dependencies ```output cargo update Updating crates.io index Locking 27 packages to latest compatible versions Updating bitflags v2.5.0 -> v2.6.0 Updating cc v1.0.99 -> v1.0.104 Updating clap v4.5.7 -> v4.5.8 Updating clap_builder v4.5.7 -> v4.5.8 Updating clap_derive v4.5.5 -> v4.5.8 Updating either v1.12.0 -> v1.13.0 Updating hyper v1.3.1 -> v1.4.0 Updating hyper-util v0.1.5 -> v0.1.6 Updating lazy_static v1.4.0 -> v1.5.0 Updating log v0.4.21 -> v0.4.22 Updating mime_guess v2.0.4 -> v2.0.5 Updating num-bigint v0.4.5 -> v0.4.6 Updating object v0.36.0 -> v0.36.1 Updating pest v2.7.10 -> v2.7.11 Updating pest_derive v2.7.10 -> v2.7.11 Updating pest_generator v2.7.10 -> v2.7.11 Updating pest_meta v2.7.10 -> v2.7.11 Updating proc-macro2 v1.0.85 -> v1.0.86 Updating rgb v0.8.37 -> v0.8.40 Updating serde_bytes v0.11.14 -> v0.11.15 Updating serde_json v1.0.117 -> v1.0.120 Updating serde_with v3.8.1 -> v3.8.2 Updating serde_with_macros v3.8.1 -> v3.8.2 Removing spin v0.5.2 Updating subtle v2.5.0 -> v2.6.1 Updating syn v2.0.66 -> v2.0.68 Updating tinyvec v1.6.0 -> v1.6.1 Updating uuid v1.8.0 -> v1.9.1 ``` --- Cargo.lock | 178 ++++++++++++++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d8a18de..f42e6493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -355,9 +355,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -446,9 +446,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" dependencies = [ "jobserver", "libc", @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -508,14 +508,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -666,7 +666,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -677,7 +677,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -731,7 +731,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -760,9 +760,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] @@ -892,7 +892,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -1009,7 +1009,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1090,7 +1090,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "ignore", "walkdir", ] @@ -1252,9 +1252,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" dependencies = [ "bytes", "futures-channel", @@ -1306,9 +1306,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", @@ -1475,11 +1475,11 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -1558,9 +1558,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matches" @@ -1607,9 +1607,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1666,7 +1666,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1682,7 +1682,7 @@ dependencies = [ "httparse", "memchr", "mime", - "spin 0.9.8", + "spin", "version_check", ] @@ -1725,9 +1725,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -1798,9 +1798,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -1817,7 +1817,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1834,7 +1834,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1933,7 +1933,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1963,9 +1963,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -1974,9 +1974,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -1984,22 +1984,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "pest_meta" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -2029,7 +2029,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2124,9 +2124,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -2139,7 +2139,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", "version_check", "yansi", ] @@ -2219,7 +2219,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -2312,9 +2312,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" dependencies = [ "bytemuck", ] @@ -2329,7 +2329,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -2384,7 +2384,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2532,7 +2532,7 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2576,9 +2576,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] @@ -2591,14 +2591,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -2638,9 +2638,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.1" +version = "3.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "079f3a42cd87588d924ed95b533f8d30a483388c4e400ab736a7058e34f16169" dependencies = [ "base64 0.22.1", "chrono", @@ -2656,14 +2656,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "bc03aad67c1d26b7de277d51c86892e7d9a0110a2fe44bf6b26cc569fba302d6" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2785,12 +2785,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -2921,7 +2915,7 @@ checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "bytes", "crc", @@ -2964,7 +2958,7 @@ checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "crc", "dotenvy", @@ -3051,9 +3045,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svgtypes" @@ -3077,9 +3071,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -3197,7 +3191,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -3257,9 +3251,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -3296,7 +3290,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -3485,7 +3479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "http", @@ -3543,7 +3537,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -3814,9 +3808,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "getrandom", ] @@ -3891,7 +3885,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -3925,7 +3919,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4232,7 +4226,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] From 0bcce161ae2701988817a94bfe5ba345d22372c8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 10:42:56 +0100 Subject: [PATCH 225/309] chore: bump build-push-action action from 5 to 6 --- .github/workflows/container.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/container.yaml b/.github/workflows/container.yaml index 8a6e4a82..f9dc1b5d 100644 --- a/.github/workflows/container.yaml +++ b/.github/workflows/container.yaml @@ -30,7 +30,7 @@ jobs: - id: build name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: ./Containerfile push: false @@ -127,7 +127,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: ./Containerfile push: true @@ -168,7 +168,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: ./Containerfile push: true From 940c24680955e7e125f6b769a9e691fdec06c4c3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 11:05:11 +0100 Subject: [PATCH 226/309] fix: [#648] udpate tracker configuration New breaking changes have been applied to the Tracker configuration. - Renamed `log_level` to `threshold`. - Tracker mode split into `listed` and `private` flags. - Added configuration `version`. From: ```toml [logging] log_level = "info" [core] mode = "public" tracker_usage_statistics = true inactive_peer_cleanup_interval = 600 ``` To: ```toml version = "2" [logging] threshold = "info" [core] inactive_peer_cleanup_interval = 600 listed = false private = false tracker_usage_statistics = true ``` --- .../config/tracker.private.e2e.container.sqlite3.toml | 4 +++- .../config/tracker.public.e2e.container.sqlite3.toml | 4 +++- src/config/mod.rs | 7 +------ src/config/v1/tracker.rs | 4 +--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 9cd59c01..63e7cc47 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -1,5 +1,7 @@ +version = "2" + [core] -mode = "private" +private = true [core.database] driver = "Sqlite3" diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index 9018f37c..3dfa2e7b 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -1,5 +1,7 @@ +version = "2" + [core] -mode = "public" +private = true [core.database] driver = "Sqlite3" diff --git a/src/config/mod.rs b/src/config/mod.rs index d0fbe7b2..9a80d7da 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -136,12 +136,7 @@ impl From for Error { } } -// todo: use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. - -/// The mode the tracker will run in. -/// -/// Refer to [Torrust Tracker Configuration](https://docs.rs/torrust-tracker-configuration) -/// to know how to configure the tracker to run in each mode. +/// The mode the tracker is running in. #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub enum TrackerMode { /// Will track every new info hash and serve every peer. diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index 889e77ad..20f73f7c 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -13,9 +13,7 @@ pub struct Tracker { #[serde(default = "Tracker::default_api_url")] pub api_url: Url, - /// The mode of the tracker. For example: `Public`. - /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) - /// crate for more information. + /// The mode the tracker is running in. #[serde(default = "Tracker::default_mode")] pub mode: TrackerMode, From 238340bcd20b424172936242da85f9aa10eb357f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 17:25:35 +0100 Subject: [PATCH 227/309] feat: add version to config file Add the configuration version at the top of the TOML Index config file. ```toml version = "2" ``` And change the current version to V2. V1 is the old one before the configuration overhaul. --- .../default/config/index.container.mysql.toml | 2 + .../config/index.container.sqlite3.toml | 2 + .../config/index.development.sqlite3.toml | 2 + .../index.private.e2e.container.sqlite3.toml | 2 + .../index.public.e2e.container.mysql.toml | 2 + .../index.public.e2e.container.sqlite3.toml | 2 + src/config/mod.rs | 112 ++++++++++++++---- src/config/{v1 => v2}/api.rs | 0 src/config/{v1 => v2}/auth.rs | 0 src/config/{v1 => v2}/database.rs | 0 src/config/{v1 => v2}/image_cache.rs | 0 src/config/{v1 => v2}/logging.rs | 0 src/config/{v1 => v2}/mail.rs | 0 src/config/{v1 => v2}/mod.rs | 5 + src/config/{v1 => v2}/net.rs | 0 src/config/{v1 => v2}/tracker.rs | 0 .../{v1 => v2}/tracker_statistics_importer.rs | 0 src/config/{v1 => v2}/website.rs | 0 .../api/client/v1/contexts/settings/mod.rs | 2 +- 19 files changed, 110 insertions(+), 21 deletions(-) rename src/config/{v1 => v2}/api.rs (100%) rename src/config/{v1 => v2}/auth.rs (100%) rename src/config/{v1 => v2}/database.rs (100%) rename src/config/{v1 => v2}/image_cache.rs (100%) rename src/config/{v1 => v2}/logging.rs (100%) rename src/config/{v1 => v2}/mail.rs (100%) rename src/config/{v1 => v2}/mod.rs (95%) rename src/config/{v1 => v2}/net.rs (100%) rename src/config/{v1 => v2}/tracker.rs (100%) rename src/config/{v1 => v2}/tracker_statistics_importer.rs (100%) rename src/config/{v1 => v2}/website.rs (100%) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 9a53efb6..b0c9aaf5 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 5abf23f4..43cafe68 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index c2b8b582..07a77e56 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index 8747c579..64f39dbe 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 0382c12c..56742eb4 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index e04d0ef6..09dd725b 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,3 +1,5 @@ +version = "2" + [logging] log_level = "info" diff --git a/src/config/mod.rs b/src/config/mod.rs index 9a80d7da..dbf94d8d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,5 @@ //! Configuration for the application. -pub mod v1; +pub mod v2; pub mod validator; use std::str::FromStr; @@ -7,6 +7,7 @@ use std::sync::Arc; use std::{env, fmt}; use camino::Utf8PathBuf; +use derive_more::Display; use figment::providers::{Env, Format, Serialized, Toml}; use figment::Figment; use serde::{Deserialize, Serialize}; @@ -18,34 +19,37 @@ use url::Url; use crate::web::api::server::DynError; -pub type Settings = v1::Settings; +pub type Settings = v2::Settings; -pub type Api = v1::api::Api; +pub type Api = v2::api::Api; -pub type Auth = v1::auth::Auth; -pub type EmailOnSignup = v1::auth::EmailOnSignup; -pub type SecretKey = v1::auth::SecretKey; -pub type PasswordConstraints = v1::auth::PasswordConstraints; +pub type Auth = v2::auth::Auth; +pub type EmailOnSignup = v2::auth::EmailOnSignup; +pub type SecretKey = v2::auth::SecretKey; +pub type PasswordConstraints = v2::auth::PasswordConstraints; -pub type Database = v1::database::Database; +pub type Database = v2::database::Database; -pub type ImageCache = v1::image_cache::ImageCache; +pub type ImageCache = v2::image_cache::ImageCache; -pub type Mail = v1::mail::Mail; -pub type Smtp = v1::mail::Smtp; -pub type Credentials = v1::mail::Credentials; +pub type Mail = v2::mail::Mail; +pub type Smtp = v2::mail::Smtp; +pub type Credentials = v2::mail::Credentials; -pub type Network = v1::net::Network; +pub type Network = v2::net::Network; -pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; +pub type TrackerStatisticsImporter = v2::tracker_statistics_importer::TrackerStatisticsImporter; -pub type Tracker = v1::tracker::Tracker; -pub type ApiToken = v1::tracker::ApiToken; +pub type Tracker = v2::tracker::Tracker; +pub type ApiToken = v2::tracker::ApiToken; -pub type Logging = v1::logging::Logging; -pub type LogLevel = v1::logging::LogLevel; +pub type Logging = v2::logging::Logging; +pub type LogLevel = v2::logging::LogLevel; -pub type Website = v1::website::Website; +pub type Website = v2::website::Website; + +/// Configuration version +const VERSION_2: &str = "2"; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -60,6 +64,63 @@ pub const ENV_VAR_CONFIG_TOML: &str = "TORRUST_INDEX_CONFIG_TOML"; /// The `index.toml` file location. pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_INDEX_CONFIG_TOML_PATH"; +pub const LATEST_VERSION: &str = "2"; + +/// Info about the configuration specification. +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +pub struct Metadata { + #[serde(default = "Metadata::default_version")] + #[serde(flatten)] + version: Version, +} + +impl Default for Metadata { + fn default() -> Self { + Self { + version: Self::default_version(), + } + } +} + +impl Metadata { + fn default_version() -> Version { + Version::latest() + } +} + +/// The configuration version. +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +pub struct Version { + #[serde(default = "Version::default_semver")] + version: String, +} + +impl Default for Version { + fn default() -> Self { + Self { + version: Self::default_semver(), + } + } +} + +impl Version { + fn new(semver: &str) -> Self { + Self { + version: semver.to_owned(), + } + } + + fn latest() -> Self { + Self { + version: LATEST_VERSION.to_string(), + } + } + + fn default_semver() -> String { + LATEST_VERSION.to_string() + } +} + /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { @@ -125,6 +186,9 @@ pub enum Error { #[error("The error for errors that can never happen.")] Infallible, + + #[error("Unsupported configuration version: {version}")] + UnsupportedVersion { version: Version }, } impl From for Error { @@ -284,6 +348,12 @@ impl Configuration { let settings: Settings = figment.extract()?; + if settings.metadata.version != Version::new(VERSION_2) { + return Err(Error::UnsupportedVersion { + version: settings.metadata.version, + }); + } + Ok(settings) } @@ -335,7 +405,9 @@ mod tests { #[cfg(test)] fn default_config_toml() -> String { - let config = r#"[logging] + let config = r#"version = "2" + + [logging] log_level = "info" [website] diff --git a/src/config/v1/api.rs b/src/config/v2/api.rs similarity index 100% rename from src/config/v1/api.rs rename to src/config/v2/api.rs diff --git a/src/config/v1/auth.rs b/src/config/v2/auth.rs similarity index 100% rename from src/config/v1/auth.rs rename to src/config/v2/auth.rs diff --git a/src/config/v1/database.rs b/src/config/v2/database.rs similarity index 100% rename from src/config/v1/database.rs rename to src/config/v2/database.rs diff --git a/src/config/v1/image_cache.rs b/src/config/v2/image_cache.rs similarity index 100% rename from src/config/v1/image_cache.rs rename to src/config/v2/image_cache.rs diff --git a/src/config/v1/logging.rs b/src/config/v2/logging.rs similarity index 100% rename from src/config/v1/logging.rs rename to src/config/v2/logging.rs diff --git a/src/config/v1/mail.rs b/src/config/v2/mail.rs similarity index 100% rename from src/config/v1/mail.rs rename to src/config/v2/mail.rs diff --git a/src/config/v1/mod.rs b/src/config/v2/mod.rs similarity index 95% rename from src/config/v1/mod.rs rename to src/config/v2/mod.rs index 9e73d93e..6412a4e2 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v2/mod.rs @@ -22,10 +22,15 @@ use self::tracker::{ApiToken, Tracker}; use self::tracker_statistics_importer::TrackerStatisticsImporter; use self::website::Website; use super::validator::{ValidationError, Validator}; +use super::Metadata; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { + /// Configuration metadata. + #[serde(flatten)] + pub metadata: Metadata, + /// The logging configuration. #[serde(default)] pub logging: Logging, diff --git a/src/config/v1/net.rs b/src/config/v2/net.rs similarity index 100% rename from src/config/v1/net.rs rename to src/config/v2/net.rs diff --git a/src/config/v1/tracker.rs b/src/config/v2/tracker.rs similarity index 100% rename from src/config/v1/tracker.rs rename to src/config/v2/tracker.rs diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v2/tracker_statistics_importer.rs similarity index 100% rename from src/config/v1/tracker_statistics_importer.rs rename to src/config/v2/tracker_statistics_importer.rs diff --git a/src/config/v1/website.rs b/src/config/v2/website.rs similarity index 100% rename from src/config/v1/website.rs rename to src/config/v2/website.rs diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 4c1b371f..df8433d6 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -5,7 +5,7 @@ use std::net::SocketAddr; use serde::{Deserialize, Serialize}; use url::Url; -use crate::config::v1::tracker::ApiToken; +use crate::config::v2::tracker::ApiToken; use crate::config::{ Api as DomainApi, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, From f39f5e575eaaf2e7a78905bc14dcd73c52d57905 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 18:00:25 +0100 Subject: [PATCH 228/309] feat: print the final configuration to console When the application starts, it only prints the loaded configuration when it's loaded from an env var. It's very helpful to print the final configuration after merging all sources with Figment. default values -> merge with TOML file -> merge with env vars -> final configuration It's printed in JSON format. --- src/app.rs | 9 +++++++++ src/config/mod.rs | 6 +++--- src/config/v2/mod.rs | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 954f7cb4..11389076 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,6 +2,7 @@ use std::net::SocketAddr; use std::sync::Arc; use tokio::task::JoinHandle; +use tracing::info; use crate::bootstrap::logging; use crate::cache::image::manager::ImageCacheService; @@ -42,6 +43,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running logging::setup(&log_level); + log_configuration(&configuration).await; + let configuration = Arc::new(configuration); // Get configuration settings needed to build the app dependencies and @@ -190,3 +193,9 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running tracker_data_importer_handle: tracker_statistics_importer_handle, } } + +async fn log_configuration(configuration: &Configuration) { + let mut setting = configuration.get_all().await.clone(); + setting.remove_secrets(); + info!("Configuration:\n{}", setting.to_json()); +} diff --git a/src/config/mod.rs b/src/config/mod.rs index dbf94d8d..16973e12 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -141,17 +141,17 @@ impl Info { let env_var_config_toml_path = ENV_VAR_CONFIG_TOML_PATH.to_string(); let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) { - println!("Loading configuration from environment variable {config_toml} ..."); + println!("Loading extra configuration from environment variable {config_toml} ..."); Some(config_toml) } else { None }; let config_toml_path = if let Ok(config_toml_path) = env::var(env_var_config_toml_path) { - println!("Loading configuration from file: `{config_toml_path}` ..."); + println!("Loading extra configuration from file: `{config_toml_path}` ..."); config_toml_path } else { - println!("Loading configuration from default configuration file: `{default_config_toml_path}` ..."); + println!("Loading extra configuration from default configuration file: `{default_config_toml_path}` ..."); default_config_toml_path }; diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index 6412a4e2..5bb9096f 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -81,6 +81,26 @@ impl Settings { "***".clone_into(&mut self.mail.smtp.credentials.password); self.auth.secret_key = SecretKey::new("***"); } + + /// Encodes the configuration to TOML. + /// + /// # Panics + /// + /// Will panic if it can't be converted to TOML. + #[must_use] + pub fn to_toml(&self) -> String { + toml::to_string(self).expect("Could not encode TOML value") + } + + /// Encodes the configuration to JSON. + /// + /// # Panics + /// + /// Will panic if it can't be converted to JSON. + #[must_use] + pub fn to_json(&self) -> String { + serde_json::to_string_pretty(self).expect("Could not encode JSON value") + } } impl Validator for Settings { From a2b8fc5963ddf9efef99978f7bf98b81fbd5e044 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 18:07:32 +0100 Subject: [PATCH 229/309] refactor: normalize log texts - First word uppercase - Remove full stop at the end of the text --- src/app.rs | 1 + src/bootstrap/logging.rs | 2 +- src/console/commands/seeder/logging.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 11389076..dbd0d786 100644 --- a/src/app.rs +++ b/src/app.rs @@ -194,6 +194,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running } } +/// It logs the final configuration removing secrets. async fn log_configuration(configuration: &Configuration) { let mut setting = configuration.get_all().await.clone(); setting.remove_secrets(); diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index d15c676b..1be09990 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -37,7 +37,7 @@ fn tracing_stdout_init(filter: LevelFilter, style: &TraceStyle) { TraceStyle::Json => builder.json().init(), }; - info!("logging initialized."); + info!("Logging initialized"); } #[derive(Debug)] diff --git a/src/console/commands/seeder/logging.rs b/src/console/commands/seeder/logging.rs index 3ba046f3..0f9a9afc 100644 --- a/src/console/commands/seeder/logging.rs +++ b/src/console/commands/seeder/logging.rs @@ -8,5 +8,5 @@ use tracing::level_filters::LevelFilter; pub fn setup(level: LevelFilter) { tracing_subscriber::fmt().with_max_level(level).init(); - debug!("logging initialized."); + debug!("Logging initialized"); } From df962647b45c411022dcb5c328bb6604824c1c7f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 2 Jul 2024 18:35:37 +0100 Subject: [PATCH 230/309] feat: [#652] rename config option log_level to threshold Rename `log_level` to `threshold` ```toml [logging] log_level = "info" ``` ```toml [logging] threshold = "info" ``` --- .../default/config/index.container.mysql.toml | 7 ++- .../config/index.container.sqlite3.toml | 7 ++- .../config/index.development.sqlite3.toml | 7 ++- .../index.private.e2e.container.sqlite3.toml | 7 ++- .../index.public.e2e.container.mysql.toml | 7 ++- .../index.public.e2e.container.sqlite3.toml | 7 ++- src/app.rs | 4 +- src/bootstrap/logging.rs | 10 ++-- src/config/mod.rs | 4 +- src/config/v2/logging.rs | 58 +++++++++---------- .../tracker_statistics_importer/app.rs | 4 +- tests/common/contexts/settings/mod.rs | 4 +- tests/environments/isolated.rs | 4 +- 13 files changed, 80 insertions(+), 50 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index b0c9aaf5..2626c2f9 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 43cafe68..2c5bf978 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" [database] connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 07a77e56..eaae9bd3 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" # Uncomment if you want to enable TSL for development #[net.tsl] diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index 64f39dbe..abc24ef9 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" [tracker] api_url = "http://tracker:1212" diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 56742eb4..6ed38608 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" [tracker] api_url = "http://tracker:1212" diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 09dd725b..6177b9c7 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,7 +1,12 @@ version = "2" [logging] -log_level = "info" +#threshold = "off" +#threshold = "error" +#threshold = "warn" +threshold = "info" +#threshold = "debug" +#threshold = "trace" [tracker] api_url = "http://tracker:1212" diff --git a/src/app.rs b/src/app.rs index dbd0d786..ad4a5d73 100644 --- a/src/app.rs +++ b/src/app.rs @@ -39,9 +39,9 @@ pub struct Running { /// It panics if there is an error connecting to the database. #[allow(clippy::too_many_lines)] pub async fn run(configuration: Configuration, api_version: &Version) -> Running { - let log_level = configuration.settings.read().await.logging.log_level.clone(); + let threshold = configuration.settings.read().await.logging.threshold.clone(); - logging::setup(&log_level); + logging::setup(&threshold); log_configuration(&configuration).await; diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index 1be09990..d9d141d9 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -11,19 +11,19 @@ use std::sync::Once; use tracing::info; use tracing::level_filters::LevelFilter; -use crate::config::LogLevel; +use crate::config::Threshold; static INIT: Once = Once::new(); -pub fn setup(log_level: &LogLevel) { - let tracing_level: LevelFilter = log_level.clone().into(); +pub fn setup(threshold: &Threshold) { + let tracing_level_filter: LevelFilter = threshold.clone().into(); - if tracing_level == LevelFilter::OFF { + if tracing_level_filter == LevelFilter::OFF { return; } INIT.call_once(|| { - tracing_stdout_init(tracing_level, &TraceStyle::Default); + tracing_stdout_init(tracing_level_filter, &TraceStyle::Default); }); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 16973e12..e47cf64c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -44,7 +44,7 @@ pub type Tracker = v2::tracker::Tracker; pub type ApiToken = v2::tracker::ApiToken; pub type Logging = v2::logging::Logging; -pub type LogLevel = v2::logging::LogLevel; +pub type Threshold = v2::logging::Threshold; pub type Website = v2::website::Website; @@ -408,7 +408,7 @@ mod tests { let config = r#"version = "2" [logging] - log_level = "info" + threshold = "info" [website] name = "Torrust" diff --git a/src/config/v2/logging.rs b/src/config/v2/logging.rs index 93387286..ec33ad3d 100644 --- a/src/config/v2/logging.rs +++ b/src/config/v2/logging.rs @@ -7,70 +7,70 @@ use tracing::level_filters::LevelFilter; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Logging { /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. - #[serde(default = "Logging::default_log_level")] - pub log_level: LogLevel, + #[serde(default = "Logging::default_threshold")] + pub threshold: Threshold, } impl Default for Logging { fn default() -> Self { Self { - log_level: Logging::default_log_level(), + threshold: Logging::default_threshold(), } } } impl Logging { - fn default_log_level() -> LogLevel { - LogLevel::Info + fn default_threshold() -> Threshold { + Threshold::Info } } #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] #[serde(rename_all = "lowercase")] -pub enum LogLevel { - /// A level lower than all log levels. +pub enum Threshold { + /// A level lower than all log security levels. Off, - /// Corresponds to the `Error` log level. + /// Corresponds to the `Error` log security level. Error, - /// Corresponds to the `Warn` log level. + /// Corresponds to the `Warn` log security level. Warn, - /// Corresponds to the `Info` log level. + /// Corresponds to the `Info` log security level. Info, - /// Corresponds to the `Debug` log level. + /// Corresponds to the `Debug` log security level. Debug, - /// Corresponds to the `Trace` log level. + /// Corresponds to the `Trace` log security level. Trace, } -impl Default for LogLevel { +impl Default for Threshold { fn default() -> Self { Self::Info } } -impl fmt::Display for LogLevel { +impl fmt::Display for Threshold { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let display_str = match self { - LogLevel::Off => "off", - LogLevel::Error => "error", - LogLevel::Warn => "warn", - LogLevel::Info => "info", - LogLevel::Debug => "debug", - LogLevel::Trace => "trace", + Threshold::Off => "off", + Threshold::Error => "error", + Threshold::Warn => "warn", + Threshold::Info => "info", + Threshold::Debug => "debug", + Threshold::Trace => "trace", }; write!(f, "{display_str}") } } -impl From for LevelFilter { - fn from(log_level: LogLevel) -> Self { - match log_level { - LogLevel::Off => LevelFilter::OFF, - LogLevel::Error => LevelFilter::ERROR, - LogLevel::Warn => LevelFilter::WARN, - LogLevel::Info => LevelFilter::INFO, - LogLevel::Debug => LevelFilter::DEBUG, - LogLevel::Trace => LevelFilter::TRACE, +impl From for LevelFilter { + fn from(threshold: Threshold) -> Self { + match threshold { + Threshold::Off => LevelFilter::OFF, + Threshold::Error => LevelFilter::ERROR, + Threshold::Warn => LevelFilter::WARN, + Threshold::Info => LevelFilter::INFO, + Threshold::Debug => LevelFilter::DEBUG, + Threshold::Trace => LevelFilter::TRACE, } } } diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index f9d9d4f9..2d2dec53 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -90,9 +90,9 @@ pub async fn import() { let configuration = initialize_configuration(); - let log_level = configuration.settings.read().await.logging.log_level.clone(); + let threshold = configuration.settings.read().await.logging.threshold.clone(); - logging::setup(&log_level); + logging::setup(&threshold); let cfg = Arc::new(configuration); diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 22f33aa3..aef91661 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -27,7 +27,7 @@ pub struct Settings { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Logging { - pub log_level: String, + pub threshold: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -130,7 +130,7 @@ impl From for Settings { impl From for Logging { fn from(logging: DomainLogging) -> Self { Self { - log_level: logging.log_level.to_string(), + threshold: logging.threshold.to_string(), } } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index c9fbeadb..e13207d2 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -2,7 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::{LogLevel, FREE_PORT}; +use torrust_index::config::{Threshold, FREE_PORT}; use torrust_index::web::api::Version; use url::Url; @@ -75,7 +75,7 @@ impl Default for TestEnv { fn ephemeral(temp_dir: &TempDir) -> config::Settings { let mut configuration = config::Settings::default(); - configuration.logging.log_level = LogLevel::Off; // Change to `debug` for tests debugging + configuration.logging.threshold = Threshold::Off; // Change to `debug` for tests debugging // Ephemeral API port configuration.net.bind_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), FREE_PORT); From cea5b44bad202ef3589f93ca4f4c74ca4596f754 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 3 Jul 2024 00:11:26 +0200 Subject: [PATCH 231/309] chore(deps): update dependencies --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f42e6493..21bdb2e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4211,18 +4211,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", From 61041031927b799fce34a99e1c0279a1cfbb91a1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 3 Jul 2024 11:07:13 +0100 Subject: [PATCH 232/309] feat: [#655] remove tracker mode in config Update the tracker configuration section to follow [changes in the tracker configuration](https://github.com/torrust/torrust-tracker/issues/932). ```toml [tracker] mode = "public" ``` ```toml [tracker] private = false listed = false ``` --- .../index.private.e2e.container.sqlite3.toml | 3 +- src/config/mod.rs | 85 +++---------------- src/config/v2/tracker.rs | 27 +++--- src/lib.rs | 7 +- src/services/torrent.rs | 19 ++--- .../api/client/v1/contexts/settings/mod.rs | 6 +- tests/common/contexts/settings/mod.rs | 6 +- tests/common/contexts/settings/responses.rs | 3 +- tests/e2e/environment.rs | 7 +- .../web/api/v1/contexts/settings/contract.rs | 3 +- 10 files changed, 57 insertions(+), 109 deletions(-) diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index abc24ef9..48a34e81 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -10,7 +10,8 @@ threshold = "info" [tracker] api_url = "http://tracker:1212" -mode = "private" +listed = false +private = true url = "http://tracker:7070" [database] diff --git a/src/config/mod.rs b/src/config/mod.rs index e47cf64c..5a9cf071 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,9 +2,8 @@ pub mod v2; pub mod validator; -use std::str::FromStr; +use std::env; use std::sync::Arc; -use std::{env, fmt}; use camino::Utf8PathBuf; use derive_more::Display; @@ -200,70 +199,6 @@ impl From for Error { } } -/// The mode the tracker is running in. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] -pub enum TrackerMode { - /// Will track every new info hash and serve every peer. - #[serde(rename = "public")] - Public, - - /// Will only track whitelisted info hashes. - #[serde(rename = "listed")] - Listed, - - /// Will only serve authenticated peers - #[serde(rename = "private")] - Private, - - /// Will only track whitelisted info hashes and serve authenticated peers - #[serde(rename = "private_listed")] - PrivateListed, -} - -impl Default for TrackerMode { - fn default() -> Self { - Self::Public - } -} - -impl fmt::Display for TrackerMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display_str = match self { - TrackerMode::Public => "public", - TrackerMode::Listed => "listed", - TrackerMode::Private => "private", - TrackerMode::PrivateListed => "private_listed", - }; - write!(f, "{display_str}") - } -} - -impl FromStr for TrackerMode { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "public" => Ok(TrackerMode::Public), - "listed" => Ok(TrackerMode::Listed), - "private" => Ok(TrackerMode::Private), - "private_listed" => Ok(TrackerMode::PrivateListed), - _ => Err(format!("Unknown tracker mode: {s}")), - } - } -} - -impl TrackerMode { - #[must_use] - pub fn is_open(&self) -> bool { - matches!(self, TrackerMode::Public | TrackerMode::Listed) - } - - #[must_use] - pub fn is_close(&self) -> bool { - !self.is_open() - } -} - /// Port number representing that the OS will choose one randomly from the available ports. /// /// It's the port number `0` @@ -369,7 +304,8 @@ impl Configuration { ConfigurationPublic { website_name: settings_lock.website.name.clone(), tracker_url: settings_lock.tracker.url.clone(), - tracker_mode: settings_lock.tracker.mode.clone(), + tracker_listed: settings_lock.tracker.listed, + tracker_private: settings_lock.tracker.private, email_on_signup: settings_lock.auth.email_on_signup.clone(), } } @@ -392,7 +328,8 @@ impl Configuration { pub struct ConfigurationPublic { website_name: String, tracker_url: Url, - tracker_mode: TrackerMode, + tracker_listed: bool, + tracker_private: bool, email_on_signup: EmailOnSignup, } @@ -415,7 +352,8 @@ mod tests { [tracker] api_url = "http://localhost:1212/" - mode = "public" + listed = false + private = false token = "MyAccessToken" token_valid_seconds = 7257600 url = "udp://localhost:6969" @@ -497,7 +435,8 @@ mod tests { ConfigurationPublic { website_name: all_settings.website.name, tracker_url: all_settings.tracker.url, - tracker_mode: all_settings.tracker.mode, + tracker_listed: all_settings.tracker.listed, + tracker_private: all_settings.tracker.private, email_on_signup: all_settings.auth.email_on_signup, } ); @@ -586,14 +525,14 @@ mod tests { use url::Url; use crate::config::validator::Validator; - use crate::config::{Configuration, TrackerMode}; + use crate::config::Configuration; #[tokio::test] - async fn udp_trackers_in_close_mode_are_not_supported() { + async fn udp_trackers_in_private_mode_are_not_supported() { let configuration = Configuration::default(); let mut settings_lock = configuration.settings.write().await; - settings_lock.tracker.mode = TrackerMode::Private; + settings_lock.tracker.private = true; settings_lock.tracker.url = Url::parse("udp://localhost:6969").unwrap(); assert!(settings_lock.validate().is_err()); diff --git a/src/config/v2/tracker.rs b/src/config/v2/tracker.rs index 20f73f7c..3702ea5b 100644 --- a/src/config/v2/tracker.rs +++ b/src/config/v2/tracker.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; use url::Url; use super::{ValidationError, Validator}; -use crate::config::TrackerMode; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -13,9 +12,13 @@ pub struct Tracker { #[serde(default = "Tracker::default_api_url")] pub api_url: Url, - /// The mode the tracker is running in. - #[serde(default = "Tracker::default_mode")] - pub mode: TrackerMode, + /// Whether the tracker is running in listed mode or not. + #[serde(default = "Tracker::default_listed")] + pub listed: bool, + + /// Whether the tracker is running in private mode or not. + #[serde(default = "Tracker::default_private")] + pub private: bool, /// The token used to authenticate with the tracker API. #[serde(default = "Tracker::default_token")] @@ -32,10 +35,7 @@ pub struct Tracker { impl Validator for Tracker { fn validate(&self) -> Result<(), ValidationError> { - let tracker_mode = self.mode.clone(); - let tracker_url = self.url.clone(); - - if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { + if self.private && (self.url.scheme() != "http" && self.url.scheme() != "https") { return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); } @@ -47,7 +47,8 @@ impl Default for Tracker { fn default() -> Self { Self { url: Self::default_url(), - mode: Self::default_mode(), + listed: Self::default_listed(), + private: Self::default_private(), api_url: Self::default_api_url(), token: Self::default_token(), token_valid_seconds: Self::default_token_valid_seconds(), @@ -64,8 +65,12 @@ impl Tracker { Url::parse("udp://localhost:6969").unwrap() } - fn default_mode() -> TrackerMode { - TrackerMode::default() + fn default_listed() -> bool { + false + } + + fn default_private() -> bool { + false } fn default_api_url() -> Url { diff --git a/src/lib.rs b/src/lib.rs index d42990d1..a3e460ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,7 @@ //! ```toml //! [tracker] //! url = "udp://localhost:6969" -//! mode = "public" +//! //! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 @@ -170,11 +170,12 @@ //! name = "Torrust" //! //! [tracker] -//! url = "udp://localhost:6969" -//! mode = "public" //! api_url = "http://localhost:1212/" +//! listed = false +//! private = false //! token = "MyAccessToken" //! token_valid_seconds = 7257600 +//! url = "udp://localhost:6969" //! //! [net] //! bind_address = "0.0.0.0:3001" diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 28aa6223..d35fcdf2 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -6,7 +6,7 @@ use tracing::debug; use url::Url; use super::category::DbCategoryRepository; -use crate::config::{Configuration, TrackerMode}; +use crate::config::Configuration; use crate::databases::database::{Database, Error, Sorting}; use crate::errors::ServiceError; use crate::models::category::CategoryId; @@ -261,12 +261,12 @@ impl Index { let mut torrent = self.torrent_repository.get_by_info_hash(info_hash).await?; let tracker_url = self.get_tracker_url().await; - let tracker_mode = self.get_tracker_mode().await; + let tracker_is_private = self.tracker_is_private().await; // code-review: should we remove all tracker URLs in the `announce_list` - // when the tracker is not open? + // when the tracker is private? - if tracker_mode.is_open() { + if !tracker_is_private { torrent.include_url_as_main_tracker(&tracker_url); } else if let Some(authenticated_user_id) = opt_user_id { let personal_announce_url = self.tracker_service.get_personal_announce_url(authenticated_user_id).await?; @@ -438,9 +438,9 @@ impl Index { settings.tracker.url.clone() } - async fn get_tracker_mode(&self) -> TrackerMode { + async fn tracker_is_private(&self) -> bool { let settings = self.configuration.settings.read().await; - settings.tracker.mode.clone() + settings.tracker.private } async fn build_short_torrent_response( @@ -497,11 +497,8 @@ impl Index { torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?; let tracker_url = self.get_tracker_url().await; - let tracker_mode = self.get_tracker_mode().await; - if tracker_mode.is_open() { - torrent_response.include_url_as_main_tracker(&tracker_url); - } else { + if self.tracker_is_private().await { // Add main tracker URL match opt_user_id { Some(user_id) => { @@ -513,6 +510,8 @@ impl Index { torrent_response.include_url_as_main_tracker(&tracker_url); } } + } else { + torrent_response.include_url_as_main_tracker(&tracker_url); } // Add magnet link diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index df8433d6..b52ace28 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -34,7 +34,8 @@ pub struct Website { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Tracker { pub url: Url, - pub mode: String, + pub listed: bool, + pub private: bool, pub api_url: Url, pub token: ApiToken, pub token_valid_seconds: u64, @@ -132,7 +133,8 @@ impl From for Tracker { fn from(tracker: DomainTracker) -> Self { Self { url: tracker.url, - mode: format!("{:?}", tracker.mode), + listed: tracker.listed, + private: tracker.private, api_url: tracker.api_url, token: tracker.token, token_valid_seconds: tracker.token_valid_seconds, diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index aef91661..85abfbf5 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -38,7 +38,8 @@ pub struct Website { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Tracker { pub url: Url, - pub mode: String, + pub listed: bool, + pub private: bool, pub api_url: Url, pub token: ApiToken, pub token_valid_seconds: u64, @@ -145,7 +146,8 @@ impl From for Tracker { fn from(tracker: DomainTracker) -> Self { Self { url: tracker.url, - mode: tracker.mode.to_string(), + listed: tracker.listed, + private: tracker.private, api_url: tracker.api_url, token: tracker.token, token_valid_seconds: tracker.token_valid_seconds, diff --git a/tests/common/contexts/settings/responses.rs b/tests/common/contexts/settings/responses.rs index 782a0703..4b7fdfd8 100644 --- a/tests/common/contexts/settings/responses.rs +++ b/tests/common/contexts/settings/responses.rs @@ -17,7 +17,8 @@ pub struct PublicSettingsResponse { pub struct Public { pub website_name: String, pub tracker_url: Url, - pub tracker_mode: String, + pub tracker_listed: bool, + pub tracker_private: bool, pub email_on_signup: String, } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 65b7a116..9aaad342 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,7 +1,6 @@ use std::env; -use std::str::FromStr; -use torrust_index::config::{ApiToken, TrackerMode}; +use torrust_index::config::ApiToken; use torrust_index::web::api::Version; use url::Url; @@ -88,9 +87,7 @@ impl TestEnv { }; match self.server_settings() { - Some(settings) => { - TrackerMode::from_str(&settings.tracker.mode).expect("it should be a valid tracker mode") == TrackerMode::Private - } + Some(settings) => settings.tracker.private, None => false, } } diff --git a/tests/e2e/web/api/v1/contexts/settings/contract.rs b/tests/e2e/web/api/v1/contexts/settings/contract.rs index 3bc741b7..5398ddd5 100644 --- a/tests/e2e/web/api/v1/contexts/settings/contract.rs +++ b/tests/e2e/web/api/v1/contexts/settings/contract.rs @@ -25,7 +25,8 @@ async fn it_should_allow_guests_to_get_the_public_settings() { Public { website_name: env.server_settings().unwrap().website.name, tracker_url: env.server_settings().unwrap().tracker.url, - tracker_mode: env.server_settings().unwrap().tracker.mode, + tracker_listed: env.server_settings().unwrap().tracker.listed, + tracker_private: env.server_settings().unwrap().tracker.private, email_on_signup: env.server_settings().unwrap().auth.email_on_signup, } ); From 9895346efcd738e6e3cc0cc5996a601e840c4de4 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 7 Jun 2024 20:56:06 +0200 Subject: [PATCH 233/309] feat: [#615] casbin package added --- Cargo.lock | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21bdb2e0..5668a9bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -24,6 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "const-random", "getrandom", "once_cell", "version_check", @@ -444,6 +456,26 @@ dependencies = [ "serde", ] +[[package]] +name = "casbin" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71063d3ee2f5ecc89229ccade0f3f8fb413b5e3978124a38b611216f91dd7c9" +dependencies = [ + "async-trait", + "fixedbitset", + "getrandom", + "once_cell", + "parking_lot", + "petgraph", + "regex", + "rhai", + "ritelinked", + "serde", + "thiserror", + "tokio", +] + [[package]] name = "cc" version = "1.0.104" @@ -546,6 +578,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -635,6 +687,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -868,6 +926,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.30" @@ -1114,6 +1178,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1126,7 +1199,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -1407,6 +1480,15 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -2006,6 +2088,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.2.6", +] + [[package]] name = "pico-args" version = "0.4.2" @@ -2319,6 +2411,35 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rhai" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7d88770120601ba1e548bb6bc2a05019e54ff01b51479e38e64ec3b59d4759" +dependencies = [ + "ahash 0.8.11", + "bitflags 2.5.0", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "serde", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ring" version = "0.17.8" @@ -2334,6 +2455,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ritelinked" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f2771d255fd99f0294f13249fecd0cae6e074f86b4197ec1f1689d537b44d3" +dependencies = [ + "ahash 0.7.8", + "hashbrown 0.11.2", +] + [[package]] name = "roxmltree" version = "0.14.1" @@ -2774,6 +2905,21 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "serde", + "static_assertions", + "version_check", +] [[package]] name = "socket2" @@ -2833,7 +2979,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "ahash", + "ahash 0.8.11", "atoi", "byteorder", "bytes", @@ -3026,6 +3172,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -3174,6 +3326,15 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "thin-vec" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" +dependencies = [ + "serde", +] + [[package]] name = "thiserror" version = "1.0.61" @@ -3235,6 +3396,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.6.6" @@ -3394,6 +3564,7 @@ dependencies = [ "binascii", "bytes", "camino", + "casbin", "chrono", "clap", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index f8acf76b..a9826995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ url = { version = "2.5.0", features = ["serde"] } urlencoding = "2" uuid = { version = "1", features = ["v4"] } tracing-subscriber = { version = "0.3.18", features = ["json"] } +casbin = "2.2.0" [dev-dependencies] tempfile = "3" From d9ea173dd50f69c24558d95a12638e6d4fb63b89 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 8 Jun 2024 14:11:51 +0200 Subject: [PATCH 234/309] feat: [#615] initial casbin configuration --- casbin/model.conf | 14 ++++++++++++++ casbin/policy.csv | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 casbin/model.conf create mode 100644 casbin/policy.csv diff --git a/casbin/model.conf b/casbin/model.conf new file mode 100644 index 00000000..5f7e0a7d --- /dev/null +++ b/casbin/model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, act + +[policy_definition] +p = sub, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.act == p.act \ No newline at end of file diff --git a/casbin/policy.csv b/casbin/policy.csv new file mode 100644 index 00000000..3441300b --- /dev/null +++ b/casbin/policy.csv @@ -0,0 +1,13 @@ +p, alice, data1, read +p, bob, data2, write +p, data2_admin, data2, read +p, data2_admin, data2, write +p, admin, A +p, testuser, AddCategory +p, admin, AddCategory +p, admin, ACTION::DeleteCategory + + +g, alice, data2_admins + +g, alice, data2_admin \ No newline at end of file From 7a39d0bc677b66450df4057a960dc9256e684d6f Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 11 Jun 2024 19:37:39 +0200 Subject: [PATCH 235/309] refactor: [#615] policy file allows admin user to perform any action --- casbin/policy.csv | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/casbin/policy.csv b/casbin/policy.csv index 3441300b..88610751 100644 --- a/casbin/policy.csv +++ b/casbin/policy.csv @@ -1,13 +1,6 @@ -p, alice, data1, read -p, bob, data2, write -p, data2_admin, data2, read -p, data2_admin, data2, write -p, admin, A -p, testuser, AddCategory -p, admin, AddCategory -p, admin, ACTION::DeleteCategory - - -g, alice, data2_admins - -g, alice, data2_admin \ No newline at end of file +p, true, AddCategory +p, true, DeleteCategory +p, true, GetSettings +p, true, GetSettingsSecret +p, true, AddTag +p, true, DeleteTag \ No newline at end of file From e00b0f61677e73d8f6809b2754408a4519b30960 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 11 Jun 2024 19:44:10 +0200 Subject: [PATCH 236/309] feat: [#615] added Casbin enforcer to app state --- src/app.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index ad4a5d73..94d6347c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -87,9 +87,10 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let torrent_tag_repository = Arc::new(DbTorrentTagRepository::new(database.clone())); let torrent_listing_generator = Arc::new(DbTorrentListingGenerator::new(database.clone())); let banned_user_list = Arc::new(DbBannedUserList::new(database.clone())); + let casbin_enforcer = Arc::new(authorization::CasbinEnforcer::new().await); // Services - let authorization_service = Arc::new(authorization::Service::new(user_repository.clone())); + let authorization_service = Arc::new(authorization::Service::new(user_repository.clone(), casbin_enforcer.clone())); let tracker_service = Arc::new(tracker::service::Service::new(configuration.clone(), database.clone()).await); let tracker_statistics_importer = Arc::new(StatisticsImporter::new(configuration.clone(), tracker_service.clone(), database.clone()).await); From 58a33e7dd42f3d8d4701317f580ae1efe2465a56 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 14 Jun 2024 00:43:31 +0200 Subject: [PATCH 237/309] feat: [#615] added authorization for delete torrent action --- casbin/policy.csv | 3 +- src/app.rs | 1 + src/services/authorization.rs | 219 +++++++++------------------------- src/services/torrent.rs | 14 +-- 4 files changed, 66 insertions(+), 171 deletions(-) diff --git a/casbin/policy.csv b/casbin/policy.csv index 88610751..9cf66a8b 100644 --- a/casbin/policy.csv +++ b/casbin/policy.csv @@ -3,4 +3,5 @@ p, true, DeleteCategory p, true, GetSettings p, true, GetSettingsSecret p, true, AddTag -p, true, DeleteTag \ No newline at end of file +p, true, DeleteTag +p, true, DeleteTorrent \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 94d6347c..52debae3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -116,6 +116,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running torrent_announce_url_repository.clone(), torrent_tag_repository.clone(), torrent_listing_generator.clone(), + authorization_service.clone(), )); let registration_service = Arc::new(user::RegistrationService::new( configuration.clone(), diff --git a/src/services/authorization.rs b/src/services/authorization.rs index f49c0716..42fd7ba3 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -1,10 +1,15 @@ //! Authorization service. use std::sync::Arc; +use casbin::prelude::*; +use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; + use super::user::Repository; use crate::errors::ServiceError; use crate::models::user::{UserCompact, UserId}; +#[derive(Debug, Clone, Serialize, Deserialize, Hash)] pub enum ACTION { AddCategory, DeleteCategory, @@ -12,188 +17,76 @@ pub enum ACTION { GetSettingsSecret, AddTag, DeleteTag, + DeleteTorrent, + BanUser, } pub struct Service { user_repository: Arc>, + casbin_enforcer: Arc, } impl Service { #[must_use] - pub fn new(user_repository: Arc>) -> Self { - Self { user_repository } + pub fn new(user_repository: Arc>, casbin_enforcer: Arc) -> Self { + Self { + user_repository, + casbin_enforcer, + } } + /// It returns the compact user. + /// /// # Errors /// - /// Will return an error if: + /// It returns an error if there is a database error. + pub async fn get_user(&self, user_id: UserId) -> std::result::Result { + self.user_repository.get_compact(&user_id).await + } + + ///Allows or denies an user to perform an action based on the user's privileges /// - /// - There is not any user with the provided `UserId` (when the user id is some). + /// # Errors + /// + /// Will return an error if: + /// - There is no user_id found in the request + /// - The user_id is not found in the database /// - The user is not authorized to perform the action. - pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> Result<(), ServiceError> { - match action { - ACTION::AddCategory - | ACTION::DeleteCategory - | ACTION::GetSettings - | ACTION::GetSettingsSecret - | ACTION::AddTag - | ACTION::DeleteTag => match maybe_user_id { - Some(user_id) => { - let user = self.get_user(user_id).await?; - - if !user.administrator { - return Err(ServiceError::Unauthorized); - } - - Ok(()) + pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { + match maybe_user_id { + Some(user_id) => { + let user_guard = self.get_user(user_id).await.map_err(|_| ServiceError::UserNotFound); + // the user that wants to access a resource. + let role = user_guard.unwrap().administrator; + + // the user that wants to access a resource. + let sub = role.to_string(); + + let act = action; // the operation that the user performs on the resource. + + let enforcer = self.casbin_enforcer.enforcer.read().await; + /* let enforcer = self.casbin_enforcer.clone(); + let enforcer_lock = enforcer.enforcer.read().await; */ + let authorize = enforcer.enforce((sub, act)).unwrap(); + match authorize { + true => Ok(()), + false => Err(ServiceError::Unauthorized), } - None => Err(ServiceError::Unauthorized), - }, + } + None => Err(ServiceError::Unauthorized), } } - - async fn get_user(&self, user_id: UserId) -> Result { - self.user_repository.get_compact(&user_id).await - } } -#[allow(unused_imports)] -#[cfg(test)] -mod test { - use std::str::FromStr; - use std::sync::Arc; - use mockall::predicate; - - use crate::databases::database; - use crate::errors::ServiceError; - use crate::models::user::{User, UserCompact}; - use crate::services::authorization::{Service, ACTION}; - use crate::services::user::{MockRepository, Repository}; - use crate::web::api::client::v1::random::string; - - #[tokio::test] - async fn a_guest_user_should_not_be_able_to_add_categories() { - let test_user_id = 1; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(|_| Err(ServiceError::UserNotFound)); - - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!( - service.authorize(ACTION::AddCategory, Some(test_user_id)).await, - Err(ServiceError::UserNotFound) - ); - } - - #[tokio::test] - async fn a_registered_user_should_not_be_able_to_add_categories() { - let test_user_id = 2; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(move |_| { - Ok(UserCompact { - user_id: test_user_id, - username: "non_admin_user".to_string(), - administrator: false, - }) - }); - - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!( - service.authorize(ACTION::AddCategory, Some(test_user_id)).await, - Err(ServiceError::Unauthorized) - ); - } - - #[tokio::test] - async fn an_admin_user_should_be_able_to_add_categories() { - let test_user_id = 3; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(move |_| { - Ok(UserCompact { - user_id: test_user_id, - username: "admin_user".to_string(), - administrator: true, - }) - }); - - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!(service.authorize(ACTION::AddCategory, Some(test_user_id)).await, Ok(())); - } - - #[tokio::test] - async fn a_guest_user_should_not_be_able_to_delete_categories() { - let test_user_id = 4; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(|_| Err(ServiceError::UserNotFound)); - - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!( - service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, - Err(ServiceError::UserNotFound) - ); - } - - #[tokio::test] - async fn a_registered_user_should_not_be_able_to_delete_categories() { - let test_user_id = 5; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(move |_| { - Ok(UserCompact { - user_id: test_user_id, - username: "non_admin_user".to_string(), - administrator: false, - }) - }); - - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!( - service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, - Err(ServiceError::Unauthorized) - ); - } - - #[tokio::test] - async fn an_admin_user_should_be_able_to_delete_categories() { - let test_user_id = 6; - - let mut mock_repository = MockRepository::new(); - mock_repository - .expect_get_compact() - .with(predicate::eq(test_user_id)) - .times(1) - .returning(move |_| { - Ok(UserCompact { - user_id: test_user_id, - username: "admin_user".to_string(), - administrator: true, - }) - }); +pub struct CasbinEnforcer { + enforcer: Arc>, //Arc> +} - let service = Service::new(Arc::new(Box::new(mock_repository))); - assert_eq!(service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, Ok(())); +impl CasbinEnforcer { + pub async fn new() -> Self { + let enforcer = Enforcer::new("casbin/model.conf", "casbin/policy.csv").await.unwrap(); + let enforcer = Arc::new(RwLock::new(enforcer)); + //casbin_enforcer.enable_log(true); + Self { enforcer } } } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 28aa6223..eb840b7b 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -5,6 +5,7 @@ use serde_derive::{Deserialize, Serialize}; use tracing::debug; use url::Url; +use super::authorization::{self, ACTION}; use super::category::DbCategoryRepository; use crate::config::{Configuration, TrackerMode}; use crate::databases::database::{Database, Error, Sorting}; @@ -34,6 +35,7 @@ pub struct Index { torrent_announce_url_repository: Arc, torrent_tag_repository: Arc, torrent_listing_generator: Arc, + authorization_service: Arc, } pub struct AddTorrentRequest { @@ -90,6 +92,7 @@ impl Index { torrent_announce_url_repository: Arc, torrent_tag_repository: Arc, torrent_listing_repository: Arc, + authorization_service: Arc, ) -> Self { Self { configuration, @@ -104,6 +107,7 @@ impl Index { torrent_announce_url_repository, torrent_tag_repository, torrent_listing_generator: torrent_listing_repository, + authorization_service, } } @@ -289,13 +293,9 @@ impl Index { /// * Unable to get the torrent listing from it's ID. /// * Unable to delete the torrent from the database. pub async fn delete_torrent(&self, info_hash: &InfoHash, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await?; - - // Only administrator can delete torrents. - // todo: move this to an authorization service. - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service + .authorize(ACTION::DeleteTorrent, Some(*user_id)) + .await?; let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?; From ba0ad3774b66b45d253832c4f4b99b7361d29a09 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 15 Jun 2024 12:48:36 +0200 Subject: [PATCH 238/309] refactor: [#615] code cleanup and improved comments --- src/services/authorization.rs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 42fd7ba3..07c26455 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -49,28 +49,32 @@ impl Service { /// # Errors /// /// Will return an error if: - /// - There is no user_id found in the request - /// - The user_id is not found in the database + /// - There is no user id found in the request + /// - The user id is not found in the database /// - The user is not authorized to perform the action. + pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { match maybe_user_id { Some(user_id) => { - let user_guard = self.get_user(user_id).await.map_err(|_| ServiceError::UserNotFound); - // the user that wants to access a resource. - let role = user_guard.unwrap().administrator; + // Checks if the user found in the requests exists in the database + let user_guard = self.get_user(user_id).await?; + + let role = user_guard.administrator; - // the user that wants to access a resource. + // The user that wants to access a resource. let sub = role.to_string(); - let act = action; // the operation that the user performs on the resource. + // The operation that the user wants to perform + let act = action; let enforcer = self.casbin_enforcer.enforcer.read().await; - /* let enforcer = self.casbin_enforcer.clone(); - let enforcer_lock = enforcer.enforcer.read().await; */ - let authorize = enforcer.enforce((sub, act)).unwrap(); - match authorize { - true => Ok(()), - false => Err(ServiceError::Unauthorized), + + let authorize = enforcer.enforce((sub, act)).map_err(|_| ServiceError::Unauthorized)?; + + if authorize { + Ok(()) + } else { + Err(ServiceError::Unauthorized) } } None => Err(ServiceError::Unauthorized), @@ -83,6 +87,9 @@ pub struct CasbinEnforcer { } impl CasbinEnforcer { + /// # Panics + /// + /// It panics if the policy and/or model file cannot be loaded or are missing pub async fn new() -> Self { let enforcer = Enforcer::new("casbin/model.conf", "casbin/policy.csv").await.unwrap(); let enforcer = Arc::new(RwLock::new(enforcer)); From e13f579f1a93d1409fdbd6d1641573686230d1d5 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 18 Jun 2024 15:20:41 +0200 Subject: [PATCH 239/309] feat: [#615] authorization service implemented for ban user handler --- casbin/policy.csv | 3 ++- src/app.rs | 2 +- src/services/user.rs | 14 +++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/casbin/policy.csv b/casbin/policy.csv index 9cf66a8b..c30a1609 100644 --- a/casbin/policy.csv +++ b/casbin/policy.csv @@ -4,4 +4,5 @@ p, true, GetSettings p, true, GetSettingsSecret p, true, AddTag p, true, DeleteTag -p, true, DeleteTorrent \ No newline at end of file +p, true, DeleteTorrent +p, true, BanUser \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 52debae3..968cba48 100644 --- a/src/app.rs +++ b/src/app.rs @@ -129,9 +129,9 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running user_authentication_repository.clone(), )); let ban_service = Arc::new(user::BanService::new( - user_repository.clone(), user_profile_repository.clone(), banned_user_list.clone(), + authorization_service.clone(), )); let authentication_service = Arc::new(Service::new( configuration.clone(), diff --git a/src/services/user.rs b/src/services/user.rs index 6d0e4c4e..3ee39d74 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -11,6 +11,7 @@ use pbkdf2::password_hash::rand_core::OsRng; use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; +use super::authorization::{self, ACTION}; use crate::config::{Configuration, EmailOnSignup, PasswordConstraints}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; @@ -237,22 +238,22 @@ impl ProfileService { } pub struct BanService { - user_repository: Arc>, user_profile_repository: Arc, banned_user_list: Arc, + authorization_service: Arc, } impl BanService { #[must_use] pub fn new( - user_repository: Arc>, user_profile_repository: Arc, banned_user_list: Arc, + authorization_service: Arc, ) -> Self { Self { - user_repository, user_profile_repository, banned_user_list, + authorization_service, } } @@ -268,12 +269,7 @@ impl BanService { pub async fn ban_user(&self, username_to_be_banned: &str, user_id: &UserId) -> Result<(), ServiceError> { debug!("user with ID {user_id} banning username: {username_to_be_banned}"); - let user = self.user_repository.get_compact(user_id).await?; - - // Check if user is administrator - if !user.administrator { - return Err(ServiceError::Unauthorized); - } + self.authorization_service.authorize(ACTION::BanUser, Some(*user_id)).await?; let user_profile = self .user_profile_repository From 5dc9dca95eb93cdae3d7f724cfbc678a13a7a6f5 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 20 Jun 2024 22:39:08 +0200 Subject: [PATCH 240/309] refactor: [#615] more human readable policy file --- casbin/policy.csv | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/casbin/policy.csv b/casbin/policy.csv index c30a1609..a17a9b5f 100644 --- a/casbin/policy.csv +++ b/casbin/policy.csv @@ -1,8 +1,9 @@ -p, true, AddCategory -p, true, DeleteCategory -p, true, GetSettings -p, true, GetSettingsSecret -p, true, AddTag -p, true, DeleteTag -p, true, DeleteTorrent -p, true, BanUser \ No newline at end of file +# Admin user policies +p, admin, AddCategory +p, admin, DeleteCategory +p, admin, GetSettings +p, admin, GetSettingsSecret +p, admin, AddTag +p, admin, DeleteTag +p, admin, DeleteTorrent +p, admin, BanUser \ No newline at end of file From 344857e653d34834258d482a5ad449195373b0cf Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 22 Jun 2024 23:33:31 +0200 Subject: [PATCH 241/309] refactor: [#615] model is now read from string instead of file and code cleanup --- src/services/authorization.rs | 80 +++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 07c26455..82299169 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -35,15 +35,6 @@ impl Service { } } - /// It returns the compact user. - /// - /// # Errors - /// - /// It returns an error if there is a database error. - pub async fn get_user(&self, user_id: UserId) -> std::result::Result { - self.user_repository.get_compact(&user_id).await - } - ///Allows or denies an user to perform an action based on the user's privileges /// /// # Errors @@ -59,10 +50,14 @@ impl Service { // Checks if the user found in the requests exists in the database let user_guard = self.get_user(user_id).await?; - let role = user_guard.administrator; + //Converts the bool administrator value to a string so the enforcer can handle the request and match against the policy file + let role = match user_guard.administrator { + true => "admin", + false => "guest", + }; - // The user that wants to access a resource. - let sub = role.to_string(); + // The role of the user that wants to access a resource. + let sub = role; // The operation that the user wants to perform let act = action; @@ -80,20 +75,77 @@ impl Service { None => Err(ServiceError::Unauthorized), } } + + /// It returns the compact user. + /// + /// # Errors + /// + /// It returns an error if there is a database error. + async fn get_user(&self, user_id: UserId) -> std::result::Result { + self.user_repository.get_compact(&user_id).await + } } pub struct CasbinEnforcer { - enforcer: Arc>, //Arc> + enforcer: Arc>, } impl CasbinEnforcer { /// # Panics /// /// It panics if the policy and/or model file cannot be loaded or are missing + /// + /// todo: review error handling and panics when creating the model, policy and enforcer pub async fn new() -> Self { - let enforcer = Enforcer::new("casbin/model.conf", "casbin/policy.csv").await.unwrap(); + let casbin_configuration = CasbinConfiguration::new(); + + let model = DefaultModel::from_str(&casbin_configuration.model).await.unwrap(); + + let enforcer = Enforcer::new(model, "casbin/policy.csv").await.unwrap(); + let enforcer = Arc::new(RwLock::new(enforcer)); //casbin_enforcer.enable_log(true); Self { enforcer } } } +#[allow(dead_code)] +struct CasbinConfiguration { + model: String, + policy: String, +} + +impl CasbinConfiguration { + pub fn new() -> Self { + CasbinConfiguration { + model: String::from( + " + [request_definition] + r = sub, act + + [policy_definition] + p = sub, act + + [policy_effect] + e = some(where (p.eft == allow)) + + [matchers] + m = r.sub == p.sub && r.act == p.act + ", + ), + policy: String::from( + " + # Admin user policies + p, admin, AddCategory + p, admin, DeleteCategory + p, admin, GetSettings + p, admin, GetSettingsSecret + p, admin, AddTag + p, admin, DeleteTag + p, admin, DeleteTorrent + p, admin, BanUser), + + ", + ), + } + } +} From f24afafc5158572c136c37cc52994289569caa1a Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 22 Jun 2024 23:35:21 +0200 Subject: [PATCH 242/309] refactor: [#165] removed unused model file --- casbin/model.conf | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 casbin/model.conf diff --git a/casbin/model.conf b/casbin/model.conf deleted file mode 100644 index 5f7e0a7d..00000000 --- a/casbin/model.conf +++ /dev/null @@ -1,14 +0,0 @@ -[request_definition] -r = sub, act - -[policy_definition] -p = sub, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = g(r.sub, p.sub) && r.act == p.act \ No newline at end of file From 7104b61c5fcc8415b323810c809f2533f0880a87 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 26 Jun 2024 16:11:19 +0200 Subject: [PATCH 243/309] feat: [#615] policy is now loaded from code and minor refactors --- src/services/authorization.rs | 57 ++++++++++++++--------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 82299169..21476fc6 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -47,24 +47,15 @@ impl Service { pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { match maybe_user_id { Some(user_id) => { - // Checks if the user found in the requests exists in the database + // Checks if the user found in the request exists in the database let user_guard = self.get_user(user_id).await?; //Converts the bool administrator value to a string so the enforcer can handle the request and match against the policy file - let role = match user_guard.administrator { - true => "admin", - false => "guest", - }; - - // The role of the user that wants to access a resource. - let sub = role; - - // The operation that the user wants to perform - let act = action; + let role = if user_guard.administrator { "admin" } else { "guest" }; let enforcer = self.casbin_enforcer.enforcer.read().await; - let authorize = enforcer.enforce((sub, act)).map_err(|_| ServiceError::Unauthorized)?; + let authorize = enforcer.enforce((role, action)).map_err(|_| ServiceError::Unauthorized)?; if authorize { Ok(()) @@ -93,15 +84,17 @@ pub struct CasbinEnforcer { impl CasbinEnforcer { /// # Panics /// - /// It panics if the policy and/or model file cannot be loaded or are missing - /// - /// todo: review error handling and panics when creating the model, policy and enforcer + /// It panics if the policy and/or model file cannot be loaded pub async fn new() -> Self { let casbin_configuration = CasbinConfiguration::new(); let model = DefaultModel::from_str(&casbin_configuration.model).await.unwrap(); - let enforcer = Enforcer::new(model, "casbin/policy.csv").await.unwrap(); + let policy = casbin_configuration.policy; + + let mut enforcer = Enforcer::new(model, ()).await.unwrap(); + + enforcer.add_policies(policy).await.unwrap(); let enforcer = Arc::new(RwLock::new(enforcer)); //casbin_enforcer.enable_log(true); @@ -111,7 +104,7 @@ impl CasbinEnforcer { #[allow(dead_code)] struct CasbinConfiguration { model: String, - policy: String, + policy: Vec>, } impl CasbinConfiguration { @@ -120,32 +113,28 @@ impl CasbinConfiguration { model: String::from( " [request_definition] - r = sub, act + r = role, action [policy_definition] - p = sub, act + p = role, action [policy_effect] e = some(where (p.eft == allow)) [matchers] - m = r.sub == p.sub && r.act == p.act + m = r.role == p.role && r.action == p.action ", ), - policy: String::from( - " - # Admin user policies - p, admin, AddCategory - p, admin, DeleteCategory - p, admin, GetSettings - p, admin, GetSettingsSecret - p, admin, AddTag - p, admin, DeleteTag - p, admin, DeleteTorrent - p, admin, BanUser), - - ", - ), + policy: vec![ + vec!["admin".to_owned(), "AddCategory".to_owned()], + vec!["admin".to_owned(), "DeleteCategory".to_owned()], + vec!["admin".to_owned(), "GetSettings".to_owned()], + vec!["admin".to_owned(), "GetSettingsSecret".to_owned()], + vec!["admin".to_owned(), "AddTag".to_owned()], + vec!["admin".to_owned(), "DeleteTag".to_owned()], + vec!["admin".to_owned(), "DeleteTorrent".to_owned()], + vec!["admin".to_owned(), "BanUser".to_owned()], + ], } } } From 463c5332c534ab0b67bcb739f54b79f57ef1c7de Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 27 Jun 2024 00:11:01 +0200 Subject: [PATCH 244/309] refactor: [#615] new panic messages and minor refactorings --- src/services/authorization.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 21476fc6..3eeac24a 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -88,16 +88,18 @@ impl CasbinEnforcer { pub async fn new() -> Self { let casbin_configuration = CasbinConfiguration::new(); - let model = DefaultModel::from_str(&casbin_configuration.model).await.unwrap(); + let model = DefaultModel::from_str(&casbin_configuration.model) + .await + .expect("Error loading the model"); let policy = casbin_configuration.policy; - let mut enforcer = Enforcer::new(model, ()).await.unwrap(); + let mut enforcer = Enforcer::new(model, ()).await.expect("Error creating the enforcer"); - enforcer.add_policies(policy).await.unwrap(); + enforcer.add_policies(policy).await.expect("Error loading the policy"); let enforcer = Arc::new(RwLock::new(enforcer)); - //casbin_enforcer.enable_log(true); + Self { enforcer } } } @@ -126,14 +128,14 @@ impl CasbinConfiguration { ", ), policy: vec![ - vec!["admin".to_owned(), "AddCategory".to_owned()], - vec!["admin".to_owned(), "DeleteCategory".to_owned()], - vec!["admin".to_owned(), "GetSettings".to_owned()], - vec!["admin".to_owned(), "GetSettingsSecret".to_owned()], - vec!["admin".to_owned(), "AddTag".to_owned()], - vec!["admin".to_owned(), "DeleteTag".to_owned()], - vec!["admin".to_owned(), "DeleteTorrent".to_owned()], - vec!["admin".to_owned(), "BanUser".to_owned()], + vec!["admin".to_string(), "AddCategory".to_string()], + vec!["admin".to_string(), "DeleteCategory".to_string()], + vec!["admin".to_string(), "GetSettings".to_string()], + vec!["admin".to_string(), "GetSettingsSecret".to_string()], + vec!["admin".to_string(), "AddTag".to_string()], + vec!["admin".to_string(), "DeleteTag".to_string()], + vec!["admin".to_string(), "DeleteTorrent".to_string()], + vec!["admin".to_string(), "BanUser".to_string()], ], } } From 22b0e678184fcd0173960049e5ebbaeabf2b0f88 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 27 Jun 2024 01:16:42 +0200 Subject: [PATCH 245/309] refactor: [#615] delete unused Casbin folder --- casbin/policy.csv | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 casbin/policy.csv diff --git a/casbin/policy.csv b/casbin/policy.csv deleted file mode 100644 index a17a9b5f..00000000 --- a/casbin/policy.csv +++ /dev/null @@ -1,9 +0,0 @@ -# Admin user policies -p, admin, AddCategory -p, admin, DeleteCategory -p, admin, GetSettings -p, admin, GetSettingsSecret -p, admin, AddTag -p, admin, DeleteTag -p, admin, DeleteTorrent -p, admin, BanUser \ No newline at end of file From 3671c78bc5009c26732bd4b850be735f0d2f9be7 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 30 Jun 2024 23:26:20 +0200 Subject: [PATCH 246/309] refactor: [#615] explicit Casbin imports and policy now defined as string --- src/services/authorization.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 3eeac24a..eb5e1930 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -1,7 +1,7 @@ //! Authorization service. use std::sync::Arc; -use casbin::prelude::*; +use casbin::{CoreApi, DefaultModel, Enforcer, MgmtApi}; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; @@ -92,7 +92,13 @@ impl CasbinEnforcer { .await .expect("Error loading the model"); - let policy = casbin_configuration.policy; + // Converts the policy from a string type to a vector + let policy = casbin_configuration + .policy + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::>()) + .collect(); let mut enforcer = Enforcer::new(model, ()).await.expect("Error creating the enforcer"); @@ -106,7 +112,7 @@ impl CasbinEnforcer { #[allow(dead_code)] struct CasbinConfiguration { model: String, - policy: Vec>, + policy: String, } impl CasbinConfiguration { @@ -127,16 +133,19 @@ impl CasbinConfiguration { m = r.role == p.role && r.action == p.action ", ), - policy: vec![ - vec!["admin".to_string(), "AddCategory".to_string()], - vec!["admin".to_string(), "DeleteCategory".to_string()], - vec!["admin".to_string(), "GetSettings".to_string()], - vec!["admin".to_string(), "GetSettingsSecret".to_string()], - vec!["admin".to_string(), "AddTag".to_string()], - vec!["admin".to_string(), "DeleteTag".to_string()], - vec!["admin".to_string(), "DeleteTorrent".to_string()], - vec!["admin".to_string(), "BanUser".to_string()], - ], + policy: String::from( + " + admin, AddCategory + admin, DeleteCategory + admin, GetSettings + admin, GetSettingsSecret + admin, AddTag + admin, DeleteTag + admin, DeleteTorrent + admin, BanUser + + ", + ), } } } From 6e1877c4a8feb8c569158b8bc2cbcb19702baf28 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 2 Jul 2024 23:08:09 +0200 Subject: [PATCH 247/309] refactor: [#615] new get role method, new user role enum and minor refactoring --- src/services/authorization.rs | 68 +++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index eb5e1930..89dbbfef 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -1,4 +1,5 @@ //! Authorization service. +use std::fmt; use std::sync::Arc; use casbin::{CoreApi, DefaultModel, Enforcer, MgmtApi}; @@ -9,6 +10,25 @@ use super::user::Repository; use crate::errors::ServiceError; use crate::models::user::{UserCompact, UserId}; +#[derive(Debug, PartialEq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "lowercase")] +enum UserRole { + Admin, + Registered, + Guest, +} + +impl fmt::Display for UserRole { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let role_str = match self { + UserRole::Admin => "admin", + UserRole::Registered => "registered", + UserRole::Guest => "guest", + }; + write!(f, "{role_str}") + } +} + #[derive(Debug, Clone, Serialize, Deserialize, Hash)] pub enum ACTION { AddCategory, @@ -40,30 +60,19 @@ impl Service { /// # Errors /// /// Will return an error if: - /// - There is no user id found in the request - /// - The user id is not found in the database /// - The user is not authorized to perform the action. pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { - match maybe_user_id { - Some(user_id) => { - // Checks if the user found in the request exists in the database - let user_guard = self.get_user(user_id).await?; + let role = self.get_role(maybe_user_id).await; - //Converts the bool administrator value to a string so the enforcer can handle the request and match against the policy file - let role = if user_guard.administrator { "admin" } else { "guest" }; + let enforcer = self.casbin_enforcer.enforcer.read().await; - let enforcer = self.casbin_enforcer.enforcer.read().await; + let authorize = enforcer.enforce((role, action)).map_err(|_| ServiceError::Unauthorized)?; - let authorize = enforcer.enforce((role, action)).map_err(|_| ServiceError::Unauthorized)?; - - if authorize { - Ok(()) - } else { - Err(ServiceError::Unauthorized) - } - } - None => Err(ServiceError::Unauthorized), + if authorize { + Ok(()) + } else { + Err(ServiceError::Unauthorized) } } @@ -75,6 +84,29 @@ impl Service { async fn get_user(&self, user_id: UserId) -> std::result::Result { self.user_repository.get_compact(&user_id).await } + + /// It returns the role of the user. + /// If the user found in the request does not exist in the database or there is no user id, a guest role is returned + async fn get_role(&self, maybe_user_id: Option) -> UserRole { + match maybe_user_id { + Some(user_id) => { + // Checks if the user found in the request exists in the database + let user_guard = self.get_user(user_id).await; + + match user_guard { + Ok(user_data) => { + if user_data.administrator { + UserRole::Admin + } else { + UserRole::Registered + } + } + Err(_) => UserRole::Guest, + } + } + None => UserRole::Guest, + } + } } pub struct CasbinEnforcer { From 389acc49c4119c8b697226a5e6902d0b402f1927 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 3 Jul 2024 14:10:32 +0200 Subject: [PATCH 248/309] chore(deps): update dependencies --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5668a9bc..bad27c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2418,7 +2418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a7d88770120601ba1e548bb6bc2a05019e54ff01b51479e38e64ec3b59d4759" dependencies = [ "ahash 0.8.11", - "bitflags 2.5.0", + "bitflags 2.6.0", "instant", "num-traits", "once_cell", @@ -2437,7 +2437,7 @@ checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] From e2a42edc2dc9a791456fa4f8d01afc8e4e01e479 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 5 Jul 2024 10:35:18 +0100 Subject: [PATCH 249/309] fix: [#665] update tracker database driver config Options for drivers use lowervcase now. --- Containerfile | 2 +- contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh | 2 +- share/default/config/tracker.private.e2e.container.sqlite3.toml | 2 +- share/default/config/tracker.public.e2e.container.sqlite3.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Containerfile b/Containerfile index 0c09684f..2c3d9b4f 100644 --- a/Containerfile +++ b/Containerfile @@ -98,7 +98,7 @@ RUN ["/busybox/cp", "-sp", "/busybox/sh","/busybox/cat","/busybox/ls","/busybox/ COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec ARG TORRUST_INDEX_CONFIG_TOML_PATH="/etc/torrust/index/index.toml" -ARG TORRUST_INDEX_DATABASE_DRIVER="Sqlite3" +ARG TORRUST_INDEX_DATABASE_DRIVER="sqlite3" ARG USER_ID=1000 ARG API_PORT=3001 ARG IMPORTER_API_PORT=3002 diff --git a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh index ac043345..328ee71a 100755 --- a/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/mysql/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_MYSQL_DATABASE="torrust_index_e2e_testing" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh index 382b8761..839ce5f4 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.private.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh index 40fee11d..c252b47f 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh @@ -11,6 +11,6 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ - TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="Sqlite3" \ + TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \ docker compose up --detach --pull always --remove-orphans diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 63e7cc47..9672f270 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -4,7 +4,7 @@ version = "2" private = true [core.database] -driver = "Sqlite3" +driver = "sqlite3" path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" [[udp_trackers]] diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index 3dfa2e7b..b5d8de7b 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -4,7 +4,7 @@ version = "2" private = true [core.database] -driver = "Sqlite3" +driver = "sqlite3" path = "/var/lib/torrust/tracker/database/e2e_testing_sqlite3.db" [[http_trackers]] From 1c5e862f07ab3b25cafa526c1271c5d0d7a73629 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 13:39:55 +0100 Subject: [PATCH 250/309] feat: [#654] new registration confg section Registration Disabled: ```toml ``` Registration without email option: ```toml [registration] ``` Registration optionally including an email address: ```toml [registration] [registration.email] ``` Registration with email: ```toml [registration] [registration.email] required = true ``` Registration with optional email, but if email is supplied it is verified: ```toml [registration] [registration.email] verified = true ``` Registration with email and email is verified: ```toml [registration] [registration.email] required = true verified = true ``` TODO: remove old settings and use the new ones. --- .../default/config/index.container.mysql.toml | 2 + .../config/index.container.sqlite3.toml | 3 + .../config/index.development.sqlite3.toml | 3 + .../index.private.e2e.container.sqlite3.toml | 3 + .../index.public.e2e.container.mysql.toml | 3 + .../index.public.e2e.container.sqlite3.toml | 3 + src/config/v2/mod.rs | 96 ++++++++++++++++--- src/config/v2/registration.rs | 54 +++++++++++ 8 files changed, 156 insertions(+), 11 deletions(-) create mode 100644 src/config/v2/registration.rs diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 2626c2f9..4a352a54 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -15,3 +15,5 @@ connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" port = 1025 server = "mailcatcher" +[registration] +[registration.email] diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 2c5bf978..cd73ce0a 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -14,3 +14,6 @@ connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" [mail.smtp] port = 1025 server = "mailcatcher" + +[registration] +[registration.email] diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index eaae9bd3..ecd66d98 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -12,3 +12,6 @@ threshold = "info" #[net.tsl] #ssl_cert_path = "./storage/index/lib/tls/localhost.crt" #ssl_key_path = "./storage/index/lib/tls/localhost.key" + +[registration] +[registration.email] diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index 48a34e81..6226bc63 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -20,3 +20,6 @@ connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?m [mail.smtp] port = 1025 server = "mailcatcher" + +[registration] +[registration.email] \ No newline at end of file diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 6ed38608..5ef2c3d8 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -18,3 +18,6 @@ connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_te [mail.smtp] port = 1025 server = "mailcatcher" + +[registration] +[registration.email] \ No newline at end of file diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 6177b9c7..8547d565 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -18,3 +18,6 @@ connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?m [mail.smtp] port = 1025 server = "mailcatcher" + +[registration] +[registration.email] \ No newline at end of file diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index 5bb9096f..53af3dc8 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -5,11 +5,13 @@ pub mod image_cache; pub mod logging; pub mod mail; pub mod net; +pub mod registration; pub mod tracker; pub mod tracker_statistics_importer; pub mod website; use logging::Logging; +use registration::Registration; use serde::{Deserialize, Serialize}; use self::api::Api; @@ -25,53 +27,77 @@ use super::validator::{ValidationError, Validator}; use super::Metadata; /// The whole configuration for the index. -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { /// Configuration metadata. + #[serde(default = "Settings::default_metadata")] #[serde(flatten)] pub metadata: Metadata, /// The logging configuration. - #[serde(default)] + #[serde(default = "Settings::default_logging")] pub logging: Logging, /// The website customizable values. - #[serde(default)] + #[serde(default = "Settings::default_website")] pub website: Website, /// The tracker configuration. - #[serde(default)] + #[serde(default = "Settings::default_tracker")] pub tracker: Tracker, /// The network configuration. - #[serde(default)] + #[serde(default = "Settings::default_network")] pub net: Network, /// The authentication configuration. - #[serde(default)] + #[serde(default = "Settings::default_auth")] pub auth: Auth, /// The database configuration. - #[serde(default)] + #[serde(default = "Settings::default_database")] pub database: Database, /// The SMTP configuration. - #[serde(default)] + #[serde(default = "Settings::default_mail")] pub mail: Mail, /// The image proxy cache configuration. - #[serde(default)] + #[serde(default = "Settings::default_image_cache")] pub image_cache: ImageCache, /// The API configuration. - #[serde(default)] + #[serde(default = "Settings::default_api")] pub api: Api, + /// The registration configuration. + #[serde(default = "Settings::default_registration")] + pub registration: Option, + /// The tracker statistics importer job configuration. - #[serde(default)] + #[serde(default = "Settings::default_tracker_statistics_importer")] pub tracker_statistics_importer: TrackerStatisticsImporter, } +impl Default for Settings { + fn default() -> Self { + Self { + metadata: Self::default_metadata(), + logging: Self::default_logging(), + website: Self::default_website(), + tracker: Self::default_tracker(), + net: Self::default_network(), + auth: Self::default_auth(), + database: Self::default_database(), + mail: Self::default_mail(), + image_cache: Self::default_image_cache(), + api: Self::default_api(), + registration: Self::default_registration(), + tracker_statistics_importer: Self::default_tracker_statistics_importer(), + } + } +} + impl Settings { pub fn remove_secrets(&mut self) { self.tracker.token = ApiToken::new("***"); @@ -101,6 +127,54 @@ impl Settings { pub fn to_json(&self) -> String { serde_json::to_string_pretty(self).expect("Could not encode JSON value") } + + fn default_metadata() -> Metadata { + Metadata::default() + } + + fn default_logging() -> Logging { + Logging::default() + } + + fn default_website() -> Website { + Website::default() + } + + fn default_tracker() -> Tracker { + Tracker::default() + } + + fn default_network() -> Network { + Network::default() + } + + fn default_auth() -> Auth { + Auth::default() + } + + fn default_database() -> Database { + Database::default() + } + + fn default_mail() -> Mail { + Mail::default() + } + + fn default_image_cache() -> ImageCache { + ImageCache::default() + } + + fn default_api() -> Api { + Api::default() + } + + fn default_registration() -> Option { + None + } + + fn default_tracker_statistics_importer() -> TrackerStatisticsImporter { + TrackerStatisticsImporter::default() + } } impl Validator for Settings { diff --git a/src/config/v2/registration.rs b/src/config/v2/registration.rs new file mode 100644 index 00000000..5052fb75 --- /dev/null +++ b/src/config/v2/registration.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Registration { + /// Whether or not to enable email verification on signup. + #[serde(default = "Registration::default_email")] + pub email: Option, +} + +impl Default for Registration { + fn default() -> Self { + Self { + email: Self::default_email(), + } + } +} + +impl Registration { + fn default_email() -> Option { + None + } +} + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Email { + /// Whether or not email is required on signup. + #[serde(default = "Email::default_required")] + pub required: bool, + + /// Whether or not email is verified. + #[serde(default = "Email::default_verified")] + pub verified: bool, +} + +impl Default for Email { + fn default() -> Self { + Self { + required: Self::default_required(), + verified: Self::default_verified(), + } + } +} + +impl Email { + fn default_required() -> bool { + false + } + + fn default_verified() -> bool { + false + } +} From 29cc9dcc28e593d2b6c5789fad67a01f5fc8b3a5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 14:44:11 +0100 Subject: [PATCH 251/309] feat: [#654] remove config option email_on_signup --- src/config/mod.rs | 86 ++++++++++++- src/config/v2/auth.rs | 54 -------- src/lib.rs | 1 - src/services/user.rs | 120 +++++++++--------- .../api/client/v1/contexts/settings/mod.rs | 2 - .../api/server/v1/contexts/settings/mod.rs | 9 +- src/web/api/server/v1/contexts/user/mod.rs | 1 - tests/common/contexts/settings/mod.rs | 39 +++++- .../web/api/v1/contexts/settings/contract.rs | 17 ++- tests/environments/isolated.rs | 9 ++ 10 files changed, 208 insertions(+), 130 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 5a9cf071..99550c0a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,8 +2,9 @@ pub mod v2; pub mod validator; -use std::env; +use std::str::FromStr; use std::sync::Arc; +use std::{env, fmt}; use camino::Utf8PathBuf; use derive_more::Display; @@ -22,8 +23,10 @@ pub type Settings = v2::Settings; pub type Api = v2::api::Api; +pub type Registration = v2::registration::Registration; +pub type Email = v2::registration::Email; + pub type Auth = v2::auth::Auth; -pub type EmailOnSignup = v2::auth::EmailOnSignup; pub type SecretKey = v2::auth::SecretKey; pub type PasswordConstraints = v2::auth::PasswordConstraints; @@ -301,12 +304,26 @@ impl Configuration { pub async fn get_public(&self) -> ConfigurationPublic { let settings_lock = self.settings.read().await; + let email_on_signup = match &settings_lock.registration { + Some(registration) => match ®istration.email { + Some(email) => { + if email.required { + EmailOnSignup::Required + } else { + EmailOnSignup::Optional + } + } + None => EmailOnSignup::NotIncluded, + }, + None => EmailOnSignup::NotIncluded, + }; + ConfigurationPublic { website_name: settings_lock.website.name.clone(), tracker_url: settings_lock.tracker.url.clone(), tracker_listed: settings_lock.tracker.listed, tracker_private: settings_lock.tracker.private, - email_on_signup: settings_lock.auth.email_on_signup.clone(), + email_on_signup, } } @@ -333,12 +350,56 @@ pub struct ConfigurationPublic { email_on_signup: EmailOnSignup, } +/// Whether the email is required on signup or not. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum EmailOnSignup { + /// The email is required on signup. + Required, + /// The email is optional on signup. + Optional, + /// The email is not allowed on signup. It will only be ignored if provided. + NotIncluded, +} + +impl Default for EmailOnSignup { + fn default() -> Self { + Self::Optional + } +} + +impl fmt::Display for EmailOnSignup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + EmailOnSignup::Required => "required", + EmailOnSignup::Optional => "optional", + EmailOnSignup::NotIncluded => "ignored", + }; + write!(f, "{display_str}") + } +} + +impl FromStr for EmailOnSignup { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "required" => Ok(EmailOnSignup::Required), + "optional" => Ok(EmailOnSignup::Optional), + "none" => Ok(EmailOnSignup::NotIncluded), + _ => Err(format!( + "Unknown config 'email_on_signup' option (required, optional, none): {s}" + )), + } + } +} + #[cfg(test)] mod tests { use url::Url; - use crate::config::{ApiToken, Configuration, ConfigurationPublic, Info, SecretKey, Settings}; + use crate::config::{ApiToken, Configuration, ConfigurationPublic, EmailOnSignup, Info, SecretKey, Settings}; #[cfg(test)] fn default_config_toml() -> String { @@ -362,7 +423,6 @@ mod tests { bind_address = "0.0.0.0:3001" [auth] - email_on_signup = "optional" secret_key = "MaxVerstappenWC2021" [auth.password_constraints] @@ -430,6 +490,20 @@ mod tests { let configuration = Configuration::default(); let all_settings = configuration.get_all().await; + let email_on_signup = match &all_settings.registration { + Some(registration) => match ®istration.email { + Some(email) => { + if email.required { + EmailOnSignup::Required + } else { + EmailOnSignup::Optional + } + } + None => EmailOnSignup::NotIncluded, + }, + None => EmailOnSignup::NotIncluded, + }; + assert_eq!( configuration.get_public().await, ConfigurationPublic { @@ -437,7 +511,7 @@ mod tests { tracker_url: all_settings.tracker.url, tracker_listed: all_settings.tracker.listed, tracker_private: all_settings.tracker.private, - email_on_signup: all_settings.auth.email_on_signup, + email_on_signup, } ); } diff --git a/src/config/v2/auth.rs b/src/config/v2/auth.rs index 71147e4c..092b7e5b 100644 --- a/src/config/v2/auth.rs +++ b/src/config/v2/auth.rs @@ -1,15 +1,10 @@ use std::fmt; -use std::str::FromStr; use serde::{Deserialize, Serialize}; /// Authentication options. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { - /// Whether or not to require an email on signup. - #[serde(default = "Auth::default_email_on_signup")] - pub email_on_signup: EmailOnSignup, - /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, @@ -22,7 +17,6 @@ pub struct Auth { impl Default for Auth { fn default() -> Self { Self { - email_on_signup: EmailOnSignup::default(), password_constraints: Self::default_password_constraints(), secret_key: Self::default_secret_key(), } @@ -34,10 +28,6 @@ impl Auth { self.secret_key = SecretKey::new(secret_key); } - fn default_email_on_signup() -> EmailOnSignup { - EmailOnSignup::default() - } - fn default_secret_key() -> SecretKey { SecretKey::new("MaxVerstappenWC2021") } @@ -47,50 +37,6 @@ impl Auth { } } -/// Whether the email is required on signup or not. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum EmailOnSignup { - /// The email is required on signup. - Required, - /// The email is optional on signup. - Optional, - /// The email is not allowed on signup. It will only be ignored if provided. - Ignored, -} - -impl Default for EmailOnSignup { - fn default() -> Self { - Self::Optional - } -} - -impl fmt::Display for EmailOnSignup { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display_str = match self { - EmailOnSignup::Required => "required", - EmailOnSignup::Optional => "optional", - EmailOnSignup::Ignored => "ignored", - }; - write!(f, "{display_str}") - } -} - -impl FromStr for EmailOnSignup { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "required" => Ok(EmailOnSignup::Required), - "optional" => Ok(EmailOnSignup::Optional), - "none" => Ok(EmailOnSignup::Ignored), - _ => Err(format!( - "Unknown config 'email_on_signup' option (required, optional, ignored): {s}" - )), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SecretKey(String); diff --git a/src/lib.rs b/src/lib.rs index a3e460ac..6a14843e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,7 +181,6 @@ //! bind_address = "0.0.0.0:3001" //! //! [auth] -//! email_on_signup = "optional" //! secret_key = "MaxVerstappenWC2021" //! //! [auth.password_constraints] diff --git a/src/services/user.rs b/src/services/user.rs index 3ee39d74..7185fe65 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -12,7 +12,7 @@ use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; use super::authorization::{self, ACTION}; -use crate::config::{Configuration, EmailOnSignup, PasswordConstraints}; +use crate::config::{Configuration, PasswordConstraints}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::mailer; @@ -74,71 +74,75 @@ impl RegistrationService { pub async fn register_user(&self, registration_form: &RegistrationForm, api_base_url: &str) -> Result { info!("registering user: {}", registration_form.username); - let Ok(username) = registration_form.username.parse::() else { - return Err(ServiceError::UsernameInvalid); - }; - let settings = self.configuration.settings.read().await; - let opt_email = match settings.auth.email_on_signup { - EmailOnSignup::Required => { - if registration_form.email.is_none() { - return Err(ServiceError::EmailMissing); + match &settings.registration { + Some(registration) => { + let Ok(username) = registration_form.username.parse::() else { + return Err(ServiceError::UsernameInvalid); + }; + + let opt_email = match ®istration.email { + Some(email) => { + if email.required && registration_form.email.is_none() { + return Err(ServiceError::EmailMissing); + } + registration_form.email.clone() + } + None => None, + }; + + if let Some(email) = ®istration_form.email { + if !validate_email_address(email) { + return Err(ServiceError::EmailInvalid); + } } - registration_form.email.clone() - } - EmailOnSignup::Ignored => None, - EmailOnSignup::Optional => registration_form.email.clone(), - }; - - if let Some(email) = ®istration_form.email { - if !validate_email_address(email) { - return Err(ServiceError::EmailInvalid); - } - } - - let password_constraints = PasswordConstraints { - min_password_length: settings.auth.password_constraints.min_password_length, - max_password_length: settings.auth.password_constraints.max_password_length, - }; - validate_password_constraints( - ®istration_form.password, - ®istration_form.confirm_password, - &password_constraints, - )?; - - let password_hash = hash_password(®istration_form.password)?; - - let user_id = self - .user_repository - .add( - &username.to_string(), - &opt_email.clone().unwrap_or(no_email()), - &password_hash, - ) - .await?; - - // If this is the first created account, give administrator rights - if user_id == 1 { - drop(self.user_repository.grant_admin_role(&user_id).await); - } - - if settings.mail.email_verification_enabled { - if let Some(email) = opt_email { - let mail_res = self - .mailer - .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) - .await; + let password_constraints = PasswordConstraints { + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, + }; + + validate_password_constraints( + ®istration_form.password, + ®istration_form.confirm_password, + &password_constraints, + )?; + + let password_hash = hash_password(®istration_form.password)?; + + let user_id = self + .user_repository + .add( + &username.to_string(), + &opt_email.clone().unwrap_or(no_email()), + &password_hash, + ) + .await?; + + // If this is the first created account, give administrator rights + if user_id == 1 { + drop(self.user_repository.grant_admin_role(&user_id).await); + } - if mail_res.is_err() { - drop(self.user_repository.delete(&user_id).await); - return Err(ServiceError::FailedToSendVerificationEmail); + if settings.mail.email_verification_enabled { + if let Some(email) = opt_email { + let mail_res = self + .mailer + .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) + .await; + + if mail_res.is_err() { + drop(self.user_repository.delete(&user_id).await); + return Err(ServiceError::FailedToSendVerificationEmail); + } + } } + + Ok(user_id) } + None => Err(ServiceError::ClosedForRegistration), } - - Ok(user_id) } /// It verifies the email address of a user via the token sent to the diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index b52ace28..d5d148aa 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -49,7 +49,6 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { - pub email_on_signup: String, pub secret_key: String, pub password_constraints: PasswordConstraints, } @@ -154,7 +153,6 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - email_on_signup: format!("{:?}", auth.email_on_signup), secret_key: auth.secret_key.to_string(), password_constraints: auth.password_constraints.into(), } diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index b30cd96b..4551b591 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -45,7 +45,6 @@ //! "base_url": null //! }, //! "auth": { -//! "email_on_signup": "optional", //! "min_password_length": 6, //! "max_password_length": 64, //! "secret_key": "MaxVerstappenWC2021" @@ -73,6 +72,12 @@ //! "default_torrent_page_size": 10, //! "max_torrent_page_size": 30 //! }, +//! "registration": { +//! "email": { +//! "required": false, +//! "verified": false +//! } +//! }, //! "tracker_statistics_importer": { //! "torrent_info_update_interval": 3600 //! "port": 3002 @@ -102,7 +107,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index 1d2eb2a3..5d9be86b 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,7 +45,6 @@ //! //! ```toml //! [auth] -//! email_on_signup = "Optional" //! secret_key = "MaxVerstappenWC2021" //! ``` //! diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 85abfbf5..45f40e25 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -5,9 +5,10 @@ use std::net::SocketAddr; use serde::{Deserialize, Serialize}; use torrust_index::config::{ Api as DomainApi, ApiToken, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, - ImageCache as DomainImageCache, Logging as DomainLogging, Mail as DomainMail, Network as DomainNetwork, - PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, Smtp as DomainSmtp, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Email as DomainEmail, ImageCache as DomainImageCache, Logging as DomainLogging, Mail as DomainMail, Network as DomainNetwork, + PasswordConstraints as DomainPasswordConstraints, Registration as DomainRegistration, Settings as DomainSettings, + Smtp as DomainSmtp, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, + Website as DomainWebsite, }; use url::Url; @@ -22,6 +23,7 @@ pub struct Settings { pub mail: Mail, pub image_cache: ImageCache, pub api: Api, + pub registration: Option, pub tracker_statistics_importer: TrackerStatisticsImporter, } @@ -53,7 +55,6 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { - pub email_on_signup: String, pub secret_key: String, pub password_constraints: PasswordConstraints, } @@ -105,6 +106,17 @@ pub struct Api { pub max_torrent_page_size: u8, } +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Registration { + pub email: Option, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Email { + pub required: bool, + pub verified: bool, +} + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct TrackerStatisticsImporter { pub torrent_info_update_interval: u64, @@ -123,6 +135,7 @@ impl From for Settings { mail: Mail::from(settings.mail), image_cache: ImageCache::from(settings.image_cache), api: Api::from(settings.api), + registration: settings.registration.map(Registration::from), tracker_statistics_importer: TrackerStatisticsImporter::from(settings.tracker_statistics_importer), } } @@ -167,7 +180,6 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - email_on_signup: auth.email_on_signup.to_string(), secret_key: auth.secret_key.to_string(), password_constraints: auth.password_constraints.into(), } @@ -242,6 +254,23 @@ impl From for Api { } } +impl From for Registration { + fn from(registration: DomainRegistration) -> Self { + Self { + email: registration.email.map(Email::from), + } + } +} + +impl From for Email { + fn from(email: DomainEmail) -> Self { + Self { + required: email.required, + verified: email.verified, + } + } +} + impl From for TrackerStatisticsImporter { fn from(tracker_statistics_importer: DomainTrackerStatisticsImporter) -> Self { Self { diff --git a/tests/e2e/web/api/v1/contexts/settings/contract.rs b/tests/e2e/web/api/v1/contexts/settings/contract.rs index 5398ddd5..8b3eb08a 100644 --- a/tests/e2e/web/api/v1/contexts/settings/contract.rs +++ b/tests/e2e/web/api/v1/contexts/settings/contract.rs @@ -1,5 +1,6 @@ //! API contract for `settings` context. +use torrust_index::config::EmailOnSignup; use torrust_index::web::api; use crate::common::asserts::assert_json_ok_response; @@ -20,6 +21,20 @@ async fn it_should_allow_guests_to_get_the_public_settings() { let res: PublicSettingsResponse = serde_json::from_str(&response.body) .unwrap_or_else(|_| panic!("response {:#?} should be a PublicSettingsResponse", response.body)); + let email_on_signup = match &env.server_settings().unwrap().registration { + Some(registration) => match ®istration.email { + Some(email) => { + if email.required { + EmailOnSignup::Required + } else { + EmailOnSignup::Optional + } + } + None => EmailOnSignup::NotIncluded, + }, + None => EmailOnSignup::NotIncluded, + }; + assert_eq!( res.data, Public { @@ -27,7 +42,7 @@ async fn it_should_allow_guests_to_get_the_public_settings() { tracker_url: env.server_settings().unwrap().tracker.url, tracker_listed: env.server_settings().unwrap().tracker.listed, tracker_private: env.server_settings().unwrap().tracker.private, - email_on_signup: env.server_settings().unwrap().auth.email_on_signup, + email_on_signup: email_on_signup.to_string(), } ); diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index e13207d2..12c04803 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -2,6 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tempfile::TempDir; use torrust_index::config; +use torrust_index::config::v2::registration::{Email, Registration}; use torrust_index::config::{Threshold, FREE_PORT}; use torrust_index::web::api::Version; use url::Url; @@ -87,6 +88,14 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { configuration.database.connect_url = Url::parse(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))).unwrap(); + // Enable user registration + configuration.registration = Some(Registration { + email: Some(Email { + required: false, + verified: false, + }), + }); + configuration } From e0dbdbe0aa21e2d2b9a49936597dceedf01dffef Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 16:40:06 +0100 Subject: [PATCH 252/309] feat: [#654] remove config option email_verification_enabled The config option: ```toml [mail] email_verification_enabled = false ``` was replaced with: ```toml [registration] [registration.email] verified = true ``` --- src/config/mod.rs | 1 - src/config/v2/mail.rs | 9 ---- src/lib.rs | 1 - src/services/authentication.rs | 8 ++- src/services/user.rs | 26 ++++++---- .../api/client/v1/contexts/settings/mod.rs | 2 - .../api/server/v1/contexts/settings/mod.rs | 50 ++++++++++++------- tests/common/contexts/settings/mod.rs | 2 - 8 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 99550c0a..87154ed8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -433,7 +433,6 @@ mod tests { connect_url = "sqlite://data.db?mode=rwc" [mail] - email_verification_enabled = false from = "example@email.com" reply_to = "noreply@email.com" diff --git a/src/config/v2/mail.rs b/src/config/v2/mail.rs index e171d41b..296b4d19 100644 --- a/src/config/v2/mail.rs +++ b/src/config/v2/mail.rs @@ -4,10 +4,6 @@ use serde::{Deserialize, Serialize}; /// SMTP configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Mail { - /// Whether or not to enable email verification on signup. - #[serde(default = "Mail::default_email_verification_enabled")] - pub email_verification_enabled: bool, - /// The email address to send emails from. #[serde(default = "Mail::default_from")] pub from: Mailbox, @@ -24,7 +20,6 @@ pub struct Mail { impl Default for Mail { fn default() -> Self { Self { - email_verification_enabled: Self::default_email_verification_enabled(), from: Self::default_from(), reply_to: Self::default_reply_to(), smtp: Self::default_smtp(), @@ -33,10 +28,6 @@ impl Default for Mail { } impl Mail { - fn default_email_verification_enabled() -> bool { - false - } - fn default_from() -> Mailbox { "example@email.com".parse().expect("valid mailbox") } diff --git a/src/lib.rs b/src/lib.rs index 6a14843e..6b9e3d54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,7 +191,6 @@ //! connect_url = "sqlite://data.db?mode=rwc" //! //! [mail] -//! email_verification_enabled = false //! from = "example@email.com" //! reply_to = "noreply@email.com" //! diff --git a/src/services/authentication.rs b/src/services/authentication.rs index cd9dd9d3..f347a857 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -70,8 +70,12 @@ impl Service { let settings = self.configuration.settings.read().await; // Fail login if email verification is required and this email is not verified - if settings.mail.email_verification_enabled && !user_profile.email_verified { - return Err(ServiceError::EmailNotVerified); + if let Some(registration) = &settings.registration { + if let Some(email) = ®istration.email { + if email.verified && !user_profile.email_verified { + return Err(ServiceError::EmailNotVerified); + } + } } // Drop read lock on settings diff --git a/src/services/user.rs b/src/services/user.rs index 7185fe65..ff885dcd 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -125,18 +125,24 @@ impl RegistrationService { drop(self.user_repository.grant_admin_role(&user_id).await); } - if settings.mail.email_verification_enabled { - if let Some(email) = opt_email { - let mail_res = self - .mailer - .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) - .await; - - if mail_res.is_err() { - drop(self.user_repository.delete(&user_id).await); - return Err(ServiceError::FailedToSendVerificationEmail); + match ®istration.email { + Some(email) => { + if email.verified { + // Email verification is enabled + if let Some(email) = opt_email { + let mail_res = self + .mailer + .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) + .await; + + if mail_res.is_err() { + drop(self.user_repository.delete(&user_id).await); + return Err(ServiceError::FailedToSendVerificationEmail); + } + } } } + None => (), } Ok(user_id) diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index d5d148aa..fdc52518 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -66,7 +66,6 @@ pub struct Database { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Mail { - pub email_verification_enabled: bool, pub from: String, pub reply_to: String, pub smtp: Smtp, @@ -179,7 +178,6 @@ impl From for Database { impl From for Mail { fn from(mail: DomainMail) -> Self { Self { - email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), smtp: Smtp::from(mail.smtp), diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index 4551b591..9d0e80a1 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -30,43 +30,55 @@ //! ```json //! { //! "data": { +//! "version": "2", +//! "logging": { +//! "threshold": "info" +//! }, //! "website": { //! "name": "Torrust" //! }, //! "tracker": { -//! "url": "udp://localhost:6969", -//! "mode": "public", //! "api_url": "http://localhost:1212/", -//! "token": "MyAccessToken", -//! "token_valid_seconds": 7257600 +//! "listed": false, +//! "private": false, +//! "token": "***", +//! "token_valid_seconds": 7257600, +//! "url": "udp://localhost:6969" //! }, //! "net": { -//! "port": 3001, -//! "base_url": null +//! "base_url": null, +//! "bind_address": "0.0.0.0:3001", +//! "tsl": null //! }, //! "auth": { -//! "min_password_length": 6, -//! "max_password_length": 64, -//! "secret_key": "MaxVerstappenWC2021" +//! "secret_key": "***", +//! "password_constraints": { +//! "max_password_length": 64, +//! "min_password_length": 6 +//! } //! }, //! "database": { -//! "connect_url": "sqlite://./storage/database/data.db?mode=rwc" +//! "connect_url": "sqlite://data.db?mode=rwc" //! }, //! "mail": { //! "email_verification_enabled": false, //! "from": "example@email.com", //! "reply_to": "noreply@email.com", -//! "username": "", -//! "password": "", -//! "server": "", -//! "port": 25 +//! "smtp": { +//! "port": 25, +//! "server": "", +//! "credentials": { +//! "password": "***", +//! "username": "" +//! } +//! } //! }, //! "image_cache": { -//! "max_request_timeout_ms": 1000, //! "capacity": 128000000, //! "entry_size_limit": 4000000, -//! "user_quota_period_seconds": 3600, -//! "user_quota_bytes": 64000000 +//! "max_request_timeout_ms": 1000, +//! "user_quota_bytes": 64000000, +//! "user_quota_period_seconds": 3600 //! }, //! "api": { //! "default_torrent_page_size": 10, @@ -79,8 +91,8 @@ //! } //! }, //! "tracker_statistics_importer": { +//! "port": 3002, //! "torrent_info_update_interval": 3600 -//! "port": 3002 //! } //! } //! } @@ -107,7 +119,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 45f40e25..baa17610 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -72,7 +72,6 @@ pub struct Database { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Mail { - pub email_verification_enabled: bool, pub from: String, pub reply_to: String, pub smtp: Smtp, @@ -206,7 +205,6 @@ impl From for Database { impl From for Mail { fn from(mail: DomainMail) -> Self { Self { - email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), smtp: mail.smtp.into(), From 4fbeead9e641b81e68bd80d592066ec9411f87b2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 16:50:41 +0100 Subject: [PATCH 253/309] fix: bug, allow empty email is registration when it's optional If the email in the registration form is optional the application should allow not validate it when it's emtpy. It should only validate the email when is not empty. We only make sure that if present is a valid email. --- src/services/user.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/services/user.rs b/src/services/user.rs index ff885dcd..63560642 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -87,12 +87,21 @@ impl RegistrationService { if email.required && registration_form.email.is_none() { return Err(ServiceError::EmailMissing); } - registration_form.email.clone() + match ®istration_form.email { + Some(email) => { + if email.trim() == String::new() { + None + } else { + Some(email.clone()) + } + } + None => None, + } } None => None, }; - if let Some(email) = ®istration_form.email { + if let Some(email) = &opt_email { if !validate_email_address(email) { return Err(ServiceError::EmailInvalid); } From a5e745e5ee3f5294381fe0ad3564dcc191b7af73 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 17:10:20 +0100 Subject: [PATCH 254/309] refactor: move ConfigurationPublic ConfigurationPublic is not a configuration type. It does not belong to the configuration. We don't need to version this type when the configuration changes. It's a type containing a subset of the data contained in the configuration that we want to expose via the API. It happens that those are the fields we want to expose via the API becuase they are the fields we are using in the webapp (Index GUI) but it's not part of the configuration. It's a concrete view of the configration used for other purposes rather than initialize the Index app. In the future, it could be even moved to the API as a API resource. Changing this struct changes the API contract. The contract with the API consumers, not the contract with the Index administrators, the people responsible for setting up the Index and it's configuration. That the reason why it was moved from the config mod to the config service. It's not a problem now, but we should cerate an API resource for this type becuase it should be versioned in the API. WE are using versioning in the API but the type was excluded, meaning it cannot be versioned. --- src/config/mod.rs | 118 +--------------- src/services/settings.rs | 126 +++++++++++++++++- .../api/server/v1/contexts/settings/mod.rs | 2 +- .../web/api/v1/contexts/settings/contract.rs | 2 +- 4 files changed, 128 insertions(+), 120 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 87154ed8..f27cb7a3 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,9 +2,8 @@ pub mod v2; pub mod validator; -use std::str::FromStr; +use std::env; use std::sync::Arc; -use std::{env, fmt}; use camino::Utf8PathBuf; use derive_more::Display; @@ -15,7 +14,6 @@ use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; use tokio::sync::RwLock; use torrust_index_located_error::LocatedError; -use url::Url; use crate::web::api::server::DynError; @@ -301,32 +299,6 @@ impl Configuration { settings_lock.clone() } - pub async fn get_public(&self) -> ConfigurationPublic { - let settings_lock = self.settings.read().await; - - let email_on_signup = match &settings_lock.registration { - Some(registration) => match ®istration.email { - Some(email) => { - if email.required { - EmailOnSignup::Required - } else { - EmailOnSignup::Optional - } - } - None => EmailOnSignup::NotIncluded, - }, - None => EmailOnSignup::NotIncluded, - }; - - ConfigurationPublic { - website_name: settings_lock.website.name.clone(), - tracker_url: settings_lock.tracker.url.clone(), - tracker_listed: settings_lock.tracker.listed, - tracker_private: settings_lock.tracker.private, - email_on_signup, - } - } - pub async fn get_site_name(&self) -> String { let settings_lock = self.settings.read().await; @@ -339,67 +311,12 @@ impl Configuration { } } -/// The public index configuration. -/// There is an endpoint to get this configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ConfigurationPublic { - website_name: String, - tracker_url: Url, - tracker_listed: bool, - tracker_private: bool, - email_on_signup: EmailOnSignup, -} - -/// Whether the email is required on signup or not. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum EmailOnSignup { - /// The email is required on signup. - Required, - /// The email is optional on signup. - Optional, - /// The email is not allowed on signup. It will only be ignored if provided. - NotIncluded, -} - -impl Default for EmailOnSignup { - fn default() -> Self { - Self::Optional - } -} - -impl fmt::Display for EmailOnSignup { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display_str = match self { - EmailOnSignup::Required => "required", - EmailOnSignup::Optional => "optional", - EmailOnSignup::NotIncluded => "ignored", - }; - write!(f, "{display_str}") - } -} - -impl FromStr for EmailOnSignup { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "required" => Ok(EmailOnSignup::Required), - "optional" => Ok(EmailOnSignup::Optional), - "none" => Ok(EmailOnSignup::NotIncluded), - _ => Err(format!( - "Unknown config 'email_on_signup' option (required, optional, none): {s}" - )), - } - } -} - #[cfg(test)] mod tests { use url::Url; - use crate::config::{ApiToken, Configuration, ConfigurationPublic, EmailOnSignup, Info, SecretKey, Settings}; + use crate::config::{ApiToken, Configuration, Info, SecretKey, Settings}; #[cfg(test)] fn default_config_toml() -> String { @@ -484,37 +401,6 @@ mod tests { assert_eq!(toml, default_config_toml()); } - #[tokio::test] - async fn configuration_should_return_only_public_settings() { - let configuration = Configuration::default(); - let all_settings = configuration.get_all().await; - - let email_on_signup = match &all_settings.registration { - Some(registration) => match ®istration.email { - Some(email) => { - if email.required { - EmailOnSignup::Required - } else { - EmailOnSignup::Optional - } - } - None => EmailOnSignup::NotIncluded, - }, - None => EmailOnSignup::NotIncluded, - }; - - assert_eq!( - configuration.get_public().await, - ConfigurationPublic { - website_name: all_settings.website.name, - tracker_url: all_settings.tracker.url, - tracker_listed: all_settings.tracker.listed, - tracker_private: all_settings.tracker.private, - email_on_signup, - } - ); - } - #[tokio::test] async fn configuration_should_return_the_site_name() { let configuration = Configuration::default(); diff --git a/src/services/settings.rs b/src/services/settings.rs index d587fb9b..8efcc89d 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -1,8 +1,13 @@ //! Settings service. +use std::fmt; +use std::str::FromStr; use std::sync::Arc; +use serde::{Deserialize, Serialize}; +use url::Url; + use super::authorization::{self, ACTION}; -use crate::config::{Configuration, ConfigurationPublic, Settings}; +use crate::config::{Configuration, Settings}; use crate::errors::ServiceError; use crate::models::user::UserId; @@ -58,7 +63,8 @@ impl Service { /// /// It returns an error if the user does not have the required permissions. pub async fn get_public(&self) -> ConfigurationPublic { - self.configuration.get_public().await + let settings_lock = self.configuration.get_all().await; + extract_public_settings(&settings_lock) } /// It gets the site name from the settings. @@ -70,3 +76,119 @@ impl Service { self.configuration.get_site_name().await } } + +fn extract_public_settings(settings: &Settings) -> ConfigurationPublic { + let email_on_signup = match &settings.registration { + Some(registration) => match ®istration.email { + Some(email) => { + if email.required { + EmailOnSignup::Required + } else { + EmailOnSignup::Optional + } + } + None => EmailOnSignup::NotIncluded, + }, + None => EmailOnSignup::NotIncluded, + }; + + ConfigurationPublic { + website_name: settings.website.name.clone(), + tracker_url: settings.tracker.url.clone(), + tracker_listed: settings.tracker.listed, + tracker_private: settings.tracker.private, + email_on_signup, + } +} + +/// The public index configuration. +/// There is an endpoint to get this configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ConfigurationPublic { + website_name: String, + tracker_url: Url, + tracker_listed: bool, + tracker_private: bool, + email_on_signup: EmailOnSignup, +} + +/// Whether the email is required on signup or not. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum EmailOnSignup { + /// The email is required on signup. + Required, + /// The email is optional on signup. + Optional, + /// The email is not allowed on signup. It will only be ignored if provided. + NotIncluded, +} + +impl Default for EmailOnSignup { + fn default() -> Self { + Self::Optional + } +} + +impl fmt::Display for EmailOnSignup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + EmailOnSignup::Required => "required", + EmailOnSignup::Optional => "optional", + EmailOnSignup::NotIncluded => "ignored", + }; + write!(f, "{display_str}") + } +} + +impl FromStr for EmailOnSignup { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "required" => Ok(EmailOnSignup::Required), + "optional" => Ok(EmailOnSignup::Optional), + "none" => Ok(EmailOnSignup::NotIncluded), + _ => Err(format!( + "Unknown config 'email_on_signup' option (required, optional, none): {s}" + )), + } + } +} + +#[cfg(test)] +mod tests { + use crate::config::Configuration; + use crate::services::settings::{extract_public_settings, ConfigurationPublic, EmailOnSignup}; + + #[tokio::test] + async fn configuration_should_return_only_public_settings() { + let configuration = Configuration::default(); + let all_settings = configuration.get_all().await; + + let email_on_signup = match &all_settings.registration { + Some(registration) => match ®istration.email { + Some(email) => { + if email.required { + EmailOnSignup::Required + } else { + EmailOnSignup::Optional + } + } + None => EmailOnSignup::NotIncluded, + }, + None => EmailOnSignup::NotIncluded, + }; + + assert_eq!( + extract_public_settings(&all_settings), + ConfigurationPublic { + website_name: all_settings.website.name, + tracker_url: all_settings.tracker.url, + tracker_listed: all_settings.tracker.listed, + tracker_private: all_settings.tracker.private, + email_on_signup, + } + ); + } +} diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index 9d0e80a1..ebbb75de 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -183,7 +183,7 @@ //! //! **Resource** //! -//! Refer to the [`ConfigurationPublic`](crate::config::ConfigurationPublic) +//! Refer to the [`ConfigurationPublic`](crate::services::settings::ConfigurationPublic) //! struct for more information about the response attributes. pub mod handlers; pub mod routes; diff --git a/tests/e2e/web/api/v1/contexts/settings/contract.rs b/tests/e2e/web/api/v1/contexts/settings/contract.rs index 8b3eb08a..02d30a36 100644 --- a/tests/e2e/web/api/v1/contexts/settings/contract.rs +++ b/tests/e2e/web/api/v1/contexts/settings/contract.rs @@ -1,6 +1,6 @@ //! API contract for `settings` context. -use torrust_index::config::EmailOnSignup; +use torrust_index::services::settings::EmailOnSignup; use torrust_index::web::api; use crate::common::asserts::assert_json_ok_response; From 8d660b8fa63fed7740982229e67d23ab8b26c654 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Jul 2024 17:37:24 +0100 Subject: [PATCH 255/309] docs: remove endpoint doc The endpoint was removed. --- .../api/server/v1/contexts/settings/mod.rs | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index ebbb75de..335fa803 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -5,7 +5,6 @@ //! # Endpoints //! //! - [Get all settings](#get-all-settings) -//! - [Update all settings](#update-all-settings) //! - [Get site name](#get-site-name) //! - [Get public settings](#get-public-settings) //! @@ -102,34 +101,6 @@ //! Refer to the [`TorrustIndex`](crate::config::Settings) //! struct for more information about the response attributes. //! -//! # Update all settings -//! -//! **NOTICE**: This endpoint to update the settings does not work when you use -//! environment variables to configure the application. You need to use a -//! configuration file instead. Because settings are persisted in that file. -//! Refer to the issue [#144](https://github.com/torrust/torrust-index/issues/144) -//! for more information. -//! -//! `POST /v1/settings` -//! -//! **Example request** -//! -//! ```bash -//! curl \ -//! --header "Content-Type: application/json" \ -//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ -//! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ -//! "http://127.0.0.1:3001/v1/settings" -//! ``` -//! -//! The response contains the settings that were updated. -//! -//! **Resource** -//! -//! Refer to the [`TorrustIndex`](crate::config::Settings) -//! struct for more information about the response attributes. -//! //! # Get site name //! //! `GET /v1/settings/name` From bcc3f8112b7ce27f5ea184afeb20f91ffd1bedf2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Jul 2024 15:25:19 +0100 Subject: [PATCH 256/309] chore(deps): update dependencies ```output cargo update Updating crates.io index Locking 34 packages to latest compatible versions Updating async-trait v0.1.80 -> v0.1.81 Updating cc v1.0.104 -> v1.1.0 Updating clap v4.5.8 -> v4.5.9 Updating clap_builder v4.5.8 -> v4.5.9 Updating darling v0.20.9 -> v0.20.10 Updating darling_core v0.20.9 -> v0.20.10 Updating darling_macro v0.20.9 -> v0.20.10 Updating email_address v0.2.4 -> v0.2.5 Updating hyper v1.4.0 -> v1.4.1 Updating rgb v0.8.40 -> v0.8.44 Updating rhai v1.18.0 -> v1.19.0 Updating rhai_codegen v2.1.0 -> v2.2.0 Updating rustls v0.23.10 -> v0.23.11 Updating rustls-webpki v0.102.4 -> v0.102.5 Updating serde v1.0.203 -> v1.0.204 Updating serde_derive v1.0.203 -> v1.0.204 Updating serde_with v3.8.2 -> v3.8.3 Updating serde_with_macros v3.8.2 -> v3.8.3 Updating syn v2.0.68 -> v2.0.70 Updating tinyvec v1.6.1 -> v1.8.0 Updating toml_edit v0.22.14 -> v0.22.15 Updating uuid v1.9.1 -> v1.10.0 Updating windows-targets v0.52.5 -> v0.52.6 Updating windows_aarch64_gnullvm v0.52.5 -> v0.52.6 Updating windows_aarch64_msvc v0.52.5 -> v0.52.6 Updating windows_i686_gnu v0.52.5 -> v0.52.6 Updating windows_i686_gnullvm v0.52.5 -> v0.52.6 Updating windows_i686_msvc v0.52.5 -> v0.52.6 Updating windows_x86_64_gnu v0.52.5 -> v0.52.6 Updating windows_x86_64_gnullvm v0.52.5 -> v0.52.6 Updating windows_x86_64_msvc v0.52.5 -> v0.52.6 Updating zstd v0.13.1 -> v0.13.2 Updating zstd-safe v7.1.0 -> v7.2.0 Updating zstd-sys v2.0.11+zstd.1.5.6 -> v2.0.12+zstd.1.5.6 ``` --- Cargo.lock | 210 ++++++++++++++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bad27c60..a89c8b55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,13 +196,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -478,9 +478,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" dependencies = [ "jobserver", "libc", @@ -503,7 +503,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -518,9 +518,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -547,7 +547,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -705,9 +705,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -715,27 +715,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -789,7 +789,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -837,9 +837,9 @@ dependencies = [ [[package]] name = "email_address" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" +checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe" dependencies = [ "serde", ] @@ -1073,7 +1073,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -1325,9 +1325,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1354,7 +1354,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1586,7 +1586,7 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pemfile", "serde", "serde_json", @@ -1748,7 +1748,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -1916,7 +1916,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -1963,7 +1963,7 @@ dependencies = [ "libc", "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2015,7 +2015,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -2074,7 +2074,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -2121,7 +2121,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -2231,7 +2231,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", "version_check", "yansi", ] @@ -2404,18 +2404,18 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.40" +version = "0.8.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" +checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" dependencies = [ "bytemuck", ] [[package]] name = "rhai" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7d88770120601ba1e548bb6bc2a05019e54ff01b51479e38e64ec3b59d4759" +checksum = "61797318be89b1a268a018a92a7657096d83f3ecb31418b9e9c16dcbb043b702" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -2431,13 +2431,13 @@ dependencies = [ [[package]] name = "rhai_codegen" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -2536,15 +2536,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.5", "subtle", "zeroize", ] @@ -2577,9 +2577,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", @@ -2688,9 +2688,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -2716,13 +2716,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -2769,9 +2769,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.2" +version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079f3a42cd87588d924ed95b533f8d30a483388c4e400ab736a7058e34f16169" +checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" dependencies = [ "base64 0.22.1", "chrono", @@ -2787,14 +2787,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.2" +version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03aad67c1d26b7de277d51c86892e7d9a0110a2fe44bf6b26cc569fba302d6" +checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -3223,9 +3223,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -3352,7 +3352,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -3421,9 +3421,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -3460,7 +3460,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -3489,7 +3489,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pki-types", "tokio", ] @@ -3541,9 +3541,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ "indexmap 2.2.6", "serde", @@ -3708,7 +3708,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -3979,9 +3979,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", ] @@ -4056,7 +4056,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", "wasm-bindgen-shared", ] @@ -4090,7 +4090,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4180,7 +4180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -4189,7 +4189,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -4207,7 +4207,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -4227,18 +4227,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4249,9 +4249,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4261,9 +4261,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4273,15 +4273,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4291,9 +4291,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4303,9 +4303,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4315,9 +4315,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4327,9 +4327,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -4397,7 +4397,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.70", ] [[package]] @@ -4408,27 +4408,27 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zstd" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", From 35528b4c0b711b104f2b3757ceadbd875e904fe1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 11 Jul 2024 15:44:20 +0100 Subject: [PATCH 257/309] feat: [#674] rename email config option From: ```toml [registration] [registration.email] required = true verified = true ``` To: ```toml [registration] [registration.email] required = true verification_required = true ``` --- src/config/v2/registration.rs | 4 ++-- src/services/authentication.rs | 2 +- src/services/user.rs | 2 +- tests/common/contexts/settings/mod.rs | 4 ++-- tests/environments/isolated.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config/v2/registration.rs b/src/config/v2/registration.rs index 5052fb75..fa1fac07 100644 --- a/src/config/v2/registration.rs +++ b/src/config/v2/registration.rs @@ -31,14 +31,14 @@ pub struct Email { /// Whether or not email is verified. #[serde(default = "Email::default_verified")] - pub verified: bool, + pub verification_required: bool, } impl Default for Email { fn default() -> Self { Self { required: Self::default_required(), - verified: Self::default_verified(), + verification_required: Self::default_verified(), } } } diff --git a/src/services/authentication.rs b/src/services/authentication.rs index f347a857..3c4ecbd1 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -72,7 +72,7 @@ impl Service { // Fail login if email verification is required and this email is not verified if let Some(registration) = &settings.registration { if let Some(email) = ®istration.email { - if email.verified && !user_profile.email_verified { + if email.verification_required && !user_profile.email_verified { return Err(ServiceError::EmailNotVerified); } } diff --git a/src/services/user.rs b/src/services/user.rs index 63560642..cbb83ca8 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -136,7 +136,7 @@ impl RegistrationService { match ®istration.email { Some(email) => { - if email.verified { + if email.verification_required { // Email verification is enabled if let Some(email) = opt_email { let mail_res = self diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index baa17610..636599bf 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -113,7 +113,7 @@ pub struct Registration { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Email { pub required: bool, - pub verified: bool, + pub verification_required: bool, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -264,7 +264,7 @@ impl From for Email { fn from(email: DomainEmail) -> Self { Self { required: email.required, - verified: email.verified, + verification_required: email.verification_required, } } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 12c04803..fb25f0cf 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -92,7 +92,7 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { configuration.registration = Some(Registration { email: Some(Email { required: false, - verified: false, + verification_required: false, }), }); From 28ac404404d8b4e389b4dcfad508237f9a99a6e9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 29 Jul 2024 10:03:25 +0100 Subject: [PATCH 258/309] chore(deps): update depedencies ```output Updating crates.io index Locking 59 packages to latest compatible versions Updating anstream v0.6.14 -> v0.6.15 Updating anstyle v1.0.7 -> v1.0.8 Updating anstyle-parse v0.2.4 -> v0.2.5 Updating anstyle-query v1.1.0 -> v1.1.1 Updating anstyle-wincon v3.0.3 -> v3.0.4 Updating arrayref v0.3.7 -> v0.3.8 Updating async-compression v0.4.11 -> v0.4.12 Updating bstr v1.9.1 -> v1.10.0 Updating bytes v1.6.0 -> v1.6.1 Updating cc v1.1.0 -> v1.1.6 Updating clap v4.5.9 -> v4.5.11 Updating clap_builder v4.5.9 -> v4.5.11 Updating clap_derive v4.5.8 -> v4.5.11 Updating clap_lex v0.7.1 -> v0.7.2 Updating colorchoice v1.0.1 -> v1.0.2 Adding concurrent-queue v2.5.0 Updating email_address v0.2.5 -> v0.2.7 Updating event-listener v2.5.3 -> v5.3.1 Updating hashlink v0.8.4 -> v0.9.1 Removing heck v0.4.1 Updating http-body v1.0.0 -> v1.0.1 Updating is_terminal_polyfill v1.70.0 -> v1.70.1 Updating jobserver v0.1.31 -> v0.1.32 Updating libsqlite3-sys v0.27.0 -> v0.28.0 (latest: v0.30.1) Updating mio v0.8.11 -> v1.0.1 Removing num_cpus v1.16.0 Updating object v0.36.1 -> v0.36.2 Updating openssl v0.10.64 -> v0.10.66 Updating openssl-sys v0.9.102 -> v0.9.103 Adding parking v2.2.0 Updating predicates v3.1.0 -> v3.1.2 Updating predicates-core v1.0.6 -> v1.0.8 Updating predicates-tree v1.0.9 -> v1.0.11 Updating quoted_printable v0.5.0 -> v0.5.1 Updating redox_syscall v0.5.2 -> v0.5.3 Updating rgb v0.8.44 -> v0.8.45 Updating rustls v0.23.11 -> v0.23.12 Updating rustls-webpki v0.102.5 -> v0.102.6 Updating security-framework v2.11.0 -> v2.11.1 Updating security-framework-sys v2.11.0 -> v2.11.1 Updating serde_json v1.0.120 -> v1.0.121 Updating serde_spanned v0.6.6 -> v0.6.7 Updating serde_with v3.8.3 -> v3.9.0 Updating serde_with_macros v3.8.3 -> v3.9.0 Updating sqlx v0.7.4 -> v0.8.0 Updating sqlx-core v0.7.4 -> v0.8.0 Updating sqlx-macros v0.7.4 -> v0.8.0 Updating sqlx-macros-core v0.7.4 -> v0.8.0 Updating sqlx-mysql v0.7.4 -> v0.8.0 Updating sqlx-postgres v0.7.4 -> v0.8.0 Updating sqlx-sqlite v0.7.4 -> v0.8.0 Updating syn v2.0.70 -> v2.0.72 Updating thiserror v1.0.61 -> v1.0.63 Updating thiserror-impl v1.0.61 -> v1.0.63 Updating tokio v1.38.0 -> v1.39.2 Updating tokio-macros v2.3.0 -> v2.4.0 Updating toml v0.8.14 -> v0.8.16 Updating toml_datetime v0.6.6 -> v0.6.7 Updating toml_edit v0.22.15 -> v0.22.17 Removing unicode-segmentation v1.11.0 Updating version_check v0.9.4 -> v0.9.5 Updating winnow v0.6.13 -> v0.6.16 ``` --- Cargo.lock | 349 ++++++++++++++++++++++++++--------------------------- 1 file changed, 172 insertions(+), 177 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a89c8b55..fe9cccfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -104,33 +104,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -180,9 +180,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ "brotli", "flate2", @@ -202,7 +202,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -443,9 +443,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "camino" @@ -478,13 +478,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.0" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -518,9 +517,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", "clap_derive", @@ -528,9 +527,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -540,27 +539,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -572,6 +571,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -724,7 +732,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -735,7 +743,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -789,7 +797,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -837,9 +845,9 @@ dependencies = [ [[package]] name = "email_address" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe" +checksum = "46b7a0ac6570e31bfe2c6cf575a576a55af9893d1a6b30b4444e6e90b216bb84" dependencies = [ "serde", ] @@ -882,9 +890,14 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" @@ -1073,7 +1086,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1205,22 +1218,13 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1290,9 +1294,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1354,7 +1358,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1497,9 +1501,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -1509,9 +1513,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1586,7 +1590,7 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pemfile", "serde", "serde_json", @@ -1613,9 +1617,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "cc", "pkg-config", @@ -1715,13 +1719,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1748,7 +1753,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1868,21 +1873,11 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -1895,9 +1890,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1916,7 +1911,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1927,9 +1922,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -1943,6 +1938,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1961,7 +1962,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -2015,7 +2016,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2074,7 +2075,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2121,7 +2122,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2190,9 +2191,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "predicates-core", @@ -2200,15 +2201,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -2231,7 +2232,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", "version_check", "yansi", ] @@ -2256,9 +2257,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "rand" @@ -2307,9 +2308,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -2404,9 +2405,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.44" +version = "0.8.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" +checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" dependencies = [ "bytemuck", ] @@ -2437,7 +2438,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2536,15 +2537,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] @@ -2577,9 +2578,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -2659,9 +2660,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -2672,9 +2673,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -2722,16 +2723,17 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2748,9 +2750,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -2769,9 +2771,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", @@ -2787,14 +2789,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2962,9 +2964,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2975,11 +2977,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b" dependencies = [ - "ahash 0.8.11", "atoi", "byteorder", "bytes", @@ -2992,6 +2993,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap 2.2.6", @@ -3016,26 +3018,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.72", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -3047,7 +3049,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.72", "tempfile", "tokio", "url", @@ -3055,12 +3057,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "bytes", @@ -3098,12 +3100,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "crc", @@ -3137,9 +3139,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee" dependencies = [ "atoi", "flume", @@ -3152,11 +3154,11 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "time", "tracing", "url", - "urlencoding", ] [[package]] @@ -3223,9 +3225,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.70" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -3337,22 +3339,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3436,31 +3438,30 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3489,7 +3490,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -3520,9 +3521,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", @@ -3532,18 +3533,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.15" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap 2.2.6", "serde", @@ -3708,7 +3709,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3902,12 +3903,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - [[package]] name = "unicode-vo" version = "0.1.0" @@ -4000,9 +3995,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -4056,7 +4051,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -4090,7 +4085,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4333,9 +4328,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" dependencies = [ "memchr", ] @@ -4397,7 +4392,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] From 72f4c69847b1c8ec7ffe6d6d4b61ce72c978cbb6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 29 Jul 2024 10:41:11 +0100 Subject: [PATCH 259/309] fix: [#580] E2E tests failing with MySQL --- contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh | 2 +- tests/environments/shared.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh index 8ef3b43f..046862d5 100755 --- a/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh +++ b/contrib/dev-tools/container/e2e/mysql/run-e2e-tests.sh @@ -41,7 +41,7 @@ docker ps # Run E2E tests with shared app instance TORRUST_INDEX_E2E_SHARED=true \ TORRUST_INDEX_CONFIG_TOML_PATH="./share/default/config/index.public.e2e.container.mysql.toml" \ - TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@localhost:3306/torrust_index_e2e_testing" \ + TORRUST_INDEX_E2E_DB_CONNECT_URL="mysql://root:root_secret_password@127.0.0.1:3306/torrust_index_e2e_testing" \ cargo test || { ./contrib/dev-tools/container/e2e/mysql/e2e-env-down.sh diff --git a/tests/environments/shared.rs b/tests/environments/shared.rs index 30ad1193..936cf375 100644 --- a/tests/environments/shared.rs +++ b/tests/environments/shared.rs @@ -36,7 +36,7 @@ impl TestEnv { impl Default for TestEnv { fn default() -> Self { Self { - authority: "localhost:3001".to_string(), + authority: "127.0.0.1:3001".to_string(), } } } From da6606fdadab3750ec116a9444578c031ca543af Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 29 Jul 2024 11:44:47 +0100 Subject: [PATCH 260/309] chore: [#685] fix Containerfile linter warning --- Containerfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Containerfile b/Containerfile index 2c3d9b4f..b17dd7ea 100644 --- a/Containerfile +++ b/Containerfile @@ -3,13 +3,13 @@ # Torrust Index ## Builder Image -FROM rust:bookworm as chef +FROM rust:bookworm AS chef WORKDIR /tmp RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash RUN cargo binstall --no-confirm cargo-chef cargo-nextest ## Tester Image -FROM rust:slim-bookworm as tester +FROM rust:slim-bookworm AS tester WORKDIR /tmp RUN apt-get update; apt-get install -y curl sqlite3; apt-get autoclean @@ -21,7 +21,7 @@ RUN mkdir -p /app/share/torrust/default/database/; \ sqlite3 /app/share/torrust/default/database/index.sqlite3.db "VACUUM;" ## Su Exe Compile -FROM docker.io/library/gcc:bookworm as gcc +FROM docker.io/library/gcc:bookworm AS gcc COPY ./contrib/dev-tools/su-exec/ /usr/local/src/su-exec/ RUN cc -Wall -Werror -g /usr/local/src/su-exec/su-exec.c -o /usr/local/bin/su-exec; chmod +x /usr/local/bin/su-exec @@ -62,7 +62,7 @@ RUN cargo nextest archive --tests --benches --examples --workspace --all-targets # Extract and Test (debug) -FROM tester as test_debug +FROM tester AS test_debug WORKDIR /test COPY . /test/src/ COPY --from=build_debug \ @@ -76,7 +76,7 @@ RUN mkdir -p /app/bin/; cp -l /test/src/target/debug/torrust-index /app/bin/torr RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; chmod -R a+x /app/bin # Extract and Test (release) -FROM tester as test +FROM tester AS test WORKDIR /test COPY . /test/src COPY --from=build \ @@ -93,7 +93,7 @@ RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; chmod -R a+x /app/bin ## Runtime -FROM gcr.io/distroless/cc-debian12:debug as runtime +FROM gcr.io/distroless/cc-debian12:debug AS runtime RUN ["/busybox/cp", "-sp", "/busybox/sh","/busybox/cat","/busybox/ls","/busybox/env", "/bin/"] COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec @@ -124,14 +124,14 @@ ENTRYPOINT ["/usr/local/bin/entry.sh"] ## Torrust-Index (debug) -FROM runtime as debug +FROM runtime AS debug ENV RUNTIME="debug" COPY --from=test_debug /app/ /usr/ RUN env CMD ["sh"] ## Torrust-Index (release) (default) -FROM runtime as release +FROM runtime AS release ENV RUNTIME="release" COPY --from=test /app/ /usr/ HEALTHCHECK --interval=5s --timeout=5s --start-period=3s --retries=3 \ From 6ca8a63decc721c85c12a653656d88423ad24ae7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Aug 2024 16:43:29 +0100 Subject: [PATCH 261/309] fix: [#696] update tracker configuration Some config options are now mandatory. --- .../config/tracker.private.e2e.container.sqlite3.toml | 7 ++++++- .../config/tracker.public.e2e.container.sqlite3.toml | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 9672f270..028f1aa6 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -1,6 +1,11 @@ -version = "2" +[metadata] +schema_version = "2.0.0" + +[logging] +threshold = "info" [core] +listed = false private = true [core.database] diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index b5d8de7b..1b93d36a 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -1,7 +1,12 @@ -version = "2" +[metadata] +schema_version = "2.0.0" + +[logging] +threshold = "info" [core] -private = true +listed = false +private = false [core.database] driver = "sqlite3" From 5bd611b5d2296260bfb0380f356298a4240873a8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Aug 2024 17:36:03 +0100 Subject: [PATCH 262/309] feat: [#672] improve metadata section in configuration ```toml [metadata] app = "torrust-index" purpose = "configuration" schema_version = "2.0.0" ``` --- .../default/config/index.container.mysql.toml | 5 +- .../config/index.container.sqlite3.toml | 5 +- .../config/index.development.sqlite3.toml | 5 +- .../index.private.e2e.container.sqlite3.toml | 5 +- .../index.public.e2e.container.mysql.toml | 5 +- .../index.public.e2e.container.sqlite3.toml | 5 +- src/config/mod.rs | 62 +++++++++++++++---- src/config/v2/mod.rs | 1 - .../api/server/v1/contexts/settings/mod.rs | 6 +- 9 files changed, 78 insertions(+), 21 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 4a352a54..1c865ab8 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index cd73ce0a..340c29fa 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index ecd66d98..6998613e 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index 6226bc63..fdf43695 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 5ef2c3d8..2d429d70 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 8547d565..4e364fb8 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,4 +1,7 @@ -version = "2" +[metadata] +app = "torrust-index" +purpose = "configuration" +schema_version = "2.0.0" [logging] #threshold = "off" diff --git a/src/config/mod.rs b/src/config/mod.rs index f27cb7a3..0000c450 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -49,7 +49,7 @@ pub type Threshold = v2::logging::Threshold; pub type Website = v2::website::Website; /// Configuration version -const VERSION_2: &str = "2"; +const VERSION_2: &str = "2.0.0"; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -64,41 +64,74 @@ pub const ENV_VAR_CONFIG_TOML: &str = "TORRUST_INDEX_CONFIG_TOML"; /// The `index.toml` file location. pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_INDEX_CONFIG_TOML_PATH"; -pub const LATEST_VERSION: &str = "2"; +pub const LATEST_VERSION: &str = "2.0.0"; /// Info about the configuration specification. #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +#[display(fmt = "Metadata(app: {app}, purpose: {purpose}, schema_version: {schema_version})")] pub struct Metadata { - #[serde(default = "Metadata::default_version")] + /// The application this configuration is valid for. + #[serde(default = "Metadata::default_app")] + app: App, + + /// The purpose of this parsed file. + #[serde(default = "Metadata::default_purpose")] + purpose: Purpose, + + /// The schema version for the configuration. + #[serde(default = "Metadata::default_schema_version")] #[serde(flatten)] - version: Version, + schema_version: Version, } impl Default for Metadata { fn default() -> Self { Self { - version: Self::default_version(), + app: Self::default_app(), + purpose: Self::default_purpose(), + schema_version: Self::default_schema_version(), } } } impl Metadata { - fn default_version() -> Version { + fn default_app() -> App { + App::TorrustIndex + } + + fn default_purpose() -> Purpose { + Purpose::Configuration + } + + fn default_schema_version() -> Version { Version::latest() } } +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum App { + TorrustIndex, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +#[serde(rename_all = "lowercase")] +pub enum Purpose { + Configuration, +} + /// The configuration version. #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)] +#[serde(rename_all = "lowercase")] pub struct Version { #[serde(default = "Version::default_semver")] - version: String, + schema_version: String, } impl Default for Version { fn default() -> Self { Self { - version: Self::default_semver(), + schema_version: Self::default_semver(), } } } @@ -106,13 +139,13 @@ impl Default for Version { impl Version { fn new(semver: &str) -> Self { Self { - version: semver.to_owned(), + schema_version: semver.to_owned(), } } fn latest() -> Self { Self { - version: LATEST_VERSION.to_string(), + schema_version: LATEST_VERSION.to_string(), } } @@ -284,9 +317,9 @@ impl Configuration { let settings: Settings = figment.extract()?; - if settings.metadata.version != Version::new(VERSION_2) { + if settings.metadata.schema_version != Version::new(VERSION_2) { return Err(Error::UnsupportedVersion { - version: settings.metadata.version, + version: settings.metadata.schema_version, }); } @@ -320,7 +353,10 @@ mod tests { #[cfg(test)] fn default_config_toml() -> String { - let config = r#"version = "2" + let config = r#"[metadata] + app = "torrust-index" + purpose = "configuration" + schema_version = "2.0.0" [logging] threshold = "info" diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index 53af3dc8..faf99edd 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -31,7 +31,6 @@ use super::Metadata; pub struct Settings { /// Configuration metadata. #[serde(default = "Settings::default_metadata")] - #[serde(flatten)] pub metadata: Metadata, /// The logging configuration. diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index 335fa803..a542bbf0 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -29,7 +29,11 @@ //! ```json //! { //! "data": { -//! "version": "2", +//! "metadata": { +//! "app": "torrust-index", +//! "purpose": "configuration", +//! "schema_version": "2.0.0" +//! }, //! "logging": { //! "threshold": "info" //! }, From c9654e0b41cec1f4727da92bf6b1e35e6b724c7c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Aug 2024 18:06:08 +0100 Subject: [PATCH 263/309] feat: [#653] rename config option secret_key to `user_claim_token_pepper`. It's used to sign the Json Web Token. --- src/config/mod.rs | 16 +++++++++---- src/config/v2/auth.rs | 24 +++++++++---------- src/config/v2/mod.rs | 4 ++-- src/config/v2/tracker.rs | 2 +- src/lib.rs | 2 +- src/mailer.rs | 2 +- src/services/authentication.rs | 4 ++-- src/services/user.rs | 2 +- .../api/client/v1/contexts/settings/mod.rs | 4 ++-- .../api/server/v1/contexts/settings/mod.rs | 2 +- src/web/api/server/v1/contexts/user/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 4 ++-- tests/e2e/environment.rs | 2 +- 13 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 0000c450..15b05262 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -25,7 +25,7 @@ pub type Registration = v2::registration::Registration; pub type Email = v2::registration::Email; pub type Auth = v2::auth::Auth; -pub type SecretKey = v2::auth::SecretKey; +pub type SecretKey = v2::auth::ClaimTokenPepper; pub type PasswordConstraints = v2::auth::PasswordConstraints; pub type Database = v2::database::Database; @@ -376,7 +376,7 @@ mod tests { bind_address = "0.0.0.0:3001" [auth] - secret_key = "MaxVerstappenWC2021" + user_claim_token_pepper = "MaxVerstappenWC2021" [auth.password_constraints] max_password_length = 64 @@ -496,12 +496,15 @@ mod tests { } #[tokio::test] - async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() { + async fn configuration_should_allow_to_override_the_authentication_user_claim_token_pepper_provided_in_the_toml_file() { figment::Jail::expect_with(|jail| { jail.create_dir("templates")?; jail.create_file("templates/verify.html", "EMAIL TEMPLATE")?; - jail.set_env("TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY", "OVERRIDDEN AUTH SECRET KEY"); + jail.set_env( + "TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__USER_CLAIM_TOKEN_PEPPER", + "OVERRIDDEN AUTH SECRET KEY", + ); let info = Info { config_toml: Some(default_config_toml()), @@ -510,7 +513,10 @@ mod tests { let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); - assert_eq!(settings.auth.secret_key, SecretKey::new("OVERRIDDEN AUTH SECRET KEY")); + assert_eq!( + settings.auth.user_claim_token_pepper, + SecretKey::new("OVERRIDDEN AUTH SECRET KEY") + ); Ok(()) }); diff --git a/src/config/v2/auth.rs b/src/config/v2/auth.rs index 092b7e5b..ec123582 100644 --- a/src/config/v2/auth.rs +++ b/src/config/v2/auth.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// The secret key used to sign JWT tokens. - #[serde(default = "Auth::default_secret_key")] - pub secret_key: SecretKey, + #[serde(default = "Auth::default_user_claim_token_pepper")] + pub user_claim_token_pepper: ClaimTokenPepper, /// The password constraints #[serde(default = "Auth::default_password_constraints")] @@ -18,18 +18,18 @@ impl Default for Auth { fn default() -> Self { Self { password_constraints: Self::default_password_constraints(), - secret_key: Self::default_secret_key(), + user_claim_token_pepper: Self::default_user_claim_token_pepper(), } } } impl Auth { - pub fn override_secret_key(&mut self, secret_key: &str) { - self.secret_key = SecretKey::new(secret_key); + pub fn override_user_claim_token_pepper(&mut self, user_claim_token_pepper: &str) { + self.user_claim_token_pepper = ClaimTokenPepper::new(user_claim_token_pepper); } - fn default_secret_key() -> SecretKey { - SecretKey::new("MaxVerstappenWC2021") + fn default_user_claim_token_pepper() -> ClaimTokenPepper { + ClaimTokenPepper::new("MaxVerstappenWC2021") } fn default_password_constraints() -> PasswordConstraints { @@ -38,9 +38,9 @@ impl Auth { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SecretKey(String); +pub struct ClaimTokenPepper(String); -impl SecretKey { +impl ClaimTokenPepper { /// # Panics /// /// Will panic if the key if empty. @@ -57,7 +57,7 @@ impl SecretKey { } } -impl fmt::Display for SecretKey { +impl fmt::Display for ClaimTokenPepper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } @@ -94,11 +94,11 @@ impl PasswordConstraints { #[cfg(test)] mod tests { - use super::SecretKey; + use super::ClaimTokenPepper; #[test] #[should_panic(expected = "secret key cannot be empty")] fn secret_key_can_not_be_empty() { - drop(SecretKey::new("")); + drop(ClaimTokenPepper::new("")); } } diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index faf99edd..e5519f56 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -15,7 +15,7 @@ use registration::Registration; use serde::{Deserialize, Serialize}; use self::api::Api; -use self::auth::{Auth, SecretKey}; +use self::auth::{Auth, ClaimTokenPepper}; use self::database::Database; use self::image_cache::ImageCache; use self::mail::Mail; @@ -104,7 +104,7 @@ impl Settings { let _ = self.database.connect_url.set_password(Some("***")); } "***".clone_into(&mut self.mail.smtp.credentials.password); - self.auth.secret_key = SecretKey::new("***"); + self.auth.user_claim_token_pepper = ClaimTokenPepper::new("***"); } /// Encodes the configuration to TOML. diff --git a/src/config/v2/tracker.rs b/src/config/v2/tracker.rs index 3702ea5b..a2bc8703 100644 --- a/src/config/v2/tracker.rs +++ b/src/config/v2/tracker.rs @@ -118,7 +118,7 @@ mod tests { #[test] #[should_panic(expected = "tracker API token cannot be empty")] - fn secret_key_can_not_be_empty() { + fn apai_token_can_not_be_empty() { drop(ApiToken::new("")); } } diff --git a/src/lib.rs b/src/lib.rs index 6b9e3d54..057a4bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,7 +181,7 @@ //! bind_address = "0.0.0.0:3001" //! //! [auth] -//! secret_key = "MaxVerstappenWC2021" +//! user_claim_token_pepper = "MaxVerstappenWC2021" //! //! [auth.password_constraints] //! min_password_length = 6 diff --git a/src/mailer.rs b/src/mailer.rs index 0eb5f35b..5cfc06ac 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -133,7 +133,7 @@ impl Service { let settings = self.cfg.settings.read().await; // create verification JWT - let key = settings.auth.secret_key.as_bytes(); + let key = settings.auth.user_claim_token_pepper.as_bytes(); // Create non expiring token that is only valid for email-verification let claims = VerifyClaims { diff --git a/src/services/authentication.rs b/src/services/authentication.rs index 3c4ecbd1..58a9023b 100644 --- a/src/services/authentication.rs +++ b/src/services/authentication.rs @@ -134,7 +134,7 @@ impl JsonWebToken { let settings = self.cfg.settings.read().await; // Create JWT that expires in two weeks - let key = settings.auth.secret_key.as_bytes(); + let key = settings.auth.user_claim_token_pepper.as_bytes(); // todo: create config option for setting the token validity in seconds. let exp_date = clock::now() + 1_209_600; // two weeks from now @@ -154,7 +154,7 @@ impl JsonWebToken { match decode::( token, - &DecodingKey::from_secret(settings.auth.secret_key.as_bytes()), + &DecodingKey::from_secret(settings.auth.user_claim_token_pepper.as_bytes()), &Validation::new(Algorithm::HS256), ) { Ok(token_data) => { diff --git a/src/services/user.rs b/src/services/user.rs index cbb83ca8..503d8b38 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -172,7 +172,7 @@ impl RegistrationService { let token_data = match decode::( token, - &DecodingKey::from_secret(settings.auth.secret_key.as_bytes()), + &DecodingKey::from_secret(settings.auth.user_claim_token_pepper.as_bytes()), &Validation::new(Algorithm::HS256), ) { Ok(token_data) => { diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index fdc52518..55260a7c 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -49,7 +49,7 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { - pub secret_key: String, + pub user_claim_token_pepper: String, pub password_constraints: PasswordConstraints, } @@ -152,7 +152,7 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - secret_key: auth.secret_key.to_string(), + user_claim_token_pepper: auth.user_claim_token_pepper.to_string(), password_constraints: auth.password_constraints.into(), } } diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index a542bbf0..95ee15c8 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -54,7 +54,7 @@ //! "tsl": null //! }, //! "auth": { -//! "secret_key": "***", +//! "user_claim_token_pepper": "***", //! "password_constraints": { //! "max_password_length": 64, //! "min_password_length": 6 diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index 5d9be86b..1c146121 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,7 +45,7 @@ //! //! ```toml //! [auth] -//! secret_key = "MaxVerstappenWC2021" +//! user_claim_token_pepper = "MaxVerstappenWC2021" //! ``` //! //! Refer to the [`RegistrationForm`](crate::web::api::server::v1::contexts::user::forms::RegistrationForm) diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 636599bf..3519d40a 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -55,7 +55,7 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { - pub secret_key: String, + pub user_claim_token_pepper: String, pub password_constraints: PasswordConstraints, } @@ -179,7 +179,7 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - secret_key: auth.secret_key.to_string(), + user_claim_token_pepper: auth.user_claim_token_pepper.to_string(), password_constraints: auth.password_constraints.into(), } } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 9aaad342..fd72458e 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -114,7 +114,7 @@ impl TestEnv { "***".clone_into(&mut settings.mail.smtp.credentials.password); - "***".clone_into(&mut settings.auth.secret_key); + "***".clone_into(&mut settings.auth.user_claim_token_pepper); Some(settings) } From 36970402dc645a29ff48f6f2a0674501a1dbb6fd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Aug 2024 18:39:37 +0100 Subject: [PATCH 264/309] feat!: [#673] make some config options mandatory These options are now mandatory: ```toml [metadata] schema_version = "2.0.0" [logging] threshold = "info" ``` --- src/config/mod.rs | 97 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 15b05262..5dfef8f0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -222,6 +222,9 @@ pub enum Error { #[error("Unsupported configuration version: {version}")] UnsupportedVersion { version: Version }, + + #[error("Missing mandatory configuration option. Option path: {path}")] + MissingMandatoryOption { path: String }, } impl From for Error { @@ -295,26 +298,28 @@ impl Configuration { /// Loads the settings from the `Info` struct. The whole /// configuration in toml format is included in the `info.index_toml` string. /// - /// Optionally will override the: - /// - /// - Tracker api token. - /// - The auth secret key. + /// Configuration provided via env var has priority over config file path. /// /// # Errors /// /// Will return `Err` if the environment variable does not exist or has a bad configuration. pub fn load_settings(info: &Info) -> Result { + // Load configuration provided by the user, prioritizing env vars let figment = if let Some(config_toml) = &info.config_toml { // Config in env var has priority over config file path - Figment::from(Serialized::defaults(Settings::default())) - .merge(Toml::string(config_toml)) - .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) + Figment::from(Toml::string(config_toml)).merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) } else { - Figment::from(Serialized::defaults(Settings::default())) - .merge(Toml::file(&info.config_toml_path)) + Figment::from(Toml::file(&info.config_toml_path)) .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) }; + // Make sure user has provided the mandatory options. + Self::check_mandatory_options(&figment)?; + + // Fill missing options with default values. + let figment = figment.join(Serialized::defaults(Settings::default())); + + // Build final configuration. let settings: Settings = figment.extract()?; if settings.metadata.schema_version != Version::new(VERSION_2) { @@ -326,6 +331,28 @@ impl Configuration { Ok(settings) } + /// Some configuration options are mandatory. The tracker will panic if + /// the user doesn't provide an explicit value for them from one of the + /// configuration sources: TOML or ENV VARS. + /// + /// # Errors + /// + /// Will return an error if a mandatory configuration option is only + /// obtained by default value (code), meaning the user hasn't overridden it. + fn check_mandatory_options(figment: &Figment) -> Result<(), Error> { + let mandatory_options = ["metadata.schema_version", "logging.threshold"]; + + for mandatory_option in mandatory_options { + figment + .find_value(mandatory_option) + .map_err(|_err| Error::MissingMandatoryOption { + path: mandatory_option.to_owned(), + })?; + } + + Ok(()) + } + pub async fn get_all(&self) -> Settings { let settings_lock = self.settings.read().await; @@ -474,6 +501,58 @@ mod tests { }); } + #[test] + fn configuration_should_use_the_default_values_when_only_the_mandatory_options_are_provided_by_the_user_via_toml_file() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "index.toml", + r#" + [metadata] + schema_version = "2.0.0" + + [logging] + threshold = "info" + "#, + )?; + + let info = Info { + config_toml: None, + config_toml_path: "index.toml".to_string(), + }; + + let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); + + assert_eq!(settings, Settings::default()); + + Ok(()) + }); + } + + #[test] + fn configuration_should_use_the_default_values_when_only_the_mandatory_options_are_provided_by_the_user_via_toml_content() { + figment::Jail::expect_with(|_jail| { + let config_toml = r#" + [metadata] + schema_version = "2.0.0" + + [logging] + threshold = "info" + "# + .to_string(); + + let info = Info { + config_toml: Some(config_toml), + config_toml_path: String::new(), + }; + + let settings = Configuration::load_settings(&info).expect("Could not load configuration from file"); + + assert_eq!(settings, Settings::default()); + + Ok(()) + }); + } + #[tokio::test] async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() { figment::Jail::expect_with(|jail| { From a757c5c27dc96c53af9da8c3ca523e78f0857217 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 3 Jul 2024 22:32:31 +0200 Subject: [PATCH 265/309] refactor: [#615] new about service and minor refactor to existing code --- src/app.rs | 5 +- src/common.rs | 5 +- src/services/about.rs | 80 ++++++++++--------- .../api/server/v1/contexts/about/handlers.rs | 23 ++---- 4 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/app.rs b/src/app.rs index 968cba48..13d8a83f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,7 +18,7 @@ use crate::services::torrent::{ DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository, }; use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository, Repository}; -use crate::services::{authorization, proxy, settings, torrent}; +use crate::services::{about, authorization, proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::web::api::server::signals::Halted; use crate::web::api::server::v1::auth::Authentication; @@ -141,6 +141,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running user_authentication_repository.clone(), )); + let about_service = Arc::new(about::Service::new()); + // Build app container let app_data = Arc::new(AppData::new( @@ -174,6 +176,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running registration_service, profile_service, ban_service, + about_service, )); // Start cronjob to import tracker torrent data and updating diff --git a/src/common.rs b/src/common.rs index 2ce59f7d..f7bb65d6 100644 --- a/src/common.rs +++ b/src/common.rs @@ -11,7 +11,7 @@ use crate::services::torrent::{ DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository, }; use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, Repository}; -use crate::services::{proxy, settings, torrent}; +use crate::services::{about, proxy, settings, torrent}; use crate::tracker::statistics_importer::StatisticsImporter; use crate::web::api::server::v1::auth::Authentication; use crate::{mailer, tracker}; @@ -51,6 +51,7 @@ pub struct AppData { pub registration_service: Arc, pub profile_service: Arc, pub ban_service: Arc, + pub about_service: Arc, } impl AppData { @@ -88,6 +89,7 @@ impl AppData { registration_service: Arc, profile_service: Arc, ban_service: Arc, + about_service: Arc, ) -> AppData { AppData { cfg, @@ -122,6 +124,7 @@ impl AppData { registration_service, profile_service, ban_service, + about_service, } } } diff --git a/src/services/about.rs b/src/services/about.rs index a11bdfb2..2e0bc29d 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -1,13 +1,15 @@ //! Templates for "about" static pages. -#[must_use] -pub fn index_page() -> String { - page() -} +pub struct Service {} + +impl Service { + #[must_use] + pub fn new() -> Service { + Service {} + } -#[must_use] -pub fn page() -> String { - r#" + pub fn get_about_page(&self) -> String { + r#" About @@ -24,36 +26,36 @@ pub fn page() -> String { "# - .to_string() -} - -#[must_use] -pub fn license_page() -> String { - r#" - - - Licensing - - -

Torrust Index

- -

Licensing

- -

Multiple Licenses

- -

This repository has multiple licenses depending on the content type, the date of contributions or stemming from external component licenses that were not developed by any of Torrust team members or Torrust repository contributors.

- -

The two main applicable license to most of its content are:

- -
- -

- For Media (Images, etc.) -- cc-by-sa

- -

If you want to read more about all the licenses and how they apply please refer to the contributor agreement.

- - - -"#.to_string() + .to_string() + } + + pub fn get_license_page(&self) -> String { + r#" + + + Licensing + + +

Torrust Index

+ +

Licensing

+ +

Multiple Licenses

+ +

This repository has multiple licenses depending on the content type, the date of contributions or stemming from external component licenses that were not developed by any of Torrust team members or Torrust repository contributors.

+ +

The two main applicable license to most of its content are:

+ +

- For Code -- agpl-3.0

+ +

- For Media (Images, etc.) -- cc-by-sa

+ +

If you want to read more about all the licenses and how they apply please refer to the contributor agreement.

+ + + + "#.to_string() + } } diff --git a/src/web/api/server/v1/contexts/about/handlers.rs b/src/web/api/server/v1/contexts/about/handlers.rs index 76004133..e1b2109c 100644 --- a/src/web/api/server/v1/contexts/about/handlers.rs +++ b/src/web/api/server/v1/contexts/about/handlers.rs @@ -7,24 +7,17 @@ use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; use crate::common::AppData; -use crate::services::about; #[allow(clippy::unused_async)] -pub async fn about_page_handler(State(_app_data): State>) -> Response { - ( - StatusCode::OK, - [(header::CONTENT_TYPE, "text/html; charset=utf-8")], - about::page(), - ) - .into_response() +pub async fn about_page_handler(State(app_data): State>) -> Response { + let html = app_data.about_service.get_about_page(); + + (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response() } #[allow(clippy::unused_async)] -pub async fn license_page_handler(State(_app_data): State>) -> Response { - ( - StatusCode::OK, - [(header::CONTENT_TYPE, "text/html; charset=utf-8")], - about::license_page(), - ) - .into_response() +pub async fn license_page_handler(State(app_data): State>) -> Response { + let html = app_data.about_service.get_license_page(); + + (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response() } From 6f8de8022c59870673a05fda141d46ff313c25a9 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 6 Jul 2024 17:07:32 +0200 Subject: [PATCH 266/309] feat: [#615] authorization service implemented for the about service --- src/app.rs | 2 +- src/services/about.rs | 34 +++++++++++++------ src/services/authorization.rs | 5 ++- .../api/server/v1/contexts/about/handlers.rs | 16 +++++---- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index 13d8a83f..f5131c23 100644 --- a/src/app.rs +++ b/src/app.rs @@ -141,7 +141,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running user_authentication_repository.clone(), )); - let about_service = Arc::new(about::Service::new()); + let about_service = Arc::new(about::Service::new(authorization_service.clone())); // Build app container diff --git a/src/services/about.rs b/src/services/about.rs index 2e0bc29d..aaa3ca9f 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -1,15 +1,24 @@ //! Templates for "about" static pages. -pub struct Service {} +use std::sync::Arc; + +use super::authorization::{self, ACTION}; +use crate::errors::ServiceError; + +pub struct Service { + authorization_service: Arc, +} impl Service { #[must_use] - pub fn new() -> Service { - Service {} + pub fn new(authorization_service: Arc) -> Service { + Service { authorization_service } } - pub fn get_about_page(&self) -> String { - r#" + pub async fn get_about_page(&self) -> Result { + self.authorization_service.authorize(ACTION::GetAboutPage, None).await?; + + let html = r#" About @@ -25,12 +34,15 @@ impl Service { license -"# - .to_string() +"#; + + Ok(html.to_string()) } - pub fn get_license_page(&self) -> String { - r#" + pub async fn get_license_page(&self) -> Result { + self.authorization_service.authorize(ACTION::GetLicensePage, None).await?; + + let html = r#" Licensing @@ -56,6 +68,8 @@ impl Service { about - "#.to_string() + "#; + + Ok(html.to_string()) } } diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 89dbbfef..cd6ae939 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -39,6 +39,8 @@ pub enum ACTION { DeleteTag, DeleteTorrent, BanUser, + GetAboutPage, + GetLicensePage, } pub struct Service { @@ -175,7 +177,8 @@ impl CasbinConfiguration { admin, DeleteTag admin, DeleteTorrent admin, BanUser - + guest, GetAboutPage + guest, GetLicensePage ", ), } diff --git a/src/web/api/server/v1/contexts/about/handlers.rs b/src/web/api/server/v1/contexts/about/handlers.rs index e1b2109c..8bc61e86 100644 --- a/src/web/api/server/v1/contexts/about/handlers.rs +++ b/src/web/api/server/v1/contexts/about/handlers.rs @@ -10,14 +10,18 @@ use crate::common::AppData; #[allow(clippy::unused_async)] pub async fn about_page_handler(State(app_data): State>) -> Response { - let html = app_data.about_service.get_about_page(); - - (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response() + match app_data.about_service.get_about_page().await { + Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response(), + Err(error) => error.into_response(), + } } #[allow(clippy::unused_async)] pub async fn license_page_handler(State(app_data): State>) -> Response { - let html = app_data.about_service.get_license_page(); - - (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response() + match app_data.about_service.get_license_page().await { + Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html) + .into_response() + .into_response(), + Err(error) => error.into_response(), + } } From eee3349cf1401beb964e43cb51c2168d875d9c64 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 6 Jul 2024 23:59:10 +0200 Subject: [PATCH 267/309] refactor: [#615] new get tags service method --- src/services/tag.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/tag.rs b/src/services/tag.rs index e8684b52..b22904a7 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -68,6 +68,12 @@ impl Service { }, } } + + pub async fn get_tags(&self) -> Result, ServiceError> { + self.authorization_service.authorize(ACTION::GetTags, None).await?; + + self.tag_repository.get_all().await.map_err(|_| ServiceError::DatabaseError) + } } pub struct DbTagRepository { From cd8f6096ad402636506df3b76e2fcd210165d1c5 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 7 Jul 2024 00:15:42 +0200 Subject: [PATCH 268/309] feat: [#615] authorization implemented for get tags method --- src/services/authorization.rs | 2 ++ src/web/api/server/v1/contexts/tag/handlers.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index cd6ae939..e025ca6b 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -41,6 +41,7 @@ pub enum ACTION { BanUser, GetAboutPage, GetLicensePage, + GetTags, } pub struct Service { @@ -179,6 +180,7 @@ impl CasbinConfiguration { admin, BanUser guest, GetAboutPage guest, GetLicensePage + guest, GetTags ", ), } diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index 18c636ef..8adf868a 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -26,7 +26,7 @@ use crate::web::api::server::v1::responses::{self}; /// It returns an error if there is a database error. #[allow(clippy::unused_async)] pub async fn get_all_handler(State(app_data): State>) -> Response { - match app_data.tag_repository.get_all().await { + match app_data.tag_service.get_tags().await { Ok(tags) => Json(responses::OkResponseData { data: tags }).into_response(), Err(error) => error.into_response(), } From fe11c2f946844b609b158a99b0a1ad7b8ad6120b Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 7 Jul 2024 18:15:08 +0200 Subject: [PATCH 269/309] refactor: [#615] added comments for new functions --- src/services/about.rs | 16 ++++++++++++++++ src/services/tag.rs | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/src/services/about.rs b/src/services/about.rs index aaa3ca9f..bc3c32bb 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -15,6 +15,14 @@ impl Service { Service { authorization_service } } + /// Returns the html with the about page + /// + /// # Errors + /// + /// It returns an error if: + /// + /// * The user does not have the required permissions. + /// * There is an error authorizing the action. pub async fn get_about_page(&self) -> Result { self.authorization_service.authorize(ACTION::GetAboutPage, None).await?; @@ -39,6 +47,14 @@ impl Service { Ok(html.to_string()) } + /// Returns the html with the license page + /// + /// # Errors + /// + /// It returns an error if: + /// + /// * The user does not have the required permissions. + /// * There is an error authorizing the action. pub async fn get_license_page(&self) -> Result { self.authorization_service.authorize(ACTION::GetLicensePage, None).await?; diff --git a/src/services/tag.rs b/src/services/tag.rs index b22904a7..f2c8cbb3 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -69,6 +69,14 @@ impl Service { } } + /// Returns all the tags from the database + /// + /// # Errors + /// + /// It returns an error if: + /// + /// * The user does not have the required permissions. + /// * There is a database error retrieving the tags. pub async fn get_tags(&self) -> Result, ServiceError> { self.authorization_service.authorize(ACTION::GetTags, None).await?; From 839a00d58a0961e5074d9d6aa61113919082b62a Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 7 Jul 2024 18:43:05 +0200 Subject: [PATCH 270/309] feat: [#615] New get categories service method --- src/services/authorization.rs | 2 ++ src/services/category.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index e025ca6b..c4b58bf0 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -41,6 +41,7 @@ pub enum ACTION { BanUser, GetAboutPage, GetLicensePage, + GetCategories, GetTags, } @@ -180,6 +181,7 @@ impl CasbinConfiguration { admin, BanUser guest, GetAboutPage guest, GetLicensePage + guest, GetCategories guest, GetTags ", ), diff --git a/src/services/category.rs b/src/services/category.rs index b5b4523f..5a197f4e 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -78,6 +78,23 @@ impl Service { }, } } + + /// Returns all the categories from the database + /// + /// # Errors + /// + /// It returns an error if: + /// + /// * The user does not have the required permissions. + /// * There is a database error retrieving the categories. + pub async fn get_categories(&self) -> Result, ServiceError> { + self.authorization_service.authorize(ACTION::GetCategories, None).await?; + + self.category_repository + .get_all() + .await + .map_err(|_| ServiceError::DatabaseError) + } } pub struct DbCategoryRepository { From 0ac5f9be7d68c379107168724b384b5278fc2cc2 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 7 Jul 2024 18:45:13 +0200 Subject: [PATCH 271/309] refactor: [#615] get all categories handler now calls the category service which uses the authorization layer --- src/web/api/server/v1/contexts/category/handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index 5e9d8c69..0363906c 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -26,7 +26,7 @@ use crate::web::api::server::v1::responses::{self}; /// It returns an error if there is a database error. #[allow(clippy::unused_async)] pub async fn get_all_handler(State(app_data): State>) -> Response { - match app_data.category_repository.get_all().await { + match app_data.category_service.get_categories().await { Ok(categories) => { let categories: Vec = categories.into_iter().map(Category::from).collect(); Json(responses::OkResponseData { data: categories }).into_response() From 4f6ea183200b80493655ff69d321c020b753047d Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 10 Jul 2024 13:46:49 +0200 Subject: [PATCH 272/309] refactor: [#615] new action and reordered old ones for cohesion --- src/services/authorization.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index c4b58bf0..8d2f73b0 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -31,18 +31,19 @@ impl fmt::Display for UserRole { #[derive(Debug, Clone, Serialize, Deserialize, Hash)] pub enum ACTION { + GetCategories, AddCategory, DeleteCategory, GetSettings, GetSettingsSecret, + GetTags, AddTag, DeleteTag, DeleteTorrent, BanUser, GetAboutPage, GetLicensePage, - GetCategories, - GetTags, + GetImageByUrl, } pub struct Service { @@ -179,10 +180,11 @@ impl CasbinConfiguration { admin, DeleteTag admin, DeleteTorrent admin, BanUser - guest, GetAboutPage - guest, GetLicensePage + registered, GetImageByUrl guest, GetCategories guest, GetTags + guest, GetAboutPage + guest, GetLicensePage ", ), } From 173f43def2c5dccc387d489f06a20ffe65a56cf8 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 10 Jul 2024 13:51:09 +0200 Subject: [PATCH 273/309] feat: [#615] authorization layer implemented for the proxy service --- src/app.rs | 5 ++++- src/services/proxy.rs | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index f5131c23..871e6e27 100644 --- a/src/app.rs +++ b/src/app.rs @@ -101,7 +101,10 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running authorization_service.clone(), )); let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), authorization_service.clone())); - let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone())); + let proxy_service = Arc::new(proxy::Service::new( + image_cache_service.clone(), + authorization_service.clone(), + )); let settings_service = Arc::new(settings::Service::new(configuration.clone(), authorization_service.clone())); let torrent_index = Arc::new(torrent::Index::new( configuration.clone(), diff --git a/src/services/proxy.rs b/src/services/proxy.rs index 7ac2a475..b0facbc8 100644 --- a/src/services/proxy.rs +++ b/src/services/proxy.rs @@ -10,21 +10,21 @@ use std::sync::Arc; use bytes::Bytes; +use super::authorization::{self, ACTION}; use crate::cache::image::manager::{Error, ImageCacheService}; use crate::models::user::UserId; -use crate::services::user::Repository; pub struct Service { image_cache_service: Arc, - user_repository: Arc>, + authorization_service: Arc, } impl Service { #[must_use] - pub fn new(image_cache_service: Arc, user_repository: Arc>) -> Self { + pub fn new(image_cache_service: Arc, authorization_service: Arc) -> Self { Self { image_cache_service, - user_repository, + authorization_service, } } @@ -39,8 +39,11 @@ impl Service { /// * The image is too big. /// * The user quota is met. pub async fn get_image_by_url(&self, url: &str, user_id: &UserId) -> Result { - let user = self.user_repository.get_compact(user_id).await.ok(); + self.authorization_service + .authorize(ACTION::GetImageByUrl, Some(*user_id)) + .await + .map_err(|_| Error::Unauthenticated)?; - self.image_cache_service.get_image_by_url(url, user).await + self.image_cache_service.get_image_by_url(url, *user_id).await } } From 056cb83f5770855b3dff6fc102b6171c2e4b6a33 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 10 Jul 2024 13:53:34 +0200 Subject: [PATCH 274/309] refactor: [#615] methods used by the proxy service now use the user id instead of the user compact type --- src/cache/image/manager.rs | 45 ++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/cache/image/manager.rs b/src/cache/image/manager.rs index 24a7e771..98b7612e 100644 --- a/src/cache/image/manager.rs +++ b/src/cache/image/manager.rs @@ -7,7 +7,7 @@ use tokio::sync::RwLock; use crate::cache::BytesCache; use crate::config::Configuration; -use crate::models::user::UserCompact; +use crate::models::user::UserId; pub enum Error { UrlIsUnreachable, @@ -125,33 +125,26 @@ impl ImageCacheService { /// # Errors /// /// Return a `Error::Unauthenticated` if the user has not been authenticated. - pub async fn get_image_by_url(&self, url: &str, opt_user: Option) -> Result { + pub async fn get_image_by_url(&self, url: &str, user_id: UserId) -> Result { if let Some(entry) = self.image_cache.read().await.get(url).await { return Ok(entry.bytes); } + self.check_user_quota(&user_id).await?; - match opt_user { - None => Err(Error::Unauthenticated), + let image_bytes = self.get_image_from_url_as_bytes(url).await?; - Some(user) => { - self.check_user_quota(&user).await?; + self.check_image_size(&image_bytes).await?; - let image_bytes = self.get_image_from_url_as_bytes(url).await?; + // These two functions could be executed after returning the image to the client, + // but than we would need a dedicated task or thread that executes these functions. + // This can be problematic if a task is spawned after every user request. + // Since these functions execute very fast, I don't see a reason to further optimize this. + // For now. + self.update_image_cache(url, &image_bytes).await?; - self.check_image_size(&image_bytes).await?; + self.update_user_quota(&user_id, image_bytes.len()).await?; - // These two functions could be executed after returning the image to the client, - // but than we would need a dedicated task or thread that executes these functions. - // This can be problematic if a task is spawned after every user request. - // Since these functions execute very fast, I don't see a reason to further optimize this. - // For now. - self.update_image_cache(url, &image_bytes).await?; - - self.update_user_quota(&user, image_bytes.len()).await?; - - Ok(image_bytes) - } - } + Ok(image_bytes) } async fn get_image_from_url_as_bytes(&self, url: &str) -> Result { @@ -176,8 +169,8 @@ impl ImageCacheService { res.bytes().await.map_err(|_| Error::UrlIsNotAnImage) } - async fn check_user_quota(&self, user: &UserCompact) -> Result<(), Error> { - if let Some(quota) = self.user_quotas.read().await.get(&user.user_id) { + async fn check_user_quota(&self, user_id: &UserId) -> Result<(), Error> { + if let Some(quota) = self.user_quotas.read().await.get(user_id) { if quota.is_reached() { return Err(Error::UserQuotaMet); } @@ -211,24 +204,24 @@ impl ImageCacheService { Ok(()) } - async fn update_user_quota(&self, user: &UserCompact, amount: usize) -> Result<(), Error> { + async fn update_user_quota(&self, user_id: &UserId, amount: usize) -> Result<(), Error> { let settings = self.cfg.settings.read().await; let mut quota = self .user_quotas .read() .await - .get(&user.user_id) + .get(&user_id) .cloned() .unwrap_or(ImageCacheQuota::new( - user.user_id, + *user_id, settings.image_cache.user_quota_bytes, settings.image_cache.user_quota_period_seconds, )); let _ = quota.add_usage(amount); - let _ = self.user_quotas.write().await.insert(user.user_id, quota); + let _ = self.user_quotas.write().await.insert(*user_id, quota); Ok(()) } From d1d2941b5efec90d6fa072de5f3760eb1879e51e Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 10 Jul 2024 13:56:50 +0200 Subject: [PATCH 275/309] refactor: [#615] fix linting errors --- src/cache/image/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cache/image/manager.rs b/src/cache/image/manager.rs index 98b7612e..e954bcbd 100644 --- a/src/cache/image/manager.rs +++ b/src/cache/image/manager.rs @@ -211,7 +211,7 @@ impl ImageCacheService { .user_quotas .read() .await - .get(&user_id) + .get(user_id) .cloned() .unwrap_or(ImageCacheQuota::new( *user_id, From 530f37a9776db9d2d66bd5249e59661217fc46d5 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 10 Jul 2024 17:00:31 +0200 Subject: [PATCH 276/309] feat: [#615] admin user is now authorize to see proxied images --- src/services/authorization.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 8d2f73b0..ebd4aee8 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -180,6 +180,7 @@ impl CasbinConfiguration { admin, DeleteTag admin, DeleteTorrent admin, BanUser + admin, GetImageByUrl registered, GetImageByUrl guest, GetCategories guest, GetTags From 2880f7c18db74e0cdb8963b59e3006172cee1645 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 12 Jul 2024 17:37:35 +0200 Subject: [PATCH 277/309] feat: [#615] added authorization layer for get public settings method of the settings service --- src/services/authorization.rs | 5 ++++- src/services/settings.rs | 8 ++++++-- src/web/api/server/v1/contexts/settings/handlers.rs | 13 +++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index ebd4aee8..50ab608e 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -44,6 +44,7 @@ pub enum ACTION { GetAboutPage, GetLicensePage, GetImageByUrl, + GetPublicSettings, } pub struct Service { @@ -174,7 +175,7 @@ impl CasbinConfiguration { " admin, AddCategory admin, DeleteCategory - admin, GetSettings + admin, GetPublicSettings admin, GetSettingsSecret admin, AddTag admin, DeleteTag @@ -182,10 +183,12 @@ impl CasbinConfiguration { admin, BanUser admin, GetImageByUrl registered, GetImageByUrl + registered, GetPublicSettings guest, GetCategories guest, GetTags guest, GetAboutPage guest, GetLicensePage + guest, GetPublicSettings ", ), } diff --git a/src/services/settings.rs b/src/services/settings.rs index 8efcc89d..69914313 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -62,9 +62,13 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_public(&self) -> ConfigurationPublic { + pub async fn get_public(&self, opt_user_id: Option) -> Result { + self.authorization_service + .authorize(ACTION::GetPublicSettings, opt_user_id) + .await?; + let settings_lock = self.configuration.get_all().await; - extract_public_settings(&settings_lock) + Ok(extract_public_settings(&settings_lock)) } /// It gets the site name from the settings. diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index 27b4fc04..d4849835 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -6,6 +6,7 @@ use axum::extract::State; use axum::response::{IntoResponse, Json, Response}; use crate::common::AppData; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses; @@ -30,10 +31,14 @@ pub async fn get_all_handler( /// Get public Settings. #[allow(clippy::unused_async)] -pub async fn get_public_handler(State(app_data): State>) -> Response { - let public_settings = app_data.settings_service.get_public().await; - - Json(responses::OkResponseData { data: public_settings }).into_response() +pub async fn get_public_handler( + State(app_data): State>, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data.settings_service.get_public(opt_user_id).await { + Ok(public_settings) => Json(responses::OkResponseData { data: public_settings }).into_response(), + Err(error) => error.into_response(), + } } /// Get website name. From b483c8e226655c1c4824e5281987ee66660afe64 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 12 Jul 2024 17:57:10 +0200 Subject: [PATCH 278/309] refactor: [#615] renamed variable --- src/services/authorization.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 50ab608e..2fca395a 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -68,8 +68,8 @@ impl Service { /// Will return an error if: /// - The user is not authorized to perform the action. - pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { - let role = self.get_role(maybe_user_id).await; + pub async fn authorize(&self, action: ACTION, opt_user_id: Option) -> std::result::Result<(), ServiceError> { + let role = self.get_role(opt_user_id).await; let enforcer = self.casbin_enforcer.enforcer.read().await; From 1c607c2cbd4eaede670828de6ccc8edad185837e Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 12 Jul 2024 22:19:50 +0200 Subject: [PATCH 279/309] refactor: [#615] added optional logged in user to public handlers and methods --- src/services/about.rs | 13 +++++++++---- src/services/authorization.rs | 4 ++++ src/services/category.rs | 6 ++++-- src/services/proxy.rs | 8 +++++--- src/services/tag.rs | 4 ++-- src/web/api/server/v1/contexts/about/handlers.rs | 15 +++++++++++---- .../api/server/v1/contexts/category/handlers.rs | 8 ++++++-- src/web/api/server/v1/contexts/proxy/handlers.rs | 15 +++------------ src/web/api/server/v1/contexts/tag/handlers.rs | 8 ++++++-- 9 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/services/about.rs b/src/services/about.rs index bc3c32bb..8b294bed 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use super::authorization::{self, ACTION}; use crate::errors::ServiceError; +use crate::models::user::UserId; pub struct Service { authorization_service: Arc, @@ -23,8 +24,10 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is an error authorizing the action. - pub async fn get_about_page(&self) -> Result { - self.authorization_service.authorize(ACTION::GetAboutPage, None).await?; + pub async fn get_about_page(&self, opt_user_id: Option) -> Result { + self.authorization_service + .authorize(ACTION::GetAboutPage, opt_user_id) + .await?; let html = r#" @@ -55,8 +58,10 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is an error authorizing the action. - pub async fn get_license_page(&self) -> Result { - self.authorization_service.authorize(ACTION::GetLicensePage, None).await?; + pub async fn get_license_page(&self, opt_user_id: Option) -> Result { + self.authorization_service + .authorize(ACTION::GetLicensePage, opt_user_id) + .await?; let html = r#" diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 2fca395a..9dbed209 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -173,17 +173,21 @@ impl CasbinConfiguration { ), policy: String::from( " + admin, GetCategories admin, AddCategory admin, DeleteCategory admin, GetPublicSettings admin, GetSettingsSecret + admin, GetTags admin, AddTag admin, DeleteTag admin, DeleteTorrent admin, BanUser admin, GetImageByUrl + registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings + registered, GetTags guest, GetCategories guest, GetTags guest, GetAboutPage diff --git a/src/services/category.rs b/src/services/category.rs index 5a197f4e..746e859b 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -87,8 +87,10 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error retrieving the categories. - pub async fn get_categories(&self) -> Result, ServiceError> { - self.authorization_service.authorize(ACTION::GetCategories, None).await?; + pub async fn get_categories(&self, opt_user_id: Option) -> Result, ServiceError> { + self.authorization_service + .authorize(ACTION::GetCategories, opt_user_id) + .await?; self.category_repository .get_all() diff --git a/src/services/proxy.rs b/src/services/proxy.rs index b0facbc8..dddf0afc 100644 --- a/src/services/proxy.rs +++ b/src/services/proxy.rs @@ -38,12 +38,14 @@ impl Service { /// * The image URL is not an image. /// * The image is too big. /// * The user quota is met. - pub async fn get_image_by_url(&self, url: &str, user_id: &UserId) -> Result { + #[allow(clippy::missing_panics_doc)] + pub async fn get_image_by_url(&self, url: &str, opt_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetImageByUrl, Some(*user_id)) + .authorize(ACTION::GetImageByUrl, opt_user_id) .await .map_err(|_| Error::Unauthenticated)?; - self.image_cache_service.get_image_by_url(url, *user_id).await + // The unwrap should never panic as if the opt_user_id is none, an authorization error will be returned and handled at the method above + self.image_cache_service.get_image_by_url(url, opt_user_id.unwrap()).await } } diff --git a/src/services/tag.rs b/src/services/tag.rs index f2c8cbb3..ec2974a1 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -77,8 +77,8 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error retrieving the tags. - pub async fn get_tags(&self) -> Result, ServiceError> { - self.authorization_service.authorize(ACTION::GetTags, None).await?; + pub async fn get_tags(&self, opt_user_id: Option) -> Result, ServiceError> { + self.authorization_service.authorize(ACTION::GetTags, opt_user_id).await?; self.tag_repository.get_all().await.map_err(|_| ServiceError::DatabaseError) } diff --git a/src/web/api/server/v1/contexts/about/handlers.rs b/src/web/api/server/v1/contexts/about/handlers.rs index 8bc61e86..89a35c81 100644 --- a/src/web/api/server/v1/contexts/about/handlers.rs +++ b/src/web/api/server/v1/contexts/about/handlers.rs @@ -7,18 +7,25 @@ use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; use crate::common::AppData; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; #[allow(clippy::unused_async)] -pub async fn about_page_handler(State(app_data): State>) -> Response { - match app_data.about_service.get_about_page().await { +pub async fn about_page_handler( + State(app_data): State>, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data.about_service.get_about_page(opt_user_id).await { Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response(), Err(error) => error.into_response(), } } #[allow(clippy::unused_async)] -pub async fn license_page_handler(State(app_data): State>) -> Response { - match app_data.about_service.get_license_page().await { +pub async fn license_page_handler( + State(app_data): State>, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data.about_service.get_license_page(opt_user_id).await { Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html) .into_response() .into_response(), diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index 0363906c..376305cb 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -8,6 +8,7 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddCategoryForm, DeleteCategoryForm}; use super::responses::{added_category, deleted_category, Category}; use crate::common::AppData; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; @@ -25,8 +26,11 @@ use crate::web::api::server::v1::responses::{self}; /// /// It returns an error if there is a database error. #[allow(clippy::unused_async)] -pub async fn get_all_handler(State(app_data): State>) -> Response { - match app_data.category_service.get_categories().await { +pub async fn get_all_handler( + State(app_data): State>, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data.category_service.get_categories(opt_user_id).await { Ok(categories) => { let categories: Vec = categories.into_iter().map(Category::from).collect(); Json(responses::OkResponseData { data: categories }).into_response() diff --git a/src/web/api/server/v1/contexts/proxy/handlers.rs b/src/web/api/server/v1/contexts/proxy/handlers.rs index 7c04e50f..9153cbfe 100644 --- a/src/web/api/server/v1/contexts/proxy/handlers.rs +++ b/src/web/api/server/v1/contexts/proxy/handlers.rs @@ -6,26 +6,17 @@ use axum::extract::{Path, State}; use axum::response::Response; use super::responses::png_image; -use crate::cache::image::manager::Error; use crate::common::AppData; use crate::ui::proxy::map_error_to_image; -use crate::web::api::server::v1::extractors::bearer_token::Extract; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; /// Get the remote image. It uses the cached image if available. #[allow(clippy::unused_async)] pub async fn get_proxy_image_handler( State(app_data): State>, - Extract(maybe_bearer_token): Extract, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, Path(url): Path, ) -> Response { - if maybe_bearer_token.is_none() { - return png_image(map_error_to_image(&Error::Unauthenticated)); - } - - let Ok(user_id) = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await else { - return png_image(map_error_to_image(&Error::Unauthenticated)); - }; - // code-review: Handling status codes in the index-gui other tan OK is quite a pain. // Return OK for now. @@ -36,7 +27,7 @@ pub async fn get_proxy_image_handler( // Get image URL from URL path parameter. let image_url = urlencoding::decode(&url).unwrap_or_default().into_owned(); - match app_data.proxy_service.get_image_by_url(&image_url, &user_id).await { + match app_data.proxy_service.get_image_by_url(&image_url, opt_user_id).await { Ok(image_bytes) => { // Returns the cached image. png_image(image_bytes) diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index 8adf868a..b5ee637c 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -8,6 +8,7 @@ use axum::response::{IntoResponse, Json, Response}; use super::forms::{AddTagForm, DeleteTagForm}; use super::responses::{added_tag, deleted_tag}; use crate::common::AppData; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; @@ -25,8 +26,11 @@ use crate::web::api::server::v1::responses::{self}; /// /// It returns an error if there is a database error. #[allow(clippy::unused_async)] -pub async fn get_all_handler(State(app_data): State>) -> Response { - match app_data.tag_service.get_tags().await { +pub async fn get_all_handler( + State(app_data): State>, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data.tag_service.get_tags(opt_user_id).await { Ok(tags) => Json(responses::OkResponseData { data: tags }).into_response(), Err(error) => error.into_response(), } From b5171bf1290d3b87ba278a746fe4985af5eb46c9 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 13 Jul 2024 15:42:08 +0200 Subject: [PATCH 280/309] feat: [#615] authorization layer added for add torrent method of the torrent service --- src/services/authorization.rs | 3 +++ src/services/torrent.rs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 9dbed209..72ae5679 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -45,6 +45,7 @@ pub enum ACTION { GetLicensePage, GetImageByUrl, GetPublicSettings, + AddTorrent, } pub struct Service { @@ -184,10 +185,12 @@ impl CasbinConfiguration { admin, DeleteTorrent admin, BanUser admin, GetImageByUrl + admin, AddTorrent registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings registered, GetTags + registered, AddTorrent guest, GetCategories guest, GetTags guest, GetAboutPage diff --git a/src/services/torrent.rs b/src/services/torrent.rs index fa671a1e..c71ffc18 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -134,8 +134,9 @@ impl Index { add_torrent_req: AddTorrentRequest, user_id: UserId, ) -> Result { - // Guard that the users exists - let _user = self.user_repository.get_compact(&user_id).await?; + self.authorization_service + .authorize(ACTION::AddTorrent, Some(user_id)) + .await?; let metadata = self.validate_and_build_metadata(&add_torrent_req).await?; From 7bb6ff75f3cdba21056e1fc3bc341b5a09a37d38 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 14 Jul 2024 19:39:17 +0200 Subject: [PATCH 281/309] feat: [#615] authorization layer implemented for the get torrent method of the torrent service --- src/services/authorization.rs | 4 ++++ src/services/torrent.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 72ae5679..9419b216 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -45,6 +45,7 @@ pub enum ACTION { GetLicensePage, GetImageByUrl, GetPublicSettings, + GetTorrent, AddTorrent, } @@ -185,17 +186,20 @@ impl CasbinConfiguration { admin, DeleteTorrent admin, BanUser admin, GetImageByUrl + admin, GetTorrent admin, AddTorrent registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings registered, GetTags + registered, GetTorrent registered, AddTorrent guest, GetCategories guest, GetTags guest, GetAboutPage guest, GetLicensePage guest, GetPublicSettings + guest, GetTorrent ", ), } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index c71ffc18..5d5464b9 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -263,6 +263,8 @@ impl Index { /// This function will return an error if unable to get the torrent from the /// database. pub async fn get_torrent(&self, info_hash: &InfoHash, opt_user_id: Option) -> Result { + self.authorization_service.authorize(ACTION::GetTorrent, opt_user_id).await?; + let mut torrent = self.torrent_repository.get_by_info_hash(info_hash).await?; let tracker_url = self.get_tracker_url().await; From b3ffc9f2880c694149ab75d62e182ce018a0fc23 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 14:19:02 +0200 Subject: [PATCH 282/309] feat: [#615] new get canonical info hash method --- src/services/authorization.rs | 4 ++++ src/services/torrent.rs | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 9419b216..4f85800c 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -47,6 +47,7 @@ pub enum ACTION { GetPublicSettings, GetTorrent, AddTorrent, + GetCanonicalInfoHash, } pub struct Service { @@ -188,18 +189,21 @@ impl CasbinConfiguration { admin, GetImageByUrl admin, GetTorrent admin, AddTorrent + admin, GetCanonicalInfoHash registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings registered, GetTags registered, GetTorrent registered, AddTorrent + registered, GetCanonicalInfoHash guest, GetCategories guest, GetTags guest, GetAboutPage guest, GetLicensePage guest, GetPublicSettings guest, GetTorrent + guest, GetCanonicalInfoHash ", ), } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 5d5464b9..23695684 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -547,6 +547,26 @@ impl Index { Ok(torrent_response) } + + /// It returns the canonical info-hash. + /// + /// # Errors + /// + /// Returns an error if the user is not is there was a problem with the database. + pub async fn get_canonical_info_hash( + &self, + info_hash: &InfoHash, + opt_user_id: Option, + ) -> Result, ServiceError> { + self.authorization_service + .authorize(ACTION::GetCanonicalInfoHash, opt_user_id) + .await?; + + self.torrent_info_hash_repository + .find_canonical_info_hash_for(info_hash) + .await + .map_err(|_| ServiceError::DatabaseError) + } } pub struct DbTorrentRepository { From d56d66cb9472b3a51107bc9ce50a0b9f041da20d Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 14:25:14 +0200 Subject: [PATCH 283/309] refactor: [#615] download torrent handler now calls the torrent service to get the canonical infohash --- src/web/api/server/v1/contexts/torrent/handlers.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index 5b38d3e4..98eae53b 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -77,7 +77,9 @@ pub async fn download_torrent_handler( debug!("Downloading torrent: {:?}", info_hash.to_hex_string()); - if let Some(redirect_response) = redirect_to_download_url_using_canonical_info_hash_if_needed(&app_data, &info_hash).await { + if let Some(redirect_response) = + redirect_to_download_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, opt_user_id).await + { debug!("Redirecting to URL with canonical info-hash"); redirect_response } else { @@ -101,12 +103,9 @@ pub async fn download_torrent_handler( async fn redirect_to_download_url_using_canonical_info_hash_if_needed( app_data: &Arc, info_hash: &InfoHash, + opt_user_id: Option, ) -> Option { - match app_data - .torrent_info_hash_repository - .find_canonical_info_hash_for(info_hash) - .await - { + match app_data.torrent_service.get_canonical_info_hash(info_hash, opt_user_id).await { Ok(Some(canonical_info_hash)) => { if canonical_info_hash != *info_hash { return Some( From f264e758894a9c3cf91ef9678660f0ffbefec785 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 14:56:06 +0200 Subject: [PATCH 284/309] fix: [#615] fix unfinished comment --- src/services/torrent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 23695684..5123213b 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -548,11 +548,11 @@ impl Index { Ok(torrent_response) } - /// It returns the canonical info-hash. + /// Returns the canonical info-hash. /// /// # Errors /// - /// Returns an error if the user is not is there was a problem with the database. + /// Returns an error if the user is not authorized or if there is a problem with the database. pub async fn get_canonical_info_hash( &self, info_hash: &InfoHash, From 1342dce13a22f1af5d50bb27979aae713b964544 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 16:53:19 +0200 Subject: [PATCH 285/309] feat: [#615] authorization layer implemented for the get torrents handler --- src/services/authorization.rs | 4 ++++ src/services/torrent.rs | 10 +++++++++- src/web/api/server/v1/contexts/torrent/handlers.rs | 12 ++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 4f85800c..fac7d1cb 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -48,6 +48,7 @@ pub enum ACTION { GetTorrent, AddTorrent, GetCanonicalInfoHash, + GenerateTorrentInfoListing, } pub struct Service { @@ -190,6 +191,7 @@ impl CasbinConfiguration { admin, GetTorrent admin, AddTorrent admin, GetCanonicalInfoHash + admin, GenerateTorrentInfoListing registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings @@ -197,6 +199,7 @@ impl CasbinConfiguration { registered, GetTorrent registered, AddTorrent registered, GetCanonicalInfoHash + registered, GenerateTorrentInfoListing guest, GetCategories guest, GetTags guest, GetAboutPage @@ -204,6 +207,7 @@ impl CasbinConfiguration { guest, GetPublicSettings guest, GetTorrent guest, GetCanonicalInfoHash + guest, GenerateTorrentInfoListing ", ), } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 5123213b..c1ee4125 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -347,7 +347,15 @@ impl Index { /// # Errors /// /// Returns a `ServiceError::DatabaseError` if the database query fails. - pub async fn generate_torrent_info_listing(&self, request: &ListingRequest) -> Result { + pub async fn generate_torrent_info_listing( + &self, + request: &ListingRequest, + opt_user_id: Option, + ) -> Result { + self.authorization_service + .authorize(ACTION::GenerateTorrentInfoListing, opt_user_id) + .await?; + let torrent_listing_specification = self.listing_specification_from_user_request(request).await; let torrents_response = self diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index 98eae53b..d633a6b3 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -131,8 +131,16 @@ async fn redirect_to_download_url_using_canonical_info_hash_if_needed( /// /// It returns an error if the database query fails. #[allow(clippy::unused_async)] -pub async fn get_torrents_handler(State(app_data): State>, Query(criteria): Query) -> Response { - match app_data.torrent_service.generate_torrent_info_listing(&criteria).await { +pub async fn get_torrents_handler( + State(app_data): State>, + Query(criteria): Query, + ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, +) -> Response { + match app_data + .torrent_service + .generate_torrent_info_listing(&criteria, opt_user_id) + .await + { Ok(torrents_response) => Json(OkResponseData { data: torrents_response }).into_response(), Err(error) => error.into_response(), } From f8e75709c1a04d1ae4754f26f810236fb262d0fc Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 23:18:08 +0200 Subject: [PATCH 286/309] feat: [#615] authorization layer added to the get torrent info method of the torrent service --- src/services/authorization.rs | 4 ++++ src/services/torrent.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index fac7d1cb..5d5c8bc7 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -49,6 +49,7 @@ pub enum ACTION { AddTorrent, GetCanonicalInfoHash, GenerateTorrentInfoListing, + GetTorrentInfo, } pub struct Service { @@ -192,6 +193,7 @@ impl CasbinConfiguration { admin, AddTorrent admin, GetCanonicalInfoHash admin, GenerateTorrentInfoListing + admin, GetTorrentInfo registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings @@ -200,6 +202,7 @@ impl CasbinConfiguration { registered, AddTorrent registered, GetCanonicalInfoHash registered, GenerateTorrentInfoListing + registered, GetTorrentInfo guest, GetCategories guest, GetTags guest, GetAboutPage @@ -208,6 +211,7 @@ impl CasbinConfiguration { guest, GetTorrent guest, GetCanonicalInfoHash guest, GenerateTorrentInfoListing + guest, GetTorrentInfo ", ), } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index c1ee4125..5e118270 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -333,6 +333,10 @@ impl Index { info_hash: &InfoHash, opt_user_id: Option, ) -> Result { + self.authorization_service + .authorize(ACTION::GetTorrentInfo, opt_user_id) + .await?; + let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?; let torrent_response = self From 543d804b4785fb0747ad2432d2856490aa025853 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 17 Jul 2024 23:50:56 +0200 Subject: [PATCH 287/309] refactor: [#615] redirect to details url with infohash method now calls the torrent service that implements the authorization layer --- src/web/api/server/v1/contexts/torrent/handlers.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index d633a6b3..243f1307 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -164,7 +164,9 @@ pub async fn get_torrent_info_handler( return errors::Request::InvalidInfoHashParam.into_response(); }; - if let Some(redirect_response) = redirect_to_details_url_using_canonical_info_hash_if_needed(&app_data, &info_hash).await { + if let Some(redirect_response) = + redirect_to_details_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, opt_user_id).await + { redirect_response } else { match app_data.torrent_service.get_torrent_info(&info_hash, opt_user_id).await { @@ -177,12 +179,9 @@ pub async fn get_torrent_info_handler( async fn redirect_to_details_url_using_canonical_info_hash_if_needed( app_data: &Arc, info_hash: &InfoHash, + opt_user_id: Option, ) -> Option { - match app_data - .torrent_info_hash_repository - .find_canonical_info_hash_for(info_hash) - .await - { + match app_data.torrent_service.get_canonical_info_hash(info_hash, opt_user_id).await { Ok(Some(canonical_info_hash)) => { if canonical_info_hash != *info_hash { return Some( From e614e2f662652d41cb825cc82dbfee1d6fcaea5e Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 21 Jul 2024 16:27:48 +0200 Subject: [PATCH 288/309] feat: [#615] authorization layer added to the change password method of the user service --- src/app.rs | 1 + src/services/authorization.rs | 3 +++ src/services/user.rs | 19 +++++++++++++++++-- .../api/server/v1/contexts/user/handlers.rs | 8 +++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 871e6e27..ab9a2066 100644 --- a/src/app.rs +++ b/src/app.rs @@ -130,6 +130,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let profile_service = Arc::new(user::ProfileService::new( configuration.clone(), user_authentication_repository.clone(), + authorization_service.clone(), )); let ban_service = Arc::new(user::BanService::new( user_profile_repository.clone(), diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 5d5c8bc7..7448cafd 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -50,6 +50,7 @@ pub enum ACTION { GetCanonicalInfoHash, GenerateTorrentInfoListing, GetTorrentInfo, + ChangePassword, } pub struct Service { @@ -194,6 +195,7 @@ impl CasbinConfiguration { admin, GetCanonicalInfoHash admin, GenerateTorrentInfoListing admin, GetTorrentInfo + admin, ChangePassword registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings @@ -203,6 +205,7 @@ impl CasbinConfiguration { registered, GetCanonicalInfoHash registered, GenerateTorrentInfoListing registered, GetTorrentInfo + registered, ChangePassword guest, GetCategories guest, GetTags guest, GetAboutPage diff --git a/src/services/user.rs b/src/services/user.rs index 503d8b38..356cc164 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -200,14 +200,20 @@ impl RegistrationService { pub struct ProfileService { configuration: Arc, user_authentication_repository: Arc, + authorization_service: Arc, } impl ProfileService { #[must_use] - pub fn new(configuration: Arc, user_repository: Arc) -> Self { + pub fn new( + configuration: Arc, + user_repository: Arc, + authorization_service: Arc, + ) -> Self { Self { configuration, user_authentication_repository: user_repository, + authorization_service, } } @@ -223,7 +229,16 @@ impl ProfileService { /// * `ServiceError::PasswordTooLong` if the supplied password is too long. /// * An error if unable to successfully hash the password. /// * An error if unable to change the password in the database. - pub async fn change_password(&self, user_id: UserId, change_password_form: &ChangePasswordForm) -> Result<(), ServiceError> { + pub async fn change_password( + &self, + user_id: UserId, + change_password_form: &ChangePasswordForm, + maybe_user_id: Option, + ) -> Result<(), ServiceError> { + self.authorization_service + .authorize(ACTION::ChangePassword, maybe_user_id) + .await?; + info!("changing user password for user ID: {user_id}"); let settings = self.configuration.settings.read().await; diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index 54b3f229..440c598d 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -10,6 +10,7 @@ use serde::Deserialize; use super::forms::{ChangePasswordForm, JsonWebToken, LoginForm, RegistrationForm}; use super::responses::{self}; use crate::common::AppData; +use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; @@ -133,10 +134,15 @@ pub async fn renew_token_handler( #[allow(clippy::unused_async)] pub async fn change_password_handler( State(app_data): State>, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(change_password_form): extract::Json, ) -> Response { - match app_data.profile_service.change_password(user_id, &change_password_form).await { + match app_data + .profile_service + .change_password(user_id, &change_password_form, maybe_user_id) + .await + { Ok(()) => Json(OkResponseData { data: format!("Password changed for user with ID: {user_id}"), }) From 93b15ac845b2450e19edddee34b698449fec9864 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 22 Jul 2024 20:06:04 +0200 Subject: [PATCH 289/309] refactor: [#615] renamed variables to maybe_user_id --- src/services/about.rs | 8 ++--- src/services/authorization.rs | 4 +-- src/services/category.rs | 4 +-- src/services/proxy.rs | 8 ++--- src/services/settings.rs | 4 +-- src/services/tag.rs | 4 +-- src/services/torrent.rs | 26 ++++++++------- .../api/server/v1/contexts/about/handlers.rs | 8 ++--- .../server/v1/contexts/category/handlers.rs | 4 +-- .../api/server/v1/contexts/proxy/handlers.rs | 4 +-- .../server/v1/contexts/settings/handlers.rs | 4 +-- .../api/server/v1/contexts/tag/handlers.rs | 4 +-- .../server/v1/contexts/torrent/handlers.rs | 32 ++++++++++++------- 13 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/services/about.rs b/src/services/about.rs index 8b294bed..6daacf0b 100644 --- a/src/services/about.rs +++ b/src/services/about.rs @@ -24,9 +24,9 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is an error authorizing the action. - pub async fn get_about_page(&self, opt_user_id: Option) -> Result { + pub async fn get_about_page(&self, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetAboutPage, opt_user_id) + .authorize(ACTION::GetAboutPage, maybe_user_id) .await?; let html = r#" @@ -58,9 +58,9 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is an error authorizing the action. - pub async fn get_license_page(&self, opt_user_id: Option) -> Result { + pub async fn get_license_page(&self, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetLicensePage, opt_user_id) + .authorize(ACTION::GetLicensePage, maybe_user_id) .await?; let html = r#" diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 7448cafd..29ff79ae 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -74,8 +74,8 @@ impl Service { /// Will return an error if: /// - The user is not authorized to perform the action. - pub async fn authorize(&self, action: ACTION, opt_user_id: Option) -> std::result::Result<(), ServiceError> { - let role = self.get_role(opt_user_id).await; + pub async fn authorize(&self, action: ACTION, maybe_user_id: Option) -> std::result::Result<(), ServiceError> { + let role = self.get_role(maybe_user_id).await; let enforcer = self.casbin_enforcer.enforcer.read().await; diff --git a/src/services/category.rs b/src/services/category.rs index 746e859b..8c6bc062 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -87,9 +87,9 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error retrieving the categories. - pub async fn get_categories(&self, opt_user_id: Option) -> Result, ServiceError> { + pub async fn get_categories(&self, maybe_user_id: Option) -> Result, ServiceError> { self.authorization_service - .authorize(ACTION::GetCategories, opt_user_id) + .authorize(ACTION::GetCategories, maybe_user_id) .await?; self.category_repository diff --git a/src/services/proxy.rs b/src/services/proxy.rs index dddf0afc..37517c5c 100644 --- a/src/services/proxy.rs +++ b/src/services/proxy.rs @@ -39,13 +39,13 @@ impl Service { /// * The image is too big. /// * The user quota is met. #[allow(clippy::missing_panics_doc)] - pub async fn get_image_by_url(&self, url: &str, opt_user_id: Option) -> Result { + pub async fn get_image_by_url(&self, url: &str, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetImageByUrl, opt_user_id) + .authorize(ACTION::GetImageByUrl, maybe_user_id) .await .map_err(|_| Error::Unauthenticated)?; - // The unwrap should never panic as if the opt_user_id is none, an authorization error will be returned and handled at the method above - self.image_cache_service.get_image_by_url(url, opt_user_id.unwrap()).await + // The unwrap should never panic as if the maybe_user_id is none, an authorization error will be returned and handled at the method above + self.image_cache_service.get_image_by_url(url, maybe_user_id.unwrap()).await } } diff --git a/src/services/settings.rs b/src/services/settings.rs index 69914313..565580ad 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -62,9 +62,9 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_public(&self, opt_user_id: Option) -> Result { + pub async fn get_public(&self, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetPublicSettings, opt_user_id) + .authorize(ACTION::GetPublicSettings, maybe_user_id) .await?; let settings_lock = self.configuration.get_all().await; diff --git a/src/services/tag.rs b/src/services/tag.rs index ec2974a1..d47b09b8 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -77,8 +77,8 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error retrieving the tags. - pub async fn get_tags(&self, opt_user_id: Option) -> Result, ServiceError> { - self.authorization_service.authorize(ACTION::GetTags, opt_user_id).await?; + pub async fn get_tags(&self, maybe_user_id: Option) -> Result, ServiceError> { + self.authorization_service.authorize(ACTION::GetTags, maybe_user_id).await?; self.tag_repository.get_all().await.map_err(|_| ServiceError::DatabaseError) } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 5e118270..0e230246 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -262,8 +262,10 @@ impl Index { /// /// This function will return an error if unable to get the torrent from the /// database. - pub async fn get_torrent(&self, info_hash: &InfoHash, opt_user_id: Option) -> Result { - self.authorization_service.authorize(ACTION::GetTorrent, opt_user_id).await?; + pub async fn get_torrent(&self, info_hash: &InfoHash, maybe_user_id: Option) -> Result { + self.authorization_service + .authorize(ACTION::GetTorrent, maybe_user_id) + .await?; let mut torrent = self.torrent_repository.get_by_info_hash(info_hash).await?; @@ -275,7 +277,7 @@ impl Index { if !tracker_is_private { torrent.include_url_as_main_tracker(&tracker_url); - } else if let Some(authenticated_user_id) = opt_user_id { + } else if let Some(authenticated_user_id) = maybe_user_id { let personal_announce_url = self.tracker_service.get_personal_announce_url(authenticated_user_id).await?; torrent.include_url_as_main_tracker(&personal_announce_url); } else { @@ -331,16 +333,16 @@ impl Index { pub async fn get_torrent_info( &self, info_hash: &InfoHash, - opt_user_id: Option, + maybe_user_id: Option, ) -> Result { self.authorization_service - .authorize(ACTION::GetTorrentInfo, opt_user_id) + .authorize(ACTION::GetTorrentInfo, maybe_user_id) .await?; let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?; let torrent_response = self - .build_full_torrent_response(torrent_listing, info_hash, opt_user_id) + .build_full_torrent_response(torrent_listing, info_hash, maybe_user_id) .await?; Ok(torrent_response) @@ -354,10 +356,10 @@ impl Index { pub async fn generate_torrent_info_listing( &self, request: &ListingRequest, - opt_user_id: Option, + maybe_user_id: Option, ) -> Result { self.authorization_service - .authorize(ACTION::GenerateTorrentInfoListing, opt_user_id) + .authorize(ACTION::GenerateTorrentInfoListing, maybe_user_id) .await?; let torrent_listing_specification = self.listing_specification_from_user_request(request).await; @@ -484,7 +486,7 @@ impl Index { &self, torrent_listing: TorrentListing, info_hash: &InfoHash, - opt_user_id: Option, + maybe_user_id: Option, ) -> Result { let torrent_id: i64 = torrent_listing.torrent_id; @@ -515,7 +517,7 @@ impl Index { if self.tracker_is_private().await { // Add main tracker URL - match opt_user_id { + match maybe_user_id { Some(user_id) => { let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?; @@ -568,10 +570,10 @@ impl Index { pub async fn get_canonical_info_hash( &self, info_hash: &InfoHash, - opt_user_id: Option, + maybe_user_id: Option, ) -> Result, ServiceError> { self.authorization_service - .authorize(ACTION::GetCanonicalInfoHash, opt_user_id) + .authorize(ACTION::GetCanonicalInfoHash, maybe_user_id) .await?; self.torrent_info_hash_repository diff --git a/src/web/api/server/v1/contexts/about/handlers.rs b/src/web/api/server/v1/contexts/about/handlers.rs index 89a35c81..530eae89 100644 --- a/src/web/api/server/v1/contexts/about/handlers.rs +++ b/src/web/api/server/v1/contexts/about/handlers.rs @@ -12,9 +12,9 @@ use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLo #[allow(clippy::unused_async)] pub async fn about_page_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - match app_data.about_service.get_about_page(opt_user_id).await { + match app_data.about_service.get_about_page(maybe_user_id).await { Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response(), Err(error) => error.into_response(), } @@ -23,9 +23,9 @@ pub async fn about_page_handler( #[allow(clippy::unused_async)] pub async fn license_page_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - match app_data.about_service.get_license_page(opt_user_id).await { + match app_data.about_service.get_license_page(maybe_user_id).await { Ok(html) => (StatusCode::OK, [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html) .into_response() .into_response(), diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index 376305cb..7a3d9425 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -28,9 +28,9 @@ use crate::web::api::server::v1::responses::{self}; #[allow(clippy::unused_async)] pub async fn get_all_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - match app_data.category_service.get_categories(opt_user_id).await { + match app_data.category_service.get_categories(maybe_user_id).await { Ok(categories) => { let categories: Vec = categories.into_iter().map(Category::from).collect(); Json(responses::OkResponseData { data: categories }).into_response() diff --git a/src/web/api/server/v1/contexts/proxy/handlers.rs b/src/web/api/server/v1/contexts/proxy/handlers.rs index 9153cbfe..241fe3aa 100644 --- a/src/web/api/server/v1/contexts/proxy/handlers.rs +++ b/src/web/api/server/v1/contexts/proxy/handlers.rs @@ -14,7 +14,7 @@ use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLo #[allow(clippy::unused_async)] pub async fn get_proxy_image_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, Path(url): Path, ) -> Response { // code-review: Handling status codes in the index-gui other tan OK is quite a pain. @@ -27,7 +27,7 @@ pub async fn get_proxy_image_handler( // Get image URL from URL path parameter. let image_url = urlencoding::decode(&url).unwrap_or_default().into_owned(); - match app_data.proxy_service.get_image_by_url(&image_url, opt_user_id).await { + match app_data.proxy_service.get_image_by_url(&image_url, maybe_user_id).await { Ok(image_bytes) => { // Returns the cached image. png_image(image_bytes) diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index d4849835..7b3a0bd7 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -33,9 +33,9 @@ pub async fn get_all_handler( #[allow(clippy::unused_async)] pub async fn get_public_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - match app_data.settings_service.get_public(opt_user_id).await { + match app_data.settings_service.get_public(maybe_user_id).await { Ok(public_settings) => Json(responses::OkResponseData { data: public_settings }).into_response(), Err(error) => error.into_response(), } diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index b5ee637c..b198fc7a 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -28,9 +28,9 @@ use crate::web::api::server::v1::responses::{self}; #[allow(clippy::unused_async)] pub async fn get_all_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - match app_data.tag_service.get_tags(opt_user_id).await { + match app_data.tag_service.get_tags(maybe_user_id).await { Ok(tags) => Json(responses::OkResponseData { data: tags }).into_response(), Err(error) => error.into_response(), } diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index 243f1307..ffdc6240 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -68,7 +68,7 @@ impl InfoHashParam { #[allow(clippy::unused_async)] pub async fn download_torrent_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { @@ -78,12 +78,12 @@ pub async fn download_torrent_handler( debug!("Downloading torrent: {:?}", info_hash.to_hex_string()); if let Some(redirect_response) = - redirect_to_download_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, opt_user_id).await + redirect_to_download_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, maybe_user_id).await { debug!("Redirecting to URL with canonical info-hash"); redirect_response } else { - let torrent = match app_data.torrent_service.get_torrent(&info_hash, opt_user_id).await { + let torrent = match app_data.torrent_service.get_torrent(&info_hash, maybe_user_id).await { Ok(torrent) => torrent, Err(error) => return error.into_response(), }; @@ -103,9 +103,13 @@ pub async fn download_torrent_handler( async fn redirect_to_download_url_using_canonical_info_hash_if_needed( app_data: &Arc, info_hash: &InfoHash, - opt_user_id: Option, + maybe_user_id: Option, ) -> Option { - match app_data.torrent_service.get_canonical_info_hash(info_hash, opt_user_id).await { + match app_data + .torrent_service + .get_canonical_info_hash(info_hash, maybe_user_id) + .await + { Ok(Some(canonical_info_hash)) => { if canonical_info_hash != *info_hash { return Some( @@ -134,11 +138,11 @@ async fn redirect_to_download_url_using_canonical_info_hash_if_needed( pub async fn get_torrents_handler( State(app_data): State>, Query(criteria): Query, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { match app_data .torrent_service - .generate_torrent_info_listing(&criteria, opt_user_id) + .generate_torrent_info_listing(&criteria, maybe_user_id) .await { Ok(torrents_response) => Json(OkResponseData { data: torrents_response }).into_response(), @@ -157,7 +161,7 @@ pub async fn get_torrents_handler( #[allow(clippy::unused_async)] pub async fn get_torrent_info_handler( State(app_data): State>, - ExtractOptionalLoggedInUser(opt_user_id): ExtractOptionalLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { @@ -165,11 +169,11 @@ pub async fn get_torrent_info_handler( }; if let Some(redirect_response) = - redirect_to_details_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, opt_user_id).await + redirect_to_details_url_using_canonical_info_hash_if_needed(&app_data, &info_hash, maybe_user_id).await { redirect_response } else { - match app_data.torrent_service.get_torrent_info(&info_hash, opt_user_id).await { + match app_data.torrent_service.get_torrent_info(&info_hash, maybe_user_id).await { Ok(torrent_response) => Json(OkResponseData { data: torrent_response }).into_response(), Err(error) => error.into_response(), } @@ -179,9 +183,13 @@ pub async fn get_torrent_info_handler( async fn redirect_to_details_url_using_canonical_info_hash_if_needed( app_data: &Arc, info_hash: &InfoHash, - opt_user_id: Option, + maybe_user_id: Option, ) -> Option { - match app_data.torrent_service.get_canonical_info_hash(info_hash, opt_user_id).await { + match app_data + .torrent_service + .get_canonical_info_hash(info_hash, maybe_user_id) + .await + { Ok(Some(canonical_info_hash)) => { if canonical_info_hash != *info_hash { return Some( From 935eb53d8cd901574e2690cb1063eeedca38b528 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 22 Jul 2024 23:48:23 +0200 Subject: [PATCH 290/309] refactor: [#615] rearrange actions --- src/services/authorization.rs | 59 +++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 29ff79ae..34bf1369 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -31,26 +31,26 @@ impl fmt::Display for UserRole { #[derive(Debug, Clone, Serialize, Deserialize, Hash)] pub enum ACTION { - GetCategories, + GetAboutPage, + GetLicensePage, AddCategory, DeleteCategory, + GetCategories, + GetImageByUrl, GetSettings, GetSettingsSecret, - GetTags, + GetPublicSettings, AddTag, DeleteTag, - DeleteTorrent, - BanUser, - GetAboutPage, - GetLicensePage, - GetImageByUrl, - GetPublicSettings, - GetTorrent, + GetTags, AddTorrent, - GetCanonicalInfoHash, - GenerateTorrentInfoListing, + GetTorrent, + DeleteTorrent, GetTorrentInfo, + GenerateTorrentInfoListing, + GetCanonicalInfoHash, ChangePassword, + BanUser, } pub struct Service { @@ -179,42 +179,47 @@ impl CasbinConfiguration { ), policy: String::from( " - admin, GetCategories + admin, GetAboutPage + admin, GetLicensePage admin, AddCategory admin, DeleteCategory - admin, GetPublicSettings + admin, GetCategories + admin, GetImageByUrl + admin, GetSettings admin, GetSettingsSecret - admin, GetTags + admin, GetPublicSettings admin, AddTag admin, DeleteTag - admin, DeleteTorrent - admin, BanUser - admin, GetImageByUrl - admin, GetTorrent + admin, GetTags admin, AddTorrent - admin, GetCanonicalInfoHash - admin, GenerateTorrentInfoListing + admin, GetTorrent + admin, DeleteTorrent admin, GetTorrentInfo + admin, GenerateTorrentInfoListing + admin, GetCanonicalInfoHash admin, ChangePassword + admin, BanUser + registered, GetAboutPage + registered, GetLicensePage registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings registered, GetTags - registered, GetTorrent registered, AddTorrent - registered, GetCanonicalInfoHash - registered, GenerateTorrentInfoListing + registered, GetTorrent registered, GetTorrentInfo + registered, GenerateTorrentInfoListing + registered, GetCanonicalInfoHash registered, ChangePassword - guest, GetCategories - guest, GetTags guest, GetAboutPage guest, GetLicensePage + guest, GetCategories guest, GetPublicSettings + guest, GetTags guest, GetTorrent - guest, GetCanonicalInfoHash - guest, GenerateTorrentInfoListing guest, GetTorrentInfo + guest, GenerateTorrentInfoListing + guest, GetCanonicalInfoHash ", ), } From 836c94d129b5c411e35bad3bd54732c8a55fc6cb Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 3 Aug 2024 16:26:34 +0200 Subject: [PATCH 291/309] refactor: [#615] upload torrent handler now uses an optional user id --- src/services/torrent.rs | 6 +++--- src/web/api/server/v1/contexts/torrent/handlers.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 0e230246..4715502f 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -132,10 +132,10 @@ impl Index { pub async fn add_torrent( &self, add_torrent_req: AddTorrentRequest, - user_id: UserId, + maybe_user_id: Option, ) -> Result { self.authorization_service - .authorize(ACTION::AddTorrent, Some(user_id)) + .authorize(ACTION::AddTorrent, maybe_user_id) .await?; let metadata = self.validate_and_build_metadata(&add_torrent_req).await?; @@ -149,7 +149,7 @@ impl Index { let torrent_id = self .torrent_repository - .add(&original_info_hash, &torrent, &metadata, user_id) + .add(&original_info_hash, &torrent, &metadata, maybe_user_id.unwrap()) .await?; // Synchronous secondary tasks diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index ffdc6240..9534cc5c 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -37,7 +37,7 @@ use crate::web::api::server::v1::routes::API_VERSION_URL_PREFIX; #[allow(clippy::unused_async)] pub async fn upload_torrent_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, multipart: Multipart, ) -> Response { let add_torrent_form = match build_add_torrent_request_from_payload(multipart).await { @@ -45,7 +45,7 @@ pub async fn upload_torrent_handler( Err(error) => return error.into_response(), }; - match app_data.torrent_service.add_torrent(add_torrent_form, user_id).await { + match app_data.torrent_service.add_torrent(add_torrent_form, maybe_user_id).await { Ok(response) => new_torrent_response(&response).into_response(), Err(error) => error.into_response(), } From 42fb0317cfee03d03d46cc56bdc3188ab81ffaa4 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 3 Aug 2024 17:01:59 +0200 Subject: [PATCH 292/309] refactor: [#615] renamed error to be more human friendly --- src/errors.rs | 4 ++-- src/services/authorization.rs | 6 ++++-- src/services/torrent.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 00c79ecf..e1082e8c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -109,7 +109,7 @@ pub enum ServiceError { InvalidTag, #[display(fmt = "Unauthorized action.")] - Unauthorized, + UnauthorizedAction, #[display(fmt = "This torrent already exists in our database.")] InfoHashAlreadyExists, @@ -300,7 +300,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::MissingMandatoryMetadataFields => StatusCode::BAD_REQUEST, ServiceError::InvalidCategory => StatusCode::BAD_REQUEST, ServiceError::InvalidTag => StatusCode::BAD_REQUEST, - ServiceError::Unauthorized => StatusCode::FORBIDDEN, + ServiceError::UnauthorizedAction => StatusCode::FORBIDDEN, ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::CONFLICT, ServiceError::OriginalInfoHashAlreadyExists => StatusCode::CONFLICT, diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 34bf1369..fca7da07 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -79,12 +79,14 @@ impl Service { let enforcer = self.casbin_enforcer.enforcer.read().await; - let authorize = enforcer.enforce((role, action)).map_err(|_| ServiceError::Unauthorized)?; + let authorize = enforcer + .enforce((role, action)) + .map_err(|_| ServiceError::UnauthorizedAction)?; if authorize { Ok(()) } else { - Err(ServiceError::Unauthorized) + Err(ServiceError::UnauthorizedAction) } } diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 4715502f..3d0e1373 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -433,7 +433,7 @@ impl Index { // Check if user is owner or administrator // todo: move this to an authorization service. if !(torrent_listing.uploader == updater.username || updater.administrator) { - return Err(ServiceError::Unauthorized); + return Err(ServiceError::UnauthorizedAction); } self.torrent_info_repository From e527867ce6bfd09a1002d8c0bc9ed8de9d4e7a8b Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 3 Aug 2024 17:10:31 +0200 Subject: [PATCH 293/309] fix: [#615] adjusted http error code for test so it pass --- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index ed3b4f33..54fbf0b6 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -442,7 +442,7 @@ mod for_guests { let response = client.upload_torrent(form.into()).await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } #[tokio::test] From 00c293c86d9c58f3cf52cc54fc06a9f63a9c0471 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 3 Aug 2024 18:40:56 +0200 Subject: [PATCH 294/309] refactor: [#615] added missing error to function comments --- src/services/user.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/user.rs b/src/services/user.rs index 356cc164..3adf3aee 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -229,6 +229,7 @@ impl ProfileService { /// * `ServiceError::PasswordTooLong` if the supplied password is too long. /// * An error if unable to successfully hash the password. /// * An error if unable to change the password in the database. + /// * An error if it is not possible to authorize the action pub async fn change_password( &self, user_id: UserId, From dbcd0e1c6ab680c61526e1b1f3d5048422afef8b Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 3 Aug 2024 18:41:26 +0200 Subject: [PATCH 295/309] refactor: [#615] added missing error to handler comments --- src/web/api/server/v1/contexts/tag/handlers.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index b198fc7a..64026663 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -24,7 +24,10 @@ use crate::web::api::server::v1::responses::{self}; /// /// # Errors /// -/// It returns an error if there is a database error. +/// It returns an error if: +/// There is a database error +/// There is a problem authorizing the action. +/// The user is not authorized to perform the action #[allow(clippy::unused_async)] pub async fn get_all_handler( State(app_data): State>, From c22c9194729cf8c7b7b82ea29e325b8b5eed6a79 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 4 Aug 2024 12:56:53 +0200 Subject: [PATCH 296/309] refactor: [#615] change password handler and function now use optional user id --- src/services/user.rs | 10 +++++----- src/web/api/server/v1/contexts/user/handlers.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/services/user.rs b/src/services/user.rs index 3adf3aee..1e884c6a 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -230,23 +230,23 @@ impl ProfileService { /// * An error if unable to successfully hash the password. /// * An error if unable to change the password in the database. /// * An error if it is not possible to authorize the action + #[allow(clippy::missing_panics_doc)] pub async fn change_password( &self, - user_id: UserId, - change_password_form: &ChangePasswordForm, maybe_user_id: Option, + change_password_form: &ChangePasswordForm, ) -> Result<(), ServiceError> { self.authorization_service .authorize(ACTION::ChangePassword, maybe_user_id) .await?; - info!("changing user password for user ID: {user_id}"); + info!("changing user password for user ID: {}", maybe_user_id.unwrap()); let settings = self.configuration.settings.read().await; let user_authentication = self .user_authentication_repository - .get_user_authentication_from_id(&user_id) + .get_user_authentication_from_id(&maybe_user_id.unwrap()) .await?; verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?; @@ -265,7 +265,7 @@ impl ProfileService { let password_hash = hash_password(&change_password_form.password)?; self.user_authentication_repository - .change_password(user_id, &password_hash) + .change_password(maybe_user_id.unwrap(), &password_hash) .await?; Ok(()) diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index 440c598d..8674526f 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -132,19 +132,19 @@ pub async fn renew_token_handler( /// /// - The user account is not found. #[allow(clippy::unused_async)] +#[allow(clippy::missing_panics_doc)] pub async fn change_password_handler( State(app_data): State>, ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, extract::Json(change_password_form): extract::Json, ) -> Response { match app_data .profile_service - .change_password(user_id, &change_password_form, maybe_user_id) + .change_password(maybe_user_id, &change_password_form) .await { Ok(()) => Json(OkResponseData { - data: format!("Password changed for user with ID: {user_id}"), + data: format!("Password changed for user with ID: {}", maybe_user_id.unwrap()), }) .into_response(), Err(error) => error.into_response(), From 60101550ecaf5ef9f9b2a31e8737a17ddf1575fc Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 4 Aug 2024 17:53:53 +0200 Subject: [PATCH 297/309] refactor: [#615] handlers and functions now use optional user id --- src/services/category.rs | 8 ++++---- src/services/settings.rs | 8 ++++---- src/services/tag.rs | 10 ++++------ src/services/torrent.rs | 8 ++++++-- src/services/user.rs | 10 +++++++--- .../api/server/v1/contexts/category/handlers.rs | 17 ++++++++++++----- .../api/server/v1/contexts/settings/handlers.rs | 5 ++--- src/web/api/server/v1/contexts/tag/handlers.rs | 9 ++++----- .../api/server/v1/contexts/torrent/handlers.rs | 4 ++-- src/web/api/server/v1/contexts/user/handlers.rs | 5 ++--- 10 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/services/category.rs b/src/services/category.rs index 8c6bc062..6f40a024 100644 --- a/src/services/category.rs +++ b/src/services/category.rs @@ -31,9 +31,9 @@ impl Service { /// * The category name is empty. /// * The category already exists. /// * There is a database error. - pub async fn add_category(&self, category_name: &str, user_id: &UserId) -> Result { + pub async fn add_category(&self, category_name: &str, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::AddCategory, Some(*user_id)) + .authorize(ACTION::AddCategory, maybe_user_id) .await?; let trimmed_name = category_name.trim(); @@ -65,9 +65,9 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error. - pub async fn delete_category(&self, category_name: &str, user_id: &UserId) -> Result<(), ServiceError> { + pub async fn delete_category(&self, category_name: &str, maybe_user_id: Option) -> Result<(), ServiceError> { self.authorization_service - .authorize(ACTION::DeleteCategory, Some(*user_id)) + .authorize(ACTION::DeleteCategory, maybe_user_id) .await?; match self.category_repository.delete(category_name).await { diff --git a/src/services/settings.rs b/src/services/settings.rs index 565580ad..7578775d 100644 --- a/src/services/settings.rs +++ b/src/services/settings.rs @@ -30,9 +30,9 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_all(&self, user_id: &UserId) -> Result { + pub async fn get_all(&self, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetSettings, Some(*user_id)) + .authorize(ACTION::GetSettings, maybe_user_id) .await?; let torrust_index_configuration = self.configuration.get_all().await; @@ -45,9 +45,9 @@ impl Service { /// # Errors /// /// It returns an error if the user does not have the required permissions. - pub async fn get_all_masking_secrets(&self, user_id: &UserId) -> Result { + pub async fn get_all_masking_secrets(&self, maybe_user_id: Option) -> Result { self.authorization_service - .authorize(ACTION::GetSettingsSecret, Some(*user_id)) + .authorize(ACTION::GetSettingsSecret, maybe_user_id) .await?; let mut torrust_index_configuration = self.configuration.get_all().await; diff --git a/src/services/tag.rs b/src/services/tag.rs index d47b09b8..282fc5c2 100644 --- a/src/services/tag.rs +++ b/src/services/tag.rs @@ -29,8 +29,8 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error. - pub async fn add_tag(&self, tag_name: &str, user_id: &UserId) -> Result { - self.authorization_service.authorize(ACTION::AddTag, Some(*user_id)).await?; + pub async fn add_tag(&self, tag_name: &str, maybe_user_id: Option) -> Result { + self.authorization_service.authorize(ACTION::AddTag, maybe_user_id).await?; let trimmed_name = tag_name.trim(); @@ -55,10 +55,8 @@ impl Service { /// /// * The user does not have the required permissions. /// * There is a database error. - pub async fn delete_tag(&self, tag_id: &TagId, user_id: &UserId) -> Result<(), ServiceError> { - self.authorization_service - .authorize(ACTION::DeleteTag, Some(*user_id)) - .await?; + pub async fn delete_tag(&self, tag_id: &TagId, maybe_user_id: Option) -> Result<(), ServiceError> { + self.authorization_service.authorize(ACTION::DeleteTag, maybe_user_id).await?; match self.tag_repository.delete(tag_id).await { Ok(()) => Ok(()), diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 3d0e1373..00c38864 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -297,9 +297,13 @@ impl Index { /// * The user does not have permission to delete the torrent. /// * Unable to get the torrent listing from it's ID. /// * Unable to delete the torrent from the database. - pub async fn delete_torrent(&self, info_hash: &InfoHash, user_id: &UserId) -> Result { + pub async fn delete_torrent( + &self, + info_hash: &InfoHash, + maybe_user_id: Option, + ) -> Result { self.authorization_service - .authorize(ACTION::DeleteTorrent, Some(*user_id)) + .authorize(ACTION::DeleteTorrent, maybe_user_id) .await?; let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?; diff --git a/src/services/user.rs b/src/services/user.rs index 1e884c6a..587d0f0d 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -301,10 +301,14 @@ impl BanService { /// * `ServiceError::InternalServerError` if unable get user from the request. /// * An error if unable to get user profile from supplied username. /// * An error if unable to set the ban of the user in the database. - pub async fn ban_user(&self, username_to_be_banned: &str, user_id: &UserId) -> Result<(), ServiceError> { - debug!("user with ID {user_id} banning username: {username_to_be_banned}"); + #[allow(clippy::missing_panics_doc)] + pub async fn ban_user(&self, username_to_be_banned: &str, maybe_user_id: Option) -> Result<(), ServiceError> { + debug!( + "user with ID {} banning username: {username_to_be_banned}", + maybe_user_id.unwrap() + ); - self.authorization_service.authorize(ACTION::BanUser, Some(*user_id)).await?; + self.authorization_service.authorize(ACTION::BanUser, maybe_user_id).await?; let user_profile = self .user_profile_repository diff --git a/src/web/api/server/v1/contexts/category/handlers.rs b/src/web/api/server/v1/contexts/category/handlers.rs index 7a3d9425..2bfe7437 100644 --- a/src/web/api/server/v1/contexts/category/handlers.rs +++ b/src/web/api/server/v1/contexts/category/handlers.rs @@ -9,7 +9,6 @@ use super::forms::{AddCategoryForm, DeleteCategoryForm}; use super::responses::{added_category, deleted_category, Category}; use crate::common::AppData; use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; -use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the categories. @@ -50,10 +49,14 @@ pub async fn get_all_handler( #[allow(clippy::unused_async)] pub async fn add_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, extract::Json(category_form): extract::Json, ) -> Response { - match app_data.category_service.add_category(&category_form.name, &user_id).await { + match app_data + .category_service + .add_category(&category_form.name, maybe_user_id) + .await + { Ok(_) => added_category(&category_form.name).into_response(), Err(error) => error.into_response(), } @@ -70,14 +73,18 @@ pub async fn add_handler( #[allow(clippy::unused_async)] pub async fn delete_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, extract::Json(category_form): extract::Json, ) -> Response { // code-review: why do we need to send the whole category object to delete it? // And we should use the ID instead of the name, because the name could change // or we could add support for multiple languages. - match app_data.category_service.delete_category(&category_form.name, &user_id).await { + match app_data + .category_service + .delete_category(&category_form.name, maybe_user_id) + .await + { Ok(()) => deleted_category(&category_form.name).into_response(), Err(error) => error.into_response(), } diff --git a/src/web/api/server/v1/contexts/settings/handlers.rs b/src/web/api/server/v1/contexts/settings/handlers.rs index 7b3a0bd7..45ff79f8 100644 --- a/src/web/api/server/v1/contexts/settings/handlers.rs +++ b/src/web/api/server/v1/contexts/settings/handlers.rs @@ -7,7 +7,6 @@ use axum::response::{IntoResponse, Json, Response}; use crate::common::AppData; use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; -use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses; /// Get all settings. @@ -19,9 +18,9 @@ use crate::web::api::server::v1::responses; #[allow(clippy::unused_async)] pub async fn get_all_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { - let all_settings = match app_data.settings_service.get_all_masking_secrets(&user_id).await { + let all_settings = match app_data.settings_service.get_all_masking_secrets(maybe_user_id).await { Ok(all_settings) => all_settings, Err(error) => return error.into_response(), }; diff --git a/src/web/api/server/v1/contexts/tag/handlers.rs b/src/web/api/server/v1/contexts/tag/handlers.rs index 64026663..73410acc 100644 --- a/src/web/api/server/v1/contexts/tag/handlers.rs +++ b/src/web/api/server/v1/contexts/tag/handlers.rs @@ -9,7 +9,6 @@ use super::forms::{AddTagForm, DeleteTagForm}; use super::responses::{added_tag, deleted_tag}; use crate::common::AppData; use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; -use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::{self}; /// It handles the request to get all the tags. @@ -50,10 +49,10 @@ pub async fn get_all_handler( #[allow(clippy::unused_async)] pub async fn add_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, extract::Json(add_tag_form): extract::Json, ) -> Response { - match app_data.tag_service.add_tag(&add_tag_form.name, &user_id).await { + match app_data.tag_service.add_tag(&add_tag_form.name, maybe_user_id).await { Ok(_) => added_tag(&add_tag_form.name).into_response(), Err(error) => error.into_response(), } @@ -70,10 +69,10 @@ pub async fn add_handler( #[allow(clippy::unused_async)] pub async fn delete_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, extract::Json(delete_tag_form): extract::Json, ) -> Response { - match app_data.tag_service.delete_tag(&delete_tag_form.tag_id, &user_id).await { + match app_data.tag_service.delete_tag(&delete_tag_form.tag_id, maybe_user_id).await { Ok(()) => deleted_tag(delete_tag_form.tag_id).into_response(), Err(error) => error.into_response(), } diff --git a/src/web/api/server/v1/contexts/torrent/handlers.rs b/src/web/api/server/v1/contexts/torrent/handlers.rs index 9534cc5c..4e66dd8a 100644 --- a/src/web/api/server/v1/contexts/torrent/handlers.rs +++ b/src/web/api/server/v1/contexts/torrent/handlers.rs @@ -256,14 +256,14 @@ pub async fn update_torrent_info_handler( #[allow(clippy::unused_async)] pub async fn delete_torrent_handler( State(app_data): State>, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, Path(info_hash): Path, ) -> Response { let Ok(info_hash) = InfoHash::from_str(&info_hash.lowercase()) else { return errors::Request::InvalidInfoHashParam.into_response(); }; - match app_data.torrent_service.delete_torrent(&info_hash, &user_id).await { + match app_data.torrent_service.delete_torrent(&info_hash, maybe_user_id).await { Ok(deleted_torrent_response) => Json(OkResponseData { data: deleted_torrent_response, }) diff --git a/src/web/api/server/v1/contexts/user/handlers.rs b/src/web/api/server/v1/contexts/user/handlers.rs index 8674526f..e22ed325 100644 --- a/src/web/api/server/v1/contexts/user/handlers.rs +++ b/src/web/api/server/v1/contexts/user/handlers.rs @@ -11,7 +11,6 @@ use super::forms::{ChangePasswordForm, JsonWebToken, LoginForm, RegistrationForm use super::responses::{self}; use crate::common::AppData; use crate::web::api::server::v1::extractors::optional_user_id::ExtractOptionalLoggedInUser; -use crate::web::api::server::v1::extractors::user_id::ExtractLoggedInUser; use crate::web::api::server::v1::responses::OkResponseData; // Registration @@ -163,11 +162,11 @@ pub async fn change_password_handler( pub async fn ban_handler( State(app_data): State>, Path(to_be_banned_username): Path, - ExtractLoggedInUser(user_id): ExtractLoggedInUser, + ExtractOptionalLoggedInUser(maybe_user_id): ExtractOptionalLoggedInUser, ) -> Response { // todo: add reason and `date_expiry` parameters to request - match app_data.ban_service.ban_user(&to_be_banned_username.0, &user_id).await { + match app_data.ban_service.ban_user(&to_be_banned_username.0, maybe_user_id).await { Ok(()) => Json(OkResponseData { data: format!("Banned user: {}", to_be_banned_username.0), }) From 0360517d61c6ff0b8569927c406384a4246cb1c8 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 4 Aug 2024 19:40:49 +0200 Subject: [PATCH 298/309] refactor: [#615] changed status code so tests pass --- tests/e2e/web/api/v1/contexts/category/contract.rs | 4 ++-- tests/e2e/web/api/v1/contexts/tag/contract.rs | 4 ++-- tests/e2e/web/api/v1/contexts/user/contract.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/web/api/v1/contexts/category/contract.rs b/tests/e2e/web/api/v1/contexts/category/contract.rs index b4775bd2..c8ce33df 100644 --- a/tests/e2e/web/api/v1/contexts/category/contract.rs +++ b/tests/e2e/web/api/v1/contexts/category/contract.rs @@ -61,7 +61,7 @@ async fn it_should_not_allow_adding_a_new_category_to_unauthenticated_users() { }) .await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } #[tokio::test] @@ -194,5 +194,5 @@ async fn it_should_not_allow_guests_to_delete_categories() { }) .await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } diff --git a/tests/e2e/web/api/v1/contexts/tag/contract.rs b/tests/e2e/web/api/v1/contexts/tag/contract.rs index 77771d49..42f42bd8 100644 --- a/tests/e2e/web/api/v1/contexts/tag/contract.rs +++ b/tests/e2e/web/api/v1/contexts/tag/contract.rs @@ -63,7 +63,7 @@ async fn it_should_not_allow_adding_a_new_tag_to_unauthenticated_users() { }) .await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } #[tokio::test] @@ -174,5 +174,5 @@ async fn it_should_not_allow_guests_to_delete_tags() { let response = client.delete_tag(DeleteTagForm { tag_id }).await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } diff --git a/tests/e2e/web/api/v1/contexts/user/contract.rs b/tests/e2e/web/api/v1/contexts/user/contract.rs index 3124fc28..e83e796b 100644 --- a/tests/e2e/web/api/v1/contexts/user/contract.rs +++ b/tests/e2e/web/api/v1/contexts/user/contract.rs @@ -231,6 +231,6 @@ mod banned_user_list { let response = client.ban_user(Username::new(registered_user.username.clone())).await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } } From d8b3ee2c647f0be872ba848d382f106811ecbeae Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 4 Aug 2024 22:56:04 +0200 Subject: [PATCH 299/309] refactor: [#615] change test http return code --- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 54fbf0b6..fed3d3ed 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -462,7 +462,7 @@ mod for_guests { let response = client.delete_torrent(&test_torrent.file_info_hash()).await; - assert_eq!(response.status, 401); + assert_eq!(response.status, 403); } } From 394a7057b82a01e3410f4f40c21658a35b80e852 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Aug 2024 13:00:09 +0100 Subject: [PATCH 300/309] chore(deps): update depedencies ```console cargo update Updating crates.io index Locking 43 packages to latest compatible versions Adding aws-lc-rs v1.8.1 Adding aws-lc-sys v0.20.1 Updating axum-server v0.6.0 -> v0.7.1 Adding bindgen v0.69.4 Updating bytemuck v1.16.1 -> v1.16.3 Updating bytes v1.6.1 -> v1.7.1 Updating cc v1.1.6 -> v1.1.7 Adding cexpr v0.6.0 Adding clang-sys v1.8.1 Updating clap v4.5.11 -> v4.5.13 Updating clap_builder v4.5.11 -> v4.5.13 Updating clap_derive v4.5.11 -> v4.5.13 Adding cmake v0.1.50 Adding dunce v1.0.5 Updating email_address v0.2.7 -> v0.2.9 Updating flate2 v1.0.30 -> v1.0.31 Adding fs_extra v1.3.0 Adding glob v0.3.1 Updating indexmap v2.2.6 -> v2.3.0 Adding itertools v0.12.1 (latest: v0.13.0) Adding lazycell v1.3.0 Adding libloading v0.8.5 Adding mirai-annotations v1.12.0 Updating ppv-lite86 v0.2.17 -> v0.2.20 Adding prettyplease v0.2.20 Updating regex v1.10.5 -> v1.10.6 Updating rgb v0.8.45 -> v0.8.47 Adding rustc-hash v1.1.0 (latest: v2.0.0) Removing rustls v0.21.12 Updating rustls-pemfile v2.1.2 -> v2.1.3 Removing rustls-webpki v0.101.7 Removing sct v0.7.1 Updating serde_json v1.0.121 -> v1.0.122 Adding shlex v1.3.0 Updating tempfile v3.10.1 -> v3.11.0 Removing tokio-rustls v0.24.1 Updating toml v0.8.16 -> v0.8.19 Updating toml_datetime v0.6.7 -> v0.6.8 Updating toml_edit v0.22.17 -> v0.22.20 Removing which v6.0.1 Adding which v4.4.2 (latest: v6.0.2) Adding which v6.0.2 Updating winapi-util v0.1.8 -> v0.1.9 Adding windows-sys v0.59.0 Updating winnow v0.6.16 -> v0.6.18 Adding zeroize_derive v1.4.2 Updating zstd-safe v7.2.0 -> v7.2.1 ``` --- Cargo.lock | 363 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 257 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe9cccfa..44588b13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,6 +235,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-lc-rs" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "axum" version = "0.7.5" @@ -293,9 +320,9 @@ dependencies = [ [[package]] name = "axum-server" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad46c3ec4e12f4a4b6835e173ba21c25e484c9d02b49770bf006ce5367c036" +checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8" dependencies = [ "arc-swap", "bytes", @@ -306,10 +333,11 @@ dependencies = [ "hyper", "hyper-util", "pin-project-lite", - "rustls 0.21.12", + "rustls", "rustls-pemfile", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tower", "tower-service", ] @@ -359,6 +387,29 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.72", + "which 4.4.2", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -431,9 +482,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "byteorder" @@ -443,9 +494,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "camino" @@ -478,14 +529,23 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" dependencies = [ "jobserver", "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -515,11 +575,22 @@ dependencies = [ "stacker", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -527,9 +598,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -539,9 +610,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -555,6 +626,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.2" @@ -824,6 +904,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.13.0" @@ -845,9 +931,9 @@ dependencies = [ [[package]] name = "email_address" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46b7a0ac6570e31bfe2c6cf575a576a55af9893d1a6b30b4444e6e90b216bb84" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" dependencies = [ "serde", ] @@ -947,9 +1033,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -1019,6 +1105,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -1148,6 +1240,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "globset" version = "0.4.14" @@ -1184,7 +1282,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.2.6", + "indexmap 2.3.0", "slab", "tokio", "tokio-util", @@ -1358,10 +1456,10 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", ] @@ -1469,9 +1567,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -1505,6 +1603,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1568,6 +1675,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lettre" version = "0.11.7" @@ -1590,14 +1703,14 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls 0.23.12", + "rustls", "rustls-pemfile", "serde", "serde_json", "socket2", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.0", + "tokio-rustls", "url", "uuid", "webpki-roots", @@ -1609,6 +1722,16 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" @@ -1729,6 +1852,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "mockall" version = "0.12.1" @@ -2096,7 +2225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.3.0", ] [[package]] @@ -2185,9 +2314,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -2215,6 +2347,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.72", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -2317,9 +2459,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -2405,9 +2547,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.45" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" +checksum = "e12bc8d2f72df26a5d3178022df33720fbede0d31d82c7291662eff89836994d" dependencies = [ "bytemuck", ] @@ -2501,6 +2643,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2523,38 +2671,27 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -2566,22 +2703,13 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2648,16 +2776,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -2728,9 +2846,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", "memchr", @@ -2779,7 +2897,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_derive", "serde_json", @@ -2841,6 +2959,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2996,7 +3120,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.2.6", + "indexmap 2.3.0", "log", "memchr", "native-tls", @@ -3269,12 +3393,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] @@ -3474,23 +3599,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", ] @@ -3521,9 +3636,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -3533,20 +3648,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.17" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_spanned", "toml_datetime", @@ -3579,7 +3694,7 @@ dependencies = [ "http-body", "hyper", "hyper-util", - "indexmap 2.2.6", + "indexmap 2.3.0", "jsonwebtoken", "lazy_static", "lettre", @@ -3616,7 +3731,7 @@ dependencies = [ "url", "urlencoding", "uuid", - "which", + "which 6.0.2", ] [[package]] @@ -4117,9 +4232,21 @@ dependencies = [ [[package]] name = "which" -version = "6.0.1" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "which" +version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" dependencies = [ "either", "home", @@ -4155,11 +4282,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4205,6 +4332,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4328,9 +4464,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -4381,6 +4517,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -4400,6 +4537,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] [[package]] name = "zstd" @@ -4412,18 +4563,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", From f38b62817f2519652354925252e1e0199bd7a53d Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 5 Aug 2024 18:02:50 +0200 Subject: [PATCH 301/309] refactor: [#615] new authorization error for guest users --- src/errors.rs | 6 ++++++ src/services/authorization.rs | 4 +++- tests/e2e/web/api/v1/contexts/category/contract.rs | 4 ++-- tests/e2e/web/api/v1/contexts/tag/contract.rs | 4 ++-- tests/e2e/web/api/v1/contexts/torrent/contract.rs | 4 ++-- tests/e2e/web/api/v1/contexts/user/contract.rs | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index e1082e8c..276d9a8d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -111,6 +111,11 @@ pub enum ServiceError { #[display(fmt = "Unauthorized action.")] UnauthorizedAction, + #[display( + fmt = "Unauthorized actions for guest users. Try logging in to check if you have permission to perform the action" + )] + UnauthorizedActionForGuests, + #[display(fmt = "This torrent already exists in our database.")] InfoHashAlreadyExists, @@ -301,6 +306,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::InvalidCategory => StatusCode::BAD_REQUEST, ServiceError::InvalidTag => StatusCode::BAD_REQUEST, ServiceError::UnauthorizedAction => StatusCode::FORBIDDEN, + ServiceError::UnauthorizedActionForGuests => StatusCode::UNAUTHORIZED, ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::CONFLICT, ServiceError::OriginalInfoHashAlreadyExists => StatusCode::CONFLICT, diff --git a/src/services/authorization.rs b/src/services/authorization.rs index fca7da07..51d2a0d7 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -80,11 +80,13 @@ impl Service { let enforcer = self.casbin_enforcer.enforcer.read().await; let authorize = enforcer - .enforce((role, action)) + .enforce((&role, action)) .map_err(|_| ServiceError::UnauthorizedAction)?; if authorize { Ok(()) + } else if role == UserRole::Guest { + Err(ServiceError::UnauthorizedActionForGuests) } else { Err(ServiceError::UnauthorizedAction) } diff --git a/tests/e2e/web/api/v1/contexts/category/contract.rs b/tests/e2e/web/api/v1/contexts/category/contract.rs index c8ce33df..b4775bd2 100644 --- a/tests/e2e/web/api/v1/contexts/category/contract.rs +++ b/tests/e2e/web/api/v1/contexts/category/contract.rs @@ -61,7 +61,7 @@ async fn it_should_not_allow_adding_a_new_category_to_unauthenticated_users() { }) .await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } #[tokio::test] @@ -194,5 +194,5 @@ async fn it_should_not_allow_guests_to_delete_categories() { }) .await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } diff --git a/tests/e2e/web/api/v1/contexts/tag/contract.rs b/tests/e2e/web/api/v1/contexts/tag/contract.rs index 42f42bd8..77771d49 100644 --- a/tests/e2e/web/api/v1/contexts/tag/contract.rs +++ b/tests/e2e/web/api/v1/contexts/tag/contract.rs @@ -63,7 +63,7 @@ async fn it_should_not_allow_adding_a_new_tag_to_unauthenticated_users() { }) .await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } #[tokio::test] @@ -174,5 +174,5 @@ async fn it_should_not_allow_guests_to_delete_tags() { let response = client.delete_tag(DeleteTagForm { tag_id }).await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index fed3d3ed..ed3b4f33 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -442,7 +442,7 @@ mod for_guests { let response = client.upload_torrent(form.into()).await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } #[tokio::test] @@ -462,7 +462,7 @@ mod for_guests { let response = client.delete_torrent(&test_torrent.file_info_hash()).await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } } diff --git a/tests/e2e/web/api/v1/contexts/user/contract.rs b/tests/e2e/web/api/v1/contexts/user/contract.rs index e83e796b..3124fc28 100644 --- a/tests/e2e/web/api/v1/contexts/user/contract.rs +++ b/tests/e2e/web/api/v1/contexts/user/contract.rs @@ -231,6 +231,6 @@ mod banned_user_list { let response = client.ban_user(Username::new(registered_user.username.clone())).await; - assert_eq!(response.status, 403); + assert_eq!(response.status, 401); } } From c1a5c25fd57446f9cf5ea1fd92a15f26e8eed4ec Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Aug 2024 11:12:27 +0100 Subject: [PATCH 302/309] feat: [#702] allow overwriting casbin configuration This is an unsatble feature. You can overwrite casbin configuration to change permissions for roles: guest, registered and admin. You can do it by adding this toml file config section: ```toml [unstable.auth.casbin] model = """ [request_definition] r = role, action [policy_definition] p = role, action [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.role == p.role && r.action == p.action """ policy = """ admin, GetAboutPage admin, GetLicensePage admin, AddCategory admin, DeleteCategory admin, GetCategories admin, GetImageByUrl admin, GetSettings admin, GetSettingsSecret admin, GetPublicSettings admin, AddTag admin, DeleteTag admin, GetTags admin, AddTorrent admin, GetTorrent admin, DeleteTorrent admin, GetTorrentInfo admin, GenerateTorrentInfoListing admin, GetCanonicalInfoHash admin, ChangePassword admin, BanUser registered, GetAboutPage registered, GetLicensePage registered, GetCategories registered, GetImageByUrl registered, GetPublicSettings registered, GetTags registered, AddTorrent registered, GetTorrent registered, GetTorrentInfo registered, GenerateTorrentInfoListing registered, GetCanonicalInfoHash registered, ChangePassword guest, GetAboutPage guest, GetLicensePage guest, GetCategories guest, GetPublicSettings guest, GetTags guest, GetTorrent guest, GetTorrentInfo guest, GenerateTorrentInfoListing guest, GetCanonicalInfoHash """ ``` For example, if you wnat to force users to login to see the torrent list you can remove the following line from the policy: ``` guest, GenerateTorrentInfoListing ``` NOTICE: This is an unstable feature. It will panic with wrong casbin configuration, invalid roles, etcetera. --- project-words.txt | 2 + src/app.rs | 17 +++++++- src/config/v2/mod.rs | 11 +++++ src/config/v2/unstable.rs | 55 ++++++++++++++++++++++++ src/services/authorization.rs | 78 +++++++++++++++++++++++++++-------- 5 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 src/config/v2/unstable.rs diff --git a/project-words.txt b/project-words.txt index c6dbbf95..01908fce 100644 --- a/project-words.txt +++ b/project-words.txt @@ -9,6 +9,7 @@ binascii btih buildx camino +Casbin chrono clippy codecov @@ -51,6 +52,7 @@ luckythelab mailcatcher mandelbrotset metainfo +Mgmt migth nanos NCCA diff --git a/src/app.rs b/src/app.rs index ab9a2066..bba7dac2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,7 @@ use crate::config::validator::Validator; use crate::config::Configuration; use crate::databases::database; use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service}; +use crate::services::authorization::{CasbinConfiguration, CasbinEnforcer}; use crate::services::category::{self, DbCategoryRepository}; use crate::services::tag::{self, DbTagRepository}; use crate::services::torrent::{ @@ -62,6 +63,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running // From [net] config let config_bind_address = settings.net.bind_address; let opt_net_tsl = settings.net.tsl.clone(); + // Unstable config + let unstable = settings.unstable.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that // leads to requests hanging. @@ -87,7 +90,19 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let torrent_tag_repository = Arc::new(DbTorrentTagRepository::new(database.clone())); let torrent_listing_generator = Arc::new(DbTorrentListingGenerator::new(database.clone())); let banned_user_list = Arc::new(DbBannedUserList::new(database.clone())); - let casbin_enforcer = Arc::new(authorization::CasbinEnforcer::new().await); + let casbin_enforcer = Arc::new( + if let Some(casbin) = unstable + .as_ref() + .and_then(|u| u.auth.as_ref()) + .and_then(|auth| auth.casbin.as_ref()) + { + println!("loading custom"); + CasbinEnforcer::with_configuration(CasbinConfiguration::new(&casbin.model, &casbin.policy)).await + } else { + println!("loading default"); + CasbinEnforcer::with_default_configuration().await + }, + ); // Services let authorization_service = Arc::new(authorization::Service::new(user_repository.clone(), casbin_enforcer.clone())); diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index e5519f56..cf8d185c 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -8,11 +8,13 @@ pub mod net; pub mod registration; pub mod tracker; pub mod tracker_statistics_importer; +pub mod unstable; pub mod website; use logging::Logging; use registration::Registration; use serde::{Deserialize, Serialize}; +use unstable::Unstable; use self::api::Api; use self::auth::{Auth, ClaimTokenPepper}; @@ -76,6 +78,10 @@ pub struct Settings { /// The tracker statistics importer job configuration. #[serde(default = "Settings::default_tracker_statistics_importer")] pub tracker_statistics_importer: TrackerStatisticsImporter, + + /// The unstable configuration. + #[serde(default = "Settings::default_unstable")] + pub unstable: Option, } impl Default for Settings { @@ -93,6 +99,7 @@ impl Default for Settings { api: Self::default_api(), registration: Self::default_registration(), tracker_statistics_importer: Self::default_tracker_statistics_importer(), + unstable: Self::default_unstable(), } } } @@ -174,6 +181,10 @@ impl Settings { fn default_tracker_statistics_importer() -> TrackerStatisticsImporter { TrackerStatisticsImporter::default() } + + fn default_unstable() -> Option { + None + } } impl Validator for Settings { diff --git a/src/config/v2/unstable.rs b/src/config/v2/unstable.rs new file mode 100644 index 00000000..437c033a --- /dev/null +++ b/src/config/v2/unstable.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Serialize}; + +/// Unstable configuration options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Unstable { + /// The casbin configuration used for authorization. + #[serde(default = "Unstable::default_auth")] + pub auth: Option, +} + +impl Default for Unstable { + fn default() -> Self { + Self { + auth: Self::default_auth(), + } + } +} + +impl Unstable { + fn default_auth() -> Option { + None + } +} + +/// Unstable auth configuration options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Auth { + /// The casbin configuration used for authorization. + #[serde(default = "Auth::default_casbin")] + pub casbin: Option, +} + +impl Default for Auth { + fn default() -> Self { + Self { + casbin: Self::default_casbin(), + } + } +} + +impl Auth { + fn default_casbin() -> Option { + None + } +} + +/// Authentication options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Casbin { + /// The model. See . + pub model: String, + + /// The policy. See . + pub policy: String, +} diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 51d2a0d7..dcca38f8 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -132,40 +132,84 @@ pub struct CasbinEnforcer { impl CasbinEnforcer { /// # Panics /// - /// It panics if the policy and/or model file cannot be loaded - pub async fn new() -> Self { - let casbin_configuration = CasbinConfiguration::new(); + /// Will panic if: + /// + /// - The enforcer can't be created. + /// - The policies can't be loaded. + pub async fn with_default_configuration() -> Self { + let casbin_configuration = CasbinConfiguration::default(); - let model = DefaultModel::from_str(&casbin_configuration.model) + let mut enforcer = Enforcer::new(casbin_configuration.default_model().await, ()) .await - .expect("Error loading the model"); + .expect("Error creating the enforcer"); - // Converts the policy from a string type to a vector - let policy = casbin_configuration - .policy - .lines() - .filter(|line| !line.trim().is_empty()) - .map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::>()) - .collect(); + enforcer + .add_policies(casbin_configuration.policy_lines()) + .await + .expect("Error loading the policy"); + + let enforcer = Arc::new(RwLock::new(enforcer)); + + Self { enforcer } + } - let mut enforcer = Enforcer::new(model, ()).await.expect("Error creating the enforcer"); + /// # Panics + /// + /// Will panic if: + /// + /// - The enforcer can't be created. + /// - The policies can't be loaded. + pub async fn with_configuration(casbin_configuration: CasbinConfiguration) -> Self { + let mut enforcer = Enforcer::new(casbin_configuration.default_model().await, ()) + .await + .expect("Error creating the enforcer"); - enforcer.add_policies(policy).await.expect("Error loading the policy"); + enforcer + .add_policies(casbin_configuration.policy_lines()) + .await + .expect("Error loading the policy"); let enforcer = Arc::new(RwLock::new(enforcer)); Self { enforcer } } } + #[allow(dead_code)] -struct CasbinConfiguration { +pub struct CasbinConfiguration { model: String, policy: String, } impl CasbinConfiguration { - pub fn new() -> Self { - CasbinConfiguration { + #[must_use] + pub fn new(model: &str, policy: &str) -> Self { + Self { + model: model.to_owned(), + policy: policy.to_owned(), + } + } + + /// # Panics + /// + /// It panics if the model cannot be loaded. + async fn default_model(&self) -> DefaultModel { + DefaultModel::from_str(&self.model).await.expect("Error loading the model") + } + + /// Converts the policy from a string type to a vector. + fn policy_lines(&self) -> Vec> { + self.policy + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::>()) + .collect() + } +} + +impl Default for CasbinConfiguration { + fn default() -> Self { + Self { model: String::from( " [request_definition] From 12316b1c44c7e459dcaf301110a5efa3b4b6c2a0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Aug 2024 11:48:13 +0100 Subject: [PATCH 303/309] chore: remove debugging code --- src/app.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index bba7dac2..9ae58cd8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -96,10 +96,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running .and_then(|u| u.auth.as_ref()) .and_then(|auth| auth.casbin.as_ref()) { - println!("loading custom"); CasbinEnforcer::with_configuration(CasbinConfiguration::new(&casbin.model, &casbin.policy)).await } else { - println!("loading default"); CasbinEnforcer::with_default_configuration().await }, ); From 04c31996361dedd04872d99e6c86287588cb45fb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Aug 2024 11:06:15 +0100 Subject: [PATCH 304/309] chore(deps): update dependencies ```console cargo update Updating crates.io index Locking 15 packages to latest compatible versions Updating cc v1.1.7 -> v1.1.10 Updating clap v4.5.13 -> v4.5.15 Updating clap_builder v4.5.13 -> v4.5.15 Updating core-foundation-sys v0.8.6 -> v0.8.7 Updating hyper-util v0.1.6 -> v0.1.7 Updating mio v1.0.1 -> v1.0.2 Updating object v0.36.2 -> v0.36.3 Updating rgb v0.8.47 -> v0.8.48 Updating rustls-pki-types v1.7.0 -> v1.8.0 Updating serde v1.0.204 -> v1.0.206 Updating serde_derive v1.0.204 -> v1.0.206 Updating serde_json v1.0.122 -> v1.0.124 Updating syn v2.0.72 -> v2.0.74 Updating tempfile v3.11.0 -> v3.12.0 Updating xml-rs v0.8.20 -> v0.8.21 ``` --- Cargo.lock | 114 ++++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44588b13..01ae29f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -406,7 +406,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.74", "which 4.4.2", ] @@ -529,9 +529,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.7" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" dependencies = [ "jobserver", "libc", @@ -588,9 +588,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -598,9 +598,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -617,7 +617,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -812,7 +812,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -823,7 +823,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -877,7 +877,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1178,7 +1178,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1481,9 +1481,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-channel", @@ -1842,9 +1842,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", @@ -1882,7 +1882,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2004,9 +2004,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -2040,7 +2040,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2145,7 +2145,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2204,7 +2204,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2251,7 +2251,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2354,7 +2354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2374,7 +2374,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "version_check", "yansi", ] @@ -2547,9 +2547,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12bc8d2f72df26a5d3178022df33720fbede0d31d82c7291662eff89836994d" +checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" dependencies = [ "bytemuck", ] @@ -2580,7 +2580,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2699,9 +2699,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" @@ -2807,9 +2807,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] @@ -2835,20 +2835,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", @@ -2914,7 +2914,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3150,7 +3150,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3173,7 +3173,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.72", + "syn 2.0.74", "tempfile", "tokio", "url", @@ -3349,9 +3349,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -3393,15 +3393,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3479,7 +3479,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3586,7 +3586,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3824,7 +3824,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4166,7 +4166,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-shared", ] @@ -4200,7 +4200,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4489,9 +4489,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" [[package]] name = "xmlparser" @@ -4529,7 +4529,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4549,7 +4549,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] From 6cb94c4628881a27751ec61cd5eddb789e257640 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Aug 2024 11:14:01 +0100 Subject: [PATCH 305/309] fix: linter errors --- src/models/torrent_file.rs | 22 ++++++------- src/services/user.rs | 27 ++++++++-------- src/web/api/server/mod.rs | 51 +++++++++++++++---------------- tests/environments/app_starter.rs | 9 ++---- 4 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index df88b350..663084f2 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -80,21 +80,19 @@ impl Torrent { torrent_nodes: Vec<(String, i64)>, ) -> Self { let pieces_or_root_hash = if db_torrent.is_bep_30 == 0 { - match &db_torrent.pieces { - Some(pieces) => pieces.clone(), - None => { - error!("Invalid torrent #{}. Null `pieces` in database", db_torrent.torrent_id); - String::new() - } + if let Some(pieces) = &db_torrent.pieces { + pieces.clone() + } else { + error!("Invalid torrent #{}. Null `pieces` in database", db_torrent.torrent_id); + String::new() } } else { // A BEP-30 torrent - match &db_torrent.root_hash { - Some(root_hash) => root_hash.clone(), - None => { - error!("Invalid torrent #{}. Null `root_hash` in database", db_torrent.torrent_id); - String::new() - } + if let Some(root_hash) = &db_torrent.root_hash { + root_hash.clone() + } else { + error!("Invalid torrent #{}. Null `root_hash` in database", db_torrent.torrent_id); + String::new() } }; diff --git a/src/services/user.rs b/src/services/user.rs index 587d0f0d..ebe3e4a7 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -134,24 +134,21 @@ impl RegistrationService { drop(self.user_repository.grant_admin_role(&user_id).await); } - match ®istration.email { - Some(email) => { - if email.verification_required { - // Email verification is enabled - if let Some(email) = opt_email { - let mail_res = self - .mailer - .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) - .await; - - if mail_res.is_err() { - drop(self.user_repository.delete(&user_id).await); - return Err(ServiceError::FailedToSendVerificationEmail); - } + if let Some(email) = ®istration.email { + if email.verification_required { + // Email verification is enabled + if let Some(email) = opt_email { + let mail_res = self + .mailer + .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url) + .await; + + if mail_res.is_err() { + drop(self.user_repository.delete(&user_id).await); + return Err(ServiceError::FailedToSendVerificationEmail); } } } - None => (), } Ok(user_id) diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index dee44ca1..edaa6a86 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -126,33 +126,30 @@ pub enum Error { } pub async fn make_rust_tls(tsl_config: &Option) -> Option> { - match tsl_config { - Some(tsl) => { - if let (Some(cert), Some(key)) = (tsl.ssl_cert_path.clone(), tsl.ssl_key_path.clone()) { - info!("Using https. Cert path: {cert}."); - info!("Using https. Key path: {key}."); - - let ssl_cert_path = cert.clone().to_string(); - let ssl_key_path = key.clone().to_string(); - - Some( - RustlsConfig::from_pem_file(cert, key) - .await - .map_err(|err| Error::BadTlsConfig { - source: (Arc::new(err) as DynError).into(), - ssl_cert_path, - ssl_key_path, - }), - ) - } else { - Some(Err(Error::MissingTlsConfig { - location: Location::caller(), - })) - } - } - None => { - info!("TLS not enabled"); - None + if let Some(tsl) = tsl_config { + if let (Some(cert), Some(key)) = (tsl.ssl_cert_path.clone(), tsl.ssl_key_path.clone()) { + info!("Using https. Cert path: {cert}."); + info!("Using https. Key path: {key}."); + + let ssl_cert_path = cert.clone().to_string(); + let ssl_key_path = key.clone().to_string(); + + Some( + RustlsConfig::from_pem_file(cert, key) + .await + .map_err(|err| Error::BadTlsConfig { + source: (Arc::new(err) as DynError).into(), + ssl_cert_path, + ssl_key_path, + }), + ) + } else { + Some(Err(Error::MissingTlsConfig { + location: Location::caller(), + })) } + } else { + info!("TLS not enabled"); + None } } diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index 921418d0..adf18589 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -68,12 +68,9 @@ impl AppStarter { } pub fn stop(&mut self) { - match &self.running_state { - Some(running_state) => { - running_state.app_handle.abort(); - self.running_state = None; - } - None => {} + if let Some(running_state) = &self.running_state { + running_state.app_handle.abort(); + self.running_state = None; } } From 7b3cc13f6d978e3471a8e90b6d04f8583cd76217 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Aug 2024 12:59:38 +0100 Subject: [PATCH 306/309] fix: [710] make secrets in config options mandatory - `tracker.token` - `auth.user_claim_token_pepper` It keeps the default for the SMPT server credential ebcuase they are not always needed. - `mail.smpt.credentials.password` --- .../default/config/index.container.mysql.toml | 6 ++++++ .../config/index.container.sqlite3.toml | 6 ++++++ .../config/index.development.sqlite3.toml | 6 ++++++ .../index.private.e2e.container.sqlite3.toml | 6 +++++- .../index.public.e2e.container.mysql.toml | 6 +++++- .../index.public.e2e.container.sqlite3.toml | 6 +++++- ...tracker.private.e2e.container.sqlite3.toml | 7 ++++++- .../tracker.public.e2e.container.sqlite3.toml | 6 ++++++ src/config/mod.rs | 19 ++++++++++++++++++- 9 files changed, 63 insertions(+), 5 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 1c865ab8..9bc28f58 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -11,6 +11,12 @@ threshold = "info" #threshold = "debug" #threshold = "trace" +[tracker] +token = "MyAccessToken" + +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 340c29fa..ab223343 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -11,6 +11,12 @@ threshold = "info" #threshold = "debug" #threshold = "trace" +[tracker] +token = "MyAccessToken" + +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [database] connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 6998613e..4b4af3aa 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -11,6 +11,12 @@ threshold = "info" #threshold = "debug" #threshold = "trace" +[tracker] +token = "MyAccessToken" + +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + # Uncomment if you want to enable TSL for development #[net.tsl] #ssl_cert_path = "./storage/index/lib/tls/localhost.crt" diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index fdf43695..608bd419 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -15,8 +15,12 @@ threshold = "info" api_url = "http://tracker:1212" listed = false private = true +token = "MyAccessToken" url = "http://tracker:7070" +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" @@ -25,4 +29,4 @@ port = 1025 server = "mailcatcher" [registration] -[registration.email] \ No newline at end of file +[registration.email] diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 2d429d70..c6b4550e 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -13,8 +13,12 @@ threshold = "info" [tracker] api_url = "http://tracker:1212" +token = "MyAccessToken" url = "udp://tracker:6969" +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" @@ -23,4 +27,4 @@ port = 1025 server = "mailcatcher" [registration] -[registration.email] \ No newline at end of file +[registration.email] diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 4e364fb8..1b807154 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -13,8 +13,12 @@ threshold = "info" [tracker] api_url = "http://tracker:1212" +token = "MyAccessToken" url = "udp://tracker:6969" +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" @@ -23,4 +27,4 @@ port = 1025 server = "mailcatcher" [registration] -[registration.email] \ No newline at end of file +[registration.email] diff --git a/share/default/config/tracker.private.e2e.container.sqlite3.toml b/share/default/config/tracker.private.e2e.container.sqlite3.toml index 028f1aa6..647d5cee 100644 --- a/share/default/config/tracker.private.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.private.e2e.container.sqlite3.toml @@ -4,6 +4,12 @@ schema_version = "2.0.0" [logging] threshold = "info" +[tracker] +token = "MyAccessToken" + +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [core] listed = false private = true @@ -17,4 +23,3 @@ bind_address = "0.0.0.0:6969" [http_api] bind_address = "0.0.0.0:1212" - diff --git a/share/default/config/tracker.public.e2e.container.sqlite3.toml b/share/default/config/tracker.public.e2e.container.sqlite3.toml index 1b93d36a..e3f73d0b 100644 --- a/share/default/config/tracker.public.e2e.container.sqlite3.toml +++ b/share/default/config/tracker.public.e2e.container.sqlite3.toml @@ -4,6 +4,12 @@ schema_version = "2.0.0" [logging] threshold = "info" +[tracker] +token = "MyAccessToken" + +[auth] +user_claim_token_pepper = "MaxVerstappenWC2021" + [core] listed = false private = false diff --git a/src/config/mod.rs b/src/config/mod.rs index 5dfef8f0..a5935242 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -340,7 +340,12 @@ impl Configuration { /// Will return an error if a mandatory configuration option is only /// obtained by default value (code), meaning the user hasn't overridden it. fn check_mandatory_options(figment: &Figment) -> Result<(), Error> { - let mandatory_options = ["metadata.schema_version", "logging.threshold"]; + let mandatory_options = [ + "auth.user_claim_token_pepper", + "logging.threshold", + "metadata.schema_version", + "tracker.token", + ]; for mandatory_option in mandatory_options { figment @@ -512,6 +517,12 @@ mod tests { [logging] threshold = "info" + + [tracker] + token = "MyAccessToken" + + [auth] + user_claim_token_pepper = "MaxVerstappenWC2021" "#, )?; @@ -537,6 +548,12 @@ mod tests { [logging] threshold = "info" + + [tracker] + token = "MyAccessToken" + + [auth] + user_claim_token_pepper = "MaxVerstappenWC2021" "# .to_string(); From cb0e37579cee18ce0df39631ab1c0bfa564f3c30 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Aug 2024 13:07:50 +0100 Subject: [PATCH 307/309] fix: [#653] rename auth.secret_key config option This changes were missing when the issue was implemented. See https://github.com/torrust/torrust-index/issues/653. --- compose.yaml | 2 +- .../dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh | 2 +- .../dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compose.yaml b/compose.yaml index 9e533e15..3f63dde5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,7 @@ services: - TORRUST_INDEX_DATABASE=${TORRUST_INDEX_DATABASE:-e2e_testing_sqlite3} - TORRUST_INDEX_DATABASE_DRIVER=${TORRUST_INDEX_DATABASE_DRIVER:-sqlite3} - TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN=${TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN:-MyAccessToken} - - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY=${TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY:-MaxVerstappenWC2021} + - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__USER_CLAIM_TOKEN_PEPPER=${TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__USER_CLAIM_TOKEN_PEPPER:-MaxVerstappenWC2021} networks: - server_side ports: diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh index 839ce5f4..f5151dc8 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh @@ -8,7 +8,7 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ + TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__USER_CLAIM_TOKEN_PEPPER="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.private.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \ diff --git a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh index c252b47f..ebaae531 100755 --- a/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh +++ b/contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh @@ -8,7 +8,7 @@ USER_ID=${USER_ID:-1000} \ TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \ TORRUST_INDEX_DATABASE_DRIVER="sqlite3" \ TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN="MyAccessToken" \ - TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \ + TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__USER_CLAIM_TOKEN_PEPPER="MaxVerstappenWC2021" \ TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.public.e2e.container.sqlite3.toml) \ TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \ TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \ From 4c568f85763b45bae01f6a65ff27f01783fdbb0d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Aug 2024 11:39:41 +0100 Subject: [PATCH 308/309] chore: synchronize version with tracker 3.0.0-alpha.12-develop --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01ae29f3..700fe069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,7 +3670,7 @@ dependencies = [ [[package]] name = "torrust-index" -version = "3.0.0-alpha.3-develop" +version = "3.0.0-alpha.12-develop" dependencies = [ "anyhow", "argon2", @@ -3736,7 +3736,7 @@ dependencies = [ [[package]] name = "torrust-index-located-error" -version = "3.0.0-alpha.3-develop" +version = "3.0.0-alpha.12-develop" dependencies = [ "thiserror", "tracing", diff --git a/Cargo.toml b/Cargo.toml index a9826995..8164c83d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ license = "AGPL-3.0-only" publish = true repository = "https://github.com/torrust/torrust-tracker" rust-version = "1.72" -version = "3.0.0-alpha.3-develop" +version = "3.0.0-alpha.12-develop" [profile.dev.package.sqlx-macros] opt-level = 3 @@ -88,7 +88,7 @@ text-to-png = "0" thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" -torrust-index-located-error = { version = "3.0.0-alpha.3-develop", path = "packages/located-error" } +torrust-index-located-error = { version = "3.0.0-alpha.12-develop", path = "packages/located-error" } tower = { version = "0.4", features = ["timeout"] } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } trace = "0.1.7" From 817cc7e241b90492b03546a5e359d810445fb0e0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Aug 2024 12:26:47 +0100 Subject: [PATCH 309/309] release: version 3.0.0-alpha.12 --- Cargo.lock | 4 ++-- Cargo.toml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 700fe069..bd7874e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,7 +3670,7 @@ dependencies = [ [[package]] name = "torrust-index" -version = "3.0.0-alpha.12-develop" +version = "3.0.0-alpha.12" dependencies = [ "anyhow", "argon2", @@ -3736,7 +3736,7 @@ dependencies = [ [[package]] name = "torrust-index-located-error" -version = "3.0.0-alpha.12-develop" +version = "3.0.0-alpha.12" dependencies = [ "thiserror", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 8164c83d..9b732288 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ license = "AGPL-3.0-only" publish = true repository = "https://github.com/torrust/torrust-tracker" rust-version = "1.72" -version = "3.0.0-alpha.12-develop" +version = "3.0.0-alpha.12" [profile.dev.package.sqlx-macros] opt-level = 3 @@ -41,6 +41,7 @@ axum-server = { version = "0", features = ["tls-rustls"] } binascii = "0" bytes = "1" camino = { version = "1.1.6", features = ["serde"] } +casbin = "2.2.0" chrono = { version = "0", default-features = false, features = ["clock"] } clap = { version = "4.5.4", features = ["derive", "env"] } derive_more = "0" @@ -56,7 +57,6 @@ hyper = "1" hyper-util = { version = "0.1.3", features = ["http1", "http2", "tokio"] } indexmap = "2" jsonwebtoken = "9" -mockall = "0.12.1" lazy_static = "1.4.0" lettre = { version = "0", features = [ "builder", @@ -67,6 +67,7 @@ lettre = { version = "0", features = [ "tokio1-rustls-tls", ] } log = "0" +mockall = "0.12.1" pbkdf2 = { version = "0", features = ["simple"] } pin-project-lite = "0.2" rand = "0" @@ -88,16 +89,15 @@ text-to-png = "0" thiserror = "1" tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } toml = "0" -torrust-index-located-error = { version = "3.0.0-alpha.12-develop", path = "packages/located-error" } +torrust-index-located-error = { version = "3.0.0-alpha.12", path = "packages/located-error" } tower = { version = "0.4", features = ["timeout"] } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } trace = "0.1.7" tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["json"] } url = { version = "2.5.0", features = ["serde"] } urlencoding = "2" uuid = { version = "1", features = ["v4"] } -tracing-subscriber = { version = "0.3.18", features = ["json"] } -casbin = "2.2.0" [dev-dependencies] tempfile = "3"

- For Code -- agpl-3.0