From c7c573f3f4ab9696b389fc9a9b73b0ef998963c9 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 6 Sep 2025 01:18:04 +0200 Subject: [PATCH] feat: Implement WebFinger discovery and ActivityPub user actor endpoint - Added a new router for handling well-known endpoints, specifically WebFinger. - Implemented the `webfinger` function to respond to WebFinger queries. - Created a new route for WebFinger in the router. - Refactored user retrieval logic to support both user ID and username in a single endpoint. - Updated user router to use the new `get_user_by_param` function. - Added tests for WebFinger discovery and ActivityPub user actor endpoint. - Updated dependencies in Cargo.toml files to include necessary libraries. --- thoughts-backend/Cargo.lock | 726 +++++++++++++++++- thoughts-backend/Cargo.toml | 2 +- thoughts-backend/api/Cargo.toml | 5 + thoughts-backend/api/src/routers/mod.rs | 4 +- thoughts-backend/api/src/routers/user.rs | 101 ++- .../api/src/routers/well_known.rs | 71 ++ thoughts-backend/doc/src/user.rs | 2 +- thoughts-backend/tests/api/activitypub.rs | 59 ++ thoughts-backend/tests/api/mod.rs | 1 + thoughts-backend/utils/src/testing/api/mod.rs | 29 +- thoughts-backend/utils/src/testing/mod.rs | 5 +- 11 files changed, 935 insertions(+), 70 deletions(-) create mode 100644 thoughts-backend/api/src/routers/well_known.rs create mode 100644 thoughts-backend/tests/api/activitypub.rs diff --git a/thoughts-backend/Cargo.lock b/thoughts-backend/Cargo.lock index f6428b5..714ba89 100644 --- a/thoughts-backend/Cargo.lock +++ b/thoughts-backend/Cargo.lock @@ -2,6 +2,211 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "activitypub_federation" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58dd6127cbb797b5626306bebef8ca464fdb38c50f2ed6ecc7a3fcf45d422ec" +dependencies = [ + "activitystreams-kinds", + "actix-web", + "async-trait", + "axum 0.7.9", + "base64", + "bytes", + "chrono", + "derive_builder", + "dyn-clone", + "either", + "enum_delegate", + "futures", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-signature-normalization", + "http-signature-normalization-reqwest", + "httpdate", + "itertools", + "moka", + "once_cell", + "pin-project-lite", + "rand 0.8.5", + "regex", + "reqwest", + "reqwest-middleware 0.3.3", + "rsa", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio", + "tower 0.4.13", + "tracing", + "url", +] + +[[package]] +name = "activitystreams-kinds" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97dfe76efd8c0b113cc3580a6b5f4acba47662e3cfbbfcce081c9ac89798990" +dependencies = [ + "serde", + "url", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.1", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" +dependencies = [ + "actix-codec", + "actix-http", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "bytes", + "bytestring", + "cfg-if", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "tracing", + "url", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -124,9 +329,10 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" name = "api" version = "0.1.0" dependencies = [ + "activitypub_federation", "anyhow", "app", - "axum", + "axum 0.8.4", "bcrypt", "dotenvy", "jsonwebtoken", @@ -134,10 +340,12 @@ dependencies = [ "once_cell", "sea-orm", "serde", - "tower", + "serde_json", + "tower 0.5.2", "tower-cookies", "tower-http", "tracing", + "url", "utoipa", "validator", ] @@ -347,24 +555,53 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.3.1", + "http-body", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "axum-core", + "axum-core 0.5.2", "axum-macros", "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -376,12 +613,32 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.5.2" @@ -390,7 +647,7 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "http-body-util", "mime", @@ -583,6 +840,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.2.27" @@ -750,6 +1016,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -848,6 +1132,58 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.104", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "unicode-xid", +] + [[package]] name = "diesel" version = "2.2.11" @@ -924,7 +1260,7 @@ name = "doc" version = "0.1.0" dependencies = [ "api", - "axum", + "axum 0.8.4", "models", "utoipa", "utoipa-scalar", @@ -951,6 +1287,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -960,6 +1302,39 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum_delegate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ea75f31022cba043afe037940d73684327e915f88f62478e778c3de914cd0a" +dependencies = [ + "enum_delegate_lib", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum_delegate_lib" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1f6c3800b304a6be0012039e2a45a322a093539c45ab818d9e6895a39c90fe" +dependencies = [ + "proc-macro2", + "quote", + "rand 0.8.5", + "syn 1.0.109", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1062,9 +1437,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1083,6 +1458,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1175,6 +1551,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1186,6 +1563,20 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1258,7 +1649,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -1304,7 +1695,7 @@ dependencies = [ "base64", "bytes", "headers-core", - "http", + "http 1.3.1", "httpdate", "mime", "sha1", @@ -1316,7 +1707,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.3.1", ] [[package]] @@ -1370,6 +1761,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.3.1" @@ -1388,7 +1790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -1399,7 +1801,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -1410,6 +1812,32 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" +[[package]] +name = "http-signature-normalization" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95e3149194de5f3f9d5225bcc6a8677979f8ff8ce39c85654730ad4824f101e" +dependencies = [ + "httpdate", +] + +[[package]] +name = "http-signature-normalization-reqwest" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8822f7eab343cae1ce3bd3b6d0b9b58c72adaf3463627cfe150f8f5406f27aa" +dependencies = [ + "async-trait", + "base64", + "http-signature-normalization", + "httpdate", + "reqwest", + "reqwest-middleware 0.3.3", + "sha2", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "httparse" version = "1.10.1" @@ -1432,7 +1860,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http", + "http 1.3.1", "http-body", "httparse", "httpdate", @@ -1449,7 +1877,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.3.1", "hyper", "hyper-util", "rustls", @@ -1471,7 +1899,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "hyper", "ipnet", @@ -1602,9 +2030,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1621,6 +2049,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indexmap" version = "2.10.0" @@ -1674,6 +2108,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1714,6 +2157,12 @@ dependencies = [ "log", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1767,6 +2216,23 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.13" @@ -1786,6 +2252,19 @@ dependencies = [ "value-bag", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -1801,6 +2280,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matchit" version = "0.8.4" @@ -1864,6 +2349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -1880,6 +2366,28 @@ dependencies = [ "validator", ] +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener 5.4.0", + "futures-util", + "loom", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + [[package]] name = "nix" version = "0.29.0" @@ -2075,9 +2583,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pgvector" @@ -2171,6 +2679,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "postgres-protocol" version = "0.6.8" @@ -2486,6 +3000,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -2516,7 +3036,8 @@ dependencies = [ "base64", "bytes", "futures-core", - "http", + "futures-util", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -2535,16 +3056,33 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tower", + "tokio-util", + "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots 1.0.1", ] +[[package]] +name = "reqwest-middleware" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" +dependencies = [ + "anyhow", + "async-trait", + "http 1.3.1", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + [[package]] name = "reqwest-middleware" version = "0.4.2" @@ -2553,7 +3091,7 @@ checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" dependencies = [ "anyhow", "async-trait", - "http", + "http 1.3.1", "reqwest", "serde", "thiserror 1.0.69", @@ -2685,6 +3223,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -2763,6 +3310,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2972,6 +3525,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ + "indexmap", "itoa", "memchr", "ryu", @@ -3046,10 +3600,10 @@ dependencies = [ "anyhow", "async-trait", "headers", - "http", + "http 1.3.1", "percent-encoding", "reqwest", - "reqwest-middleware", + "reqwest-middleware 0.4.2", "serde", "serde_json", "shuttle-common", @@ -3064,7 +3618,7 @@ version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf1e9636f7f77bb6d1eb9e0e19352eb8cbaf11109fdbf5148cabd8d28190161" dependencies = [ - "axum", + "axum 0.8.4", "shuttle-runtime", ] @@ -3087,7 +3641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33df336332f0052b047470b321709f70475a8f4d94909d23bc67d448aad15454" dependencies = [ "chrono", - "http", + "http 1.3.1", "semver", "serde", "serde_json", @@ -3571,6 +4125,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -3623,7 +4183,7 @@ version = "0.1.0" dependencies = [ "api", "app", - "axum", + "axum 0.8.4", "doc", "http-body-util", "models", @@ -3826,6 +4386,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -3848,10 +4419,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" dependencies = [ - "axum-core", + "axum-core 0.5.2", "cookie", "futures-util", - "http", + "http 1.3.1", "parking_lot", "pin-project-lite", "tower-layer", @@ -3868,7 +4439,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "http-range-header", @@ -3880,7 +4451,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -3987,7 +4558,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.3.1", "httparse", "log", "rand 0.9.1", @@ -4059,6 +4630,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4067,13 +4644,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -4098,10 +4676,10 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" name = "utils" version = "0.1.0" dependencies = [ - "axum", + "axum 0.8.4", "migration", "sea-orm", - "tower", + "tower 0.5.2", ] [[package]] @@ -4134,7 +4712,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719" dependencies = [ - "axum", + "axum 0.8.4", "serde", "serde_json", "utoipa", @@ -4146,7 +4724,7 @@ version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" dependencies = [ - "axum", + "axum 0.8.4", "base64", "mime_guess", "regex", @@ -4170,6 +4748,7 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -4340,6 +4919,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -4420,6 +5012,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -4433,6 +5047,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -4461,6 +5086,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4562,6 +5197,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" diff --git a/thoughts-backend/Cargo.toml b/thoughts-backend/Cargo.toml index f8de477..bc23389 100644 --- a/thoughts-backend/Cargo.toml +++ b/thoughts-backend/Cargo.toml @@ -19,7 +19,7 @@ axum = { version = "0.8.4", default-features = false } sea-orm = { version = "1.1.12" } sea-query = { version = "0.32.6" } # Added sea-query dependency serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.140" } +serde_json = { version = "1.0.140", features = ["raw_value"] } tracing = "0.1.41" utoipa = { version = "5.4.0", features = ["macros", "chrono"] } validator = { version = "0.20.0", default-features = false } diff --git a/thoughts-backend/api/Cargo.toml b/thoughts-backend/api/Cargo.toml index c529e73..390f85e 100644 --- a/thoughts-backend/api/Cargo.toml +++ b/thoughts-backend/api/Cargo.toml @@ -22,6 +22,8 @@ tower-http = { version = "0.6.6", features = ["fs", "cors"] } tower-cookies = "0.11.0" anyhow = "1.0.98" dotenvy = "0.15.7" +activitypub_federation = "0.6.5" +url = "2.5.7" # db sea-orm = { workspace = true } @@ -29,8 +31,11 @@ sea-orm = { workspace = true } # doc utoipa = { workspace = true } +serde_json = { workspace = true } + # local dependencies app = { path = "../app" } models = { path = "../models" } + [dev-dependencies] diff --git a/thoughts-backend/api/src/routers/mod.rs b/thoughts-backend/api/src/routers/mod.rs index 3f867c5..edb111e 100644 --- a/thoughts-backend/api/src/routers/mod.rs +++ b/thoughts-backend/api/src/routers/mod.rs @@ -5,8 +5,9 @@ pub mod feed; pub mod root; pub mod thought; pub mod user; +pub mod well_known; -use crate::routers::auth::create_auth_router; +use crate::routers::{auth::create_auth_router, well_known::create_well_known_router}; use app::state::AppState; use root::create_root_router; use tower_http::cors::CorsLayer; @@ -19,6 +20,7 @@ pub fn create_router(state: AppState) -> Router { Router::new() .merge(create_root_router()) + .nest("/.well-known", create_well_known_router()) .nest("/auth", create_auth_router()) .nest("/users", create_user_router()) .nest("/thoughts", create_thought_router()) diff --git a/thoughts-backend/api/src/routers/user.rs b/thoughts-backend/api/src/routers/user.rs index f9c6550..f594493 100644 --- a/thoughts-backend/api/src/routers/user.rs +++ b/thoughts-backend/api/src/routers/user.rs @@ -1,10 +1,11 @@ use axum::{ extract::{Path, Query, State}, http::StatusCode, - response::IntoResponse, + response::{IntoResponse, Response}, routing::{get, post}, Router, }; +use serde_json::json; use app::persistence::{ follow, @@ -44,28 +45,6 @@ async fn users_get( Ok(Json(UserListSchema::from(users))) } -#[utoipa::path( - get, - path = "/{id}", - params( - ("id" = i32, Path, description = "User id") - ), - responses( - (status = 200, description = "Get user", body = UserSchema), - (status = 404, description = "Not found", body = ApiErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ) -)] -async fn users_id_get( - state: State, - Path(id): Path, -) -> Result { - let user = get_user(&state.conn, id).await.map_err(ApiError::from)?; - - user.map(|user| Json(UserSchema::from(user))) - .ok_or_else(|| UserError::NotFound.into()) -} - #[utoipa::path( get, path = "/{username}/thoughts", @@ -165,10 +144,84 @@ async fn user_follow_delete( Ok(StatusCode::NO_CONTENT) } +#[utoipa::path( + get, + path = "/{param}", + params( + ("param" = String, Path, description = "User ID or username") + ), + responses( + (status = 200, description = "User profile or ActivityPub actor", body = UserSchema, content_type = "application/json"), + (status = 200, description = "ActivityPub actor", body = Object, content_type = "application/activity+json"), + (status = 404, description = "User not found", body = ApiErrorResponse), + (status = 500, description = "Internal server error", body = ApiErrorResponse), + ), + security( + ("api_key" = []), + ("bearer_auth" = []) + ) +)] +async fn get_user_by_param( + State(state): State, + headers: axum::http::HeaderMap, + Path(param): Path, +) -> Response { + // First, try to handle it as a numeric ID. + if let Ok(id) = param.parse::() { + return match get_user(&state.conn, id).await { + Ok(Some(user)) => Json(UserSchema::from(user)).into_response(), + Ok(None) => ApiError::from(UserError::NotFound).into_response(), + Err(db_err) => ApiError::from(db_err).into_response(), + }; + } + + // If it's not a number, treat it as a username and perform content negotiation. + let username = param; + let is_activitypub_request = headers + .get(axum::http::header::ACCEPT) + .and_then(|v| v.to_str().ok()) + .map_or(false, |s| s.contains("application/activity+json")); + + if is_activitypub_request { + // This is the logic from `user_actor_get`. + match get_user_by_username(&state.conn, &username).await { + Ok(Some(user)) => { + let base_url = "http://localhost:3000"; + let user_url = format!("{}/users/{}", base_url, user.username); + let actor = json!({ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "id": user_url, + "type": "Person", + "preferredUsername": user.username, + "inbox": format!("{}/inbox", user_url), + "outbox": format!("{}/outbox", user_url), + }); + let mut headers = axum::http::HeaderMap::new(); + headers.insert( + axum::http::header::CONTENT_TYPE, + "application/activity+json".parse().unwrap(), + ); + (headers, Json(actor)).into_response() + } + Ok(None) => ApiError::from(UserError::NotFound).into_response(), + Err(e) => ApiError::from(e).into_response(), + } + } else { + match get_user_by_username(&state.conn, &username).await { + Ok(Some(user)) => Json(UserSchema::from(user)).into_response(), + Ok(None) => ApiError::from(UserError::NotFound).into_response(), + Err(e) => ApiError::from(e).into_response(), + } + } +} + pub fn create_user_router() -> Router { Router::new() .route("/", get(users_get)) - .route("/{id}", get(users_id_get)) + .route("/{param}", get(get_user_by_param)) .route("/{username}/thoughts", get(user_thoughts_get)) .route( "/{username}/follow", diff --git a/thoughts-backend/api/src/routers/well_known.rs b/thoughts-backend/api/src/routers/well_known.rs new file mode 100644 index 0000000..65d8dc1 --- /dev/null +++ b/thoughts-backend/api/src/routers/well_known.rs @@ -0,0 +1,71 @@ +use app::state::AppState; +use axum::{ + extract::{Query, State}, + response::{IntoResponse, Json}, +}; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Deserialize)] +pub struct WebFingerQuery { + resource: String, +} + +#[derive(Serialize)] +pub struct WebFingerLink { + rel: String, + #[serde(rename = "type")] + type_: String, + href: Url, +} + +#[derive(Serialize)] +pub struct WebFingerResponse { + subject: String, + links: Vec, +} + +pub async fn webfinger( + State(state): State, + Query(query): Query, +) -> Result { + if let Some((scheme, account_info)) = query.resource.split_once(':') { + if scheme != "acct" { + return Err(( + axum::http::StatusCode::BAD_REQUEST, + "Invalid resource scheme", + )); + } + + let account_parts: Vec<&str> = account_info.split('@').collect(); + let username = account_parts[0]; + + let user = match app::persistence::user::get_user_by_username(&state.conn, username).await { + Ok(Some(user)) => user, + _ => return Err((axum::http::StatusCode::NOT_FOUND, "User not found")), + }; + + let base_url = "http://localhost:3000"; + let user_url = Url::parse(&format!("{}/users/{}", base_url, user.username)).unwrap(); + + let response = WebFingerResponse { + subject: query.resource, + links: vec![WebFingerLink { + rel: "self".to_string(), + type_: "application/activity+json".to_string(), + href: user_url, + }], + }; + + Ok(Json(response)) + } else { + Err(( + axum::http::StatusCode::BAD_REQUEST, + "Invalid resource format", + )) + } +} + +pub fn create_well_known_router() -> axum::Router { + axum::Router::new().route("/webfinger", axum::routing::get(webfinger)) +} diff --git a/thoughts-backend/doc/src/user.rs b/thoughts-backend/doc/src/user.rs index 34e1a94..82cd577 100644 --- a/thoughts-backend/doc/src/user.rs +++ b/thoughts-backend/doc/src/user.rs @@ -12,7 +12,7 @@ use models::schemas::{ #[openapi( paths( users_get, - users_id_get, + get_user_by_param, user_thoughts_get, user_follow_post, user_follow_delete diff --git a/thoughts-backend/tests/api/activitypub.rs b/thoughts-backend/tests/api/activitypub.rs new file mode 100644 index 0000000..2181d9d --- /dev/null +++ b/thoughts-backend/tests/api/activitypub.rs @@ -0,0 +1,59 @@ +use crate::api::main::{create_user_with_password, setup}; +use axum::http::{header, StatusCode}; +use http_body_util::BodyExt; +use serde_json::Value; +use utils::testing::{make_get_request, make_request_with_headers}; + +#[tokio::test] +async fn test_webfinger_discovery() { + let app = setup().await; + create_user_with_password(&app.db, "testuser", "password123").await; + + // 1. Valid WebFinger lookup for existing user + let url = "/.well-known/webfinger?resource=acct:testuser@localhost:3000"; + let response = make_get_request(app.router.clone(), url, None).await; + assert_eq!(response.status(), StatusCode::OK); + let body = response.into_body().collect().await.unwrap().to_bytes(); + let v: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(v["subject"], "acct:testuser@localhost:3000"); + assert_eq!( + v["links"][0]["href"], + "http://localhost:3000/users/testuser" + ); + + // 2. WebFinger lookup for a non-existent user + let response = make_get_request( + app.router.clone(), + "/.well-known/webfinger?resource=acct:nobody@localhost:3000", + None, + ) + .await; + assert_eq!(response.status(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn test_user_actor_endpoint() { + let app = setup().await; + create_user_with_password(&app.db, "testuser", "password123").await; + + let response = make_request_with_headers( + app.router.clone(), + "/users/testuser", + "GET", + None, + vec![( + header::ACCEPT, + "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + )], + ).await; + + assert_eq!(response.status(), StatusCode::OK); + let content_type = response.headers().get(header::CONTENT_TYPE).unwrap(); + assert_eq!(content_type, "application/activity+json"); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + let v: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(v["type"], "Person"); + assert_eq!(v["preferredUsername"], "testuser"); + assert_eq!(v["id"], "http://localhost:3000/users/testuser"); +} diff --git a/thoughts-backend/tests/api/mod.rs b/thoughts-backend/tests/api/mod.rs index 9910d47..7c7b3a8 100644 --- a/thoughts-backend/tests/api/mod.rs +++ b/thoughts-backend/tests/api/mod.rs @@ -1,3 +1,4 @@ +mod activitypub; mod auth; mod feed; mod follow; diff --git a/thoughts-backend/utils/src/testing/api/mod.rs b/thoughts-backend/utils/src/testing/api/mod.rs index a14242d..9adb476 100644 --- a/thoughts-backend/utils/src/testing/api/mod.rs +++ b/thoughts-backend/utils/src/testing/api/mod.rs @@ -1,4 +1,9 @@ -use axum::{body::Body, http::Request, response::Response, Router}; +use axum::{ + body::Body, + http::{header, Request}, + response::Response, + Router, +}; use tower::ServiceExt; pub async fn make_get_request(app: Router, url: &str, user_id: Option) -> Response { @@ -68,3 +73,25 @@ pub async fn make_jwt_request( .await .unwrap() } + +pub async fn make_request_with_headers( + app: Router, + url: &str, + method: &str, + body: Option, + headers: Vec<(header::HeaderName, &str)>, +) -> Response { + let mut builder = Request::builder() + .method(method) + .uri(url) + .header("Content-Type", "application/json"); + + for (key, value) in headers { + builder = builder.header(key, value); + } + + let request_body = body.unwrap_or_default(); + app.oneshot(builder.body(Body::from(request_body)).unwrap()) + .await + .unwrap() +} diff --git a/thoughts-backend/utils/src/testing/mod.rs b/thoughts-backend/utils/src/testing/mod.rs index 8f6fb6d..bb8a5ba 100644 --- a/thoughts-backend/utils/src/testing/mod.rs +++ b/thoughts-backend/utils/src/testing/mod.rs @@ -1,5 +1,8 @@ mod api; mod db; -pub use api::{make_delete_request, make_get_request, make_jwt_request, make_post_request}; +pub use api::{ + make_delete_request, make_get_request, make_jwt_request, make_post_request, + make_request_with_headers, +}; pub use db::setup_test_db;