From a4989b7ce79590fdab3b16e41a8b6049fed3ed4b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 6 Jun 2024 11:14:00 +0200 Subject: [PATCH] Remove `radicle-httpd` crate The HTTP daemon is moved to the `radicle-explorer` repository. Hence, all traces of it are removed from this repository. --- Cargo.lock | 1009 +------ Cargo.toml | 1 - HACKING.md | 10 +- README.md | 5 +- build/Dockerfile | 5 - debian/rules | 1 - flake.nix | 9 - radicle-httpd.1.adoc | 25 - radicle-httpd/Cargo.toml | 67 - radicle-httpd/build.rs | 1 - radicle-httpd/src/api.rs | 261 -- radicle-httpd/src/api/auth.rs | 44 - radicle-httpd/src/api/error.rs | 158 -- radicle-httpd/src/api/json.rs | 312 --- radicle-httpd/src/api/v1.rs | 71 - radicle-httpd/src/api/v1/delegates.rs | 225 -- radicle-httpd/src/api/v1/node.rs | 135 - radicle-httpd/src/api/v1/profile.rs | 123 - radicle-httpd/src/api/v1/projects.rs | 3565 ------------------------- radicle-httpd/src/api/v1/sessions.rs | 183 -- radicle-httpd/src/api/v1/stats.rs | 42 - radicle-httpd/src/axum_extra.rs | 99 - radicle-httpd/src/bin/rad-web.rs | 10 - radicle-httpd/src/cache.rs | 22 - radicle-httpd/src/commands.rs | 2 - radicle-httpd/src/commands/web.rs | 233 -- radicle-httpd/src/error.rs | 116 - radicle-httpd/src/git.rs | 252 -- radicle-httpd/src/lib.rs | 180 -- radicle-httpd/src/main.rs | 62 - radicle-httpd/src/raw.rs | 225 -- radicle-httpd/src/test.rs | 395 --- radicle-httpd/src/tracing_extra.rs | 70 - systemd/radicle-httpd.service | 23 - 34 files changed, 32 insertions(+), 7909 deletions(-) delete mode 100644 radicle-httpd.1.adoc delete mode 100644 radicle-httpd/Cargo.toml delete mode 120000 radicle-httpd/build.rs delete mode 100644 radicle-httpd/src/api.rs delete mode 100644 radicle-httpd/src/api/auth.rs delete mode 100644 radicle-httpd/src/api/error.rs delete mode 100644 radicle-httpd/src/api/json.rs delete mode 100644 radicle-httpd/src/api/v1.rs delete mode 100644 radicle-httpd/src/api/v1/delegates.rs delete mode 100644 radicle-httpd/src/api/v1/node.rs delete mode 100644 radicle-httpd/src/api/v1/profile.rs delete mode 100644 radicle-httpd/src/api/v1/projects.rs delete mode 100644 radicle-httpd/src/api/v1/sessions.rs delete mode 100644 radicle-httpd/src/api/v1/stats.rs delete mode 100644 radicle-httpd/src/axum_extra.rs delete mode 100644 radicle-httpd/src/bin/rad-web.rs delete mode 100644 radicle-httpd/src/cache.rs delete mode 100644 radicle-httpd/src/commands.rs delete mode 100644 radicle-httpd/src/commands/web.rs delete mode 100644 radicle-httpd/src/error.rs delete mode 100644 radicle-httpd/src/git.rs delete mode 100644 radicle-httpd/src/lib.rs delete mode 100644 radicle-httpd/src/main.rs delete mode 100644 radicle-httpd/src/raw.rs delete mode 100644 radicle-httpd/src/test.rs delete mode 100644 radicle-httpd/src/tracing_extra.rs delete mode 100644 systemd/radicle-httpd.service diff --git a/Cargo.lock b/Cargo.lock index ece6ea36..be29bfe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - [[package]] name = "adler" version = "1.0.2" @@ -52,18 +43,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -73,12 +52,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "amplify" version = "4.6.0" @@ -204,122 +177,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" -[[package]] -name = "axum" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.1", - "tokio", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-auth" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8169113a185f54f68614fcfc3581df585d30bf8542bcb99496990e1025e4120a" -dependencies = [ - "async-trait", - "axum-core", - "base64 0.21.7", - "http", -] - -[[package]] -name = "axum-core" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 0.1.2", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-server" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad46c3ec4e12f4a4b6835e173ba21c25e484c9d02b49770bf006ce5367c036" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower", - "tower-service", -] - -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base-x" version = "0.2.11" @@ -423,7 +286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata 0.4.6", + "regex-automata", "serde", ] @@ -448,12 +311,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - [[package]] name = "cbc" version = "0.1.2" @@ -705,7 +562,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -875,12 +731,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -890,45 +740,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - [[package]] name = "fxhash" version = "0.2.1" @@ -970,12 +781,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - [[package]] name = "git-ref-format" version = "0.3.0" @@ -1544,40 +1349,11 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hmac" @@ -1597,89 +1373,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[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", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", -] - [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1888,30 +1581,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -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 = "maybe-async" version = "0.2.10" @@ -1938,12 +1607,6 @@ dependencies = [ "libc", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1953,17 +1616,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "multibase" version = "0.9.1" @@ -2030,16 +1682,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2093,16 +1735,6 @@ 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 = "num_threads" version = "0.1.7" @@ -2118,15 +1750,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -2139,12 +1762,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -2231,38 +1848,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs1" version = "0.7.5" @@ -2448,44 +2033,10 @@ dependencies = [ "pretty_assertions", "qcheck", "qcheck-macros", - "radicle-cob 0.11.0", - "radicle-crypto 0.10.0", + "radicle-cob", + "radicle-crypto", "radicle-git-ext", - "radicle-ssh 0.9.0", - "serde", - "serde_json", - "siphasher 1.0.1", - "sqlite", - "tempfile", - "thiserror", - "unicode-normalization", -] - -[[package]] -name = "radicle" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c96b3901ca5b7bfe06da3fb18105c32dc5f9f5c48a217cfc7104385a687195" -dependencies = [ - "amplify", - "base64 0.21.7", - "chrono", - "colored", - "crossbeam-channel", - "cyphernet", - "fastrand", - "git2", - "libc", - "localtime", - "log", - "multibase", - "nonempty 0.9.0", - "once_cell", - "qcheck", - "radicle-cob 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-crypto 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-git-ext", - "radicle-ssh 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "radicle-ssh", "serde", "serde_json", "siphasher 1.0.1", @@ -2507,57 +2058,14 @@ dependencies = [ "log", "nonempty 0.9.0", "pretty_assertions", - "radicle 0.11.0", - "radicle-cli-test 0.10.0", - "radicle-cob 0.11.0", - "radicle-crypto 0.10.0", + "radicle", + "radicle-cli-test", + "radicle-cob", + "radicle-crypto", "radicle-git-ext", "radicle-node", "radicle-surf", - "radicle-term 0.10.0", - "serde", - "serde_json", - "shlex", - "tempfile", - "thiserror", - "timeago", - "tree-sitter", - "tree-sitter-bash", - "tree-sitter-c", - "tree-sitter-css", - "tree-sitter-go", - "tree-sitter-highlight", - "tree-sitter-html", - "tree-sitter-json", - "tree-sitter-md", - "tree-sitter-python", - "tree-sitter-ruby", - "tree-sitter-rust", - "tree-sitter-toml", - "tree-sitter-typescript", - "zeroize", -] - -[[package]] -name = "radicle-cli" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5347c326bec844b7ced9c8f8bcfec88104ea2066c029d49d5128dd09a4148c50" -dependencies = [ - "anyhow", - "chrono", - "git-ref-format", - "lexopt", - "localtime", - "log", - "nonempty 0.9.0", - "radicle 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-cli-test 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-cob 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-crypto 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-git-ext", - "radicle-surf", - "radicle-term 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "radicle-term", "serde", "serde_json", "shlex", @@ -2588,22 +2096,7 @@ dependencies = [ "escargot", "log", "pretty_assertions", - "radicle 0.11.0", - "shlex", - "snapbox", - "thiserror", -] - -[[package]] -name = "radicle-cli-test" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bbd1dc7cb2801693d6d00f937021adb0d398e9fec6b998e4830ebba32fdfdd" -dependencies = [ - "escargot", - "log", - "pretty_assertions", - "radicle 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "radicle", "shlex", "snapbox", "thiserror", @@ -2620,8 +2113,8 @@ dependencies = [ "once_cell", "qcheck", "qcheck-macros", - "radicle-crypto 0.10.0", - "radicle-dag 0.9.0", + "radicle-crypto", + "radicle-dag", "radicle-git-ext", "serde", "serde_json", @@ -2629,25 +2122,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "radicle-cob" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36d8268661b22cec768bdf687aa9d98db2dcd9c8f974e8208f8658244074b539" -dependencies = [ - "fastrand", - "git2", - "log", - "nonempty 0.9.0", - "once_cell", - "radicle-crypto 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-dag 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-git-ext", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "radicle-crdt" version = "0.1.0" @@ -2656,7 +2130,7 @@ dependencies = [ "num-traits", "qcheck", "qcheck-macros", - "radicle-crypto 0.10.0", + "radicle-crypto", "serde", "tempfile", "thiserror", @@ -2674,7 +2148,7 @@ dependencies = [ "qcheck", "qcheck-macros", "radicle-git-ext", - "radicle-ssh 0.9.0", + "radicle-ssh", "serde", "sqlite", "ssh-key", @@ -2683,27 +2157,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "radicle-crypto" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb86116dc5d9daa0d0b8e07fb71c9887d537b3fecebffc0cde6624b07176c711" -dependencies = [ - "amplify", - "cyphernet", - "ec25519", - "fastrand", - "multibase", - "qcheck", - "radicle-git-ext", - "radicle-ssh 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "sqlite", - "ssh-key", - "thiserror", - "zeroize", -] - [[package]] name = "radicle-dag" version = "0.9.0" @@ -2711,15 +2164,6 @@ dependencies = [ "fastrand", ] -[[package]] -name = "radicle-dag" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a678c3049a88ae6a34dd9f52ea9a5f9f066a0af63466b75cf8c48840303067" -dependencies = [ - "fastrand", -] - [[package]] name = "radicle-fetch" version = "0.9.0" @@ -2735,7 +2179,7 @@ dependencies = [ "gix-transport 0.42.0", "log", "nonempty 0.9.0", - "radicle 0.11.0", + "radicle", "radicle-git-ext", "thiserror", ] @@ -2754,43 +2198,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "radicle-httpd" -version = "0.10.0" -dependencies = [ - "anyhow", - "axum", - "axum-auth", - "axum-server", - "base64 0.21.7", - "chrono", - "fastrand", - "flate2", - "hyper", - "lexopt", - "lru", - "nonempty 0.9.0", - "pretty_assertions", - "radicle 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-cli 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-crypto 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "radicle-surf", - "radicle-term 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_json", - "tempfile", - "thiserror", - "time", - "tokio", - "tower", - "tower-http", - "tracing", - "tracing-logfmt", - "tracing-subscriber", - "ureq", - "url", -] - [[package]] name = "radicle-node" version = "0.9.0" @@ -2815,11 +2222,11 @@ dependencies = [ "once_cell", "qcheck", "qcheck-macros", - "radicle 0.11.0", - "radicle-crypto 0.10.0", + "radicle", + "radicle-crypto", "radicle-fetch", "radicle-git-ext", - "radicle-signals 0.9.0", + "radicle-signals", "scrypt", "serde", "serde_json", @@ -2835,9 +2242,9 @@ name = "radicle-remote-helper" version = "0.9.0" dependencies = [ "log", - "radicle 0.11.0", - "radicle-cli 0.10.0", - "radicle-crypto 0.10.0", + "radicle", + "radicle-cli", + "radicle-crypto", "radicle-git-ext", "thiserror", ] @@ -2850,16 +2257,6 @@ dependencies = [ "libc", ] -[[package]] -name = "radicle-signals" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0633d483e40eb96a8e57264727f1c4f0d188348eb5c155cf1369469c121c6c87" -dependencies = [ - "crossbeam-channel", - "libc", -] - [[package]] name = "radicle-ssh" version = "0.9.0" @@ -2870,18 +2267,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "radicle-ssh" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee758010fb64482be4b18591fbeb3cbc15b16450d143edf4edb5484c7366c6" -dependencies = [ - "byteorder", - "log", - "thiserror", - "zeroize", -] - [[package]] name = "radicle-std-ext" version = "0.1.0" @@ -2902,7 +2287,6 @@ dependencies = [ "nonempty 0.5.0", "radicle-git-ext", "radicle-std-ext", - "serde", "tar", "thiserror", "url", @@ -2920,7 +2304,7 @@ dependencies = [ "libc", "once_cell", "pretty_assertions", - "radicle-signals 0.9.0", + "radicle-signals", "shlex", "tempfile", "termion 3.0.0", @@ -2930,37 +2314,15 @@ dependencies = [ "zeroize", ] -[[package]] -name = "radicle-term" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a46c7b39b0fabe11cbb1f697979f1e1021122aef76b476f5d385c48a02400310" -dependencies = [ - "anstyle-query", - "anyhow", - "crossbeam-channel", - "git2", - "inquire", - "libc", - "once_cell", - "radicle-signals 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "shlex", - "termion 3.0.0", - "thiserror", - "unicode-display-width", - "unicode-segmentation", - "zeroize", -] - [[package]] name = "radicle-tools" version = "0.9.0" dependencies = [ "anyhow", - "radicle 0.11.0", - "radicle-cli 0.10.0", + "radicle", + "radicle-cli", "radicle-git-ext", - "radicle-term 0.10.0", + "radicle-term", ] [[package]] @@ -3016,17 +2378,8 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -3037,15 +2390,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -3083,12 +2430,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - [[package]] name = "rustix" version = "0.38.34" @@ -3102,12 +2443,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" - [[package]] name = "ryu" version = "1.0.17" @@ -3195,28 +2530,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha1_smol" version = "1.0.0" @@ -3244,15 +2557,6 @@ dependencies = [ "keccak", ] -[[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 = "shell-words" version = "1.1.0" @@ -3299,15 +2603,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -3479,18 +2774,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" - [[package]] name = "tar" version = "0.4.40" @@ -3558,16 +2841,6 @@ dependencies = [ "syn 2.0.60", ] -[[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" @@ -3622,154 +2895,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "tokio-util" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" -dependencies = [ - "bitflags 2.5.0", - "bytes", - "http", - "http-body", - "http-body-util", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-logfmt" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b8e455f6caa5212a102ec530bf86b8dc5a4c536299bffd84b238fed9119be7" -dependencies = [ - "time", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "thread_local", - "tracing", - "tracing-core", -] - [[package]] name = "tree-sitter" version = "0.20.10" @@ -3911,12 +3036,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typenum" version = "1.17.0" @@ -3975,20 +3094,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ureq" -version = "2.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" -dependencies = [ - "base64 0.21.7", - "log", - "once_cell", - "serde", - "serde_json", - "url", -] - [[package]] name = "url" version = "2.5.0" @@ -3998,7 +3103,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -4007,12 +3111,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[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" @@ -4035,15 +3133,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4104,22 +3193,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.7" @@ -4129,12 +3202,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.52.0" @@ -4318,26 +3385,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index f9b60eec..42cef9ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ "radicle-crypto", "radicle-dag", "radicle-fetch", - "radicle-httpd", "radicle-node", "radicle-remote-helper", "radicle-ssh", diff --git a/HACKING.md b/HACKING.md index d50904db..7fc7a890 100644 --- a/HACKING.md +++ b/HACKING.md @@ -24,7 +24,6 @@ The repository is structured in *crates*, as follows: * `radicle-crdt`: Conflict-free replicated datatypes (CRDTs) used for things like discussions and patches. * `radicle-crypto`: A wrapper around Ed25519 cryptographic signing primitives. * `radicle-dag`: A simple directed acyclic graph implementation used by `radicle-cob`. -* `radicle-httpd`: The radicle HTTP daemon that serves API clients and Git fetch requests. * `radicle-node`: The radicle peer-to-peer daemon that enables users to connect to the network and share code. * `radicle-remote-helper`: A Git remote helper for `rad://` remotes. * `radicle-ssh`: OpenSSH functionality, including a library used to interface with `ssh-agent`. @@ -41,11 +40,6 @@ For example, the equivalent of `rad auth` in debug mode would be: Arguments after the `--` are passed directly to the `rad` executable. -When running the radicle node, you may specify an alternate port for the `git-daemon` -like so: - - $ cargo run -p radicle-node -- --git-daemon 127.0.0.1:9876 - This is useful if you are running multiple nodes on the same machine. You can also specify different listen addresses for the peer-to-peer protocol using `--listen`. To view all options, run `cargo run -p radicle-node -- --help`. @@ -82,8 +76,8 @@ avoid storing development keys with `ssh-agent`. ## Logging -Logging for `radicle-node` and `radicle-httpd` is turned on by default. Check -the respective `--help` output to set the log level. +Logging for `radicle-node` is turned on by default. Check the respective +`--help` output to set the log level. ## Writing tests diff --git a/README.md b/README.md index 109ba292..a6c939db 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,8 @@ Or directly from our seed node: ## Running -*Systemd* unit files are provided for the node and HTTP daemon under the -`/systemd` folder. They can be used as a starting point for further -customization. +*Systemd* unit files are provided for the node under the `/systemd` folder. +They can be used as a starting point for further customization. For running in debug mode, see [HACKING.md](HACKING.md). diff --git a/build/Dockerfile b/build/Dockerfile index cf01fd8f..3a7efc4d 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -45,7 +45,6 @@ RUN cargo zigbuild --locked --release \ --target=aarch64-unknown-linux-musl \ --target=x86_64-unknown-linux-musl \ -p radicle-node \ - -p radicle-httpd \ -p radicle-remote-helper \ -p radicle-cli @@ -57,28 +56,24 @@ COPY --from=builder \ /src/target/x86_64-unknown-linux-musl/release/rad-web \ /src/target/x86_64-unknown-linux-musl/release/git-remote-rad \ /src/target/x86_64-unknown-linux-musl/release/radicle-node \ - /src/target/x86_64-unknown-linux-musl/release/radicle-httpd \ /builds/x86_64-unknown-linux-musl/bin/ COPY --from=builder \ /src/target/aarch64-unknown-linux-musl/release/rad \ /src/target/aarch64-unknown-linux-musl/release/rad-web \ /src/target/aarch64-unknown-linux-musl/release/git-remote-rad \ /src/target/aarch64-unknown-linux-musl/release/radicle-node \ - /src/target/aarch64-unknown-linux-musl/release/radicle-httpd \ /builds/aarch64-unknown-linux-musl/bin/ COPY --from=builder \ /src/target/aarch64-apple-darwin/release/rad \ /src/target/aarch64-apple-darwin/release/rad-web \ /src/target/aarch64-apple-darwin/release/git-remote-rad \ /src/target/aarch64-apple-darwin/release/radicle-node \ - /src/target/aarch64-apple-darwin/release/radicle-httpd \ /builds/aarch64-apple-darwin/bin/ COPY --from=builder \ /src/target/x86_64-apple-darwin/release/rad \ /src/target/x86_64-apple-darwin/release/rad-web \ /src/target/x86_64-apple-darwin/release/git-remote-rad \ /src/target/x86_64-apple-darwin/release/radicle-node \ - /src/target/x86_64-apple-darwin/release/radicle-httpd \ /builds/x86_64-apple-darwin/bin/ COPY --from=builder /src/*.1 /builds/x86_64-unknown-linux-musl/man/man1/ COPY --from=builder /src/*.1 /builds/aarch64-unknown-linux-musl/man/man1/ diff --git a/debian/rules b/debian/rules index 677f4f43..8472b0f4 100755 --- a/debian/rules +++ b/debian/rules @@ -12,7 +12,6 @@ override_dh_auto_install: cargo install --locked --path=radicle-cli --root=debian/radicle cargo install --locked --path=radicle-node --root=debian/radicle cargo install --locked --path=radicle-remote-helper --root=debian/radicle - cargo install --locked --path=radicle-httpd --root=debian/radicle rm -f debian/*/.crates*.* override_dh_auto_test: diff --git a/flake.nix b/flake.nix index 2d06af35..3c3d15bd 100644 --- a/flake.nix +++ b/flake.nix @@ -170,10 +170,6 @@ crates = builtins.listToAttrs (map ({name, ...} @ package: lib.nameValuePair name (crate package)) [ - { - name = "radicle-httpd"; - pages = ["radicle-httpd.1.adoc"]; - } { name = "radicle-cli"; pages = [ @@ -233,11 +229,6 @@ drv = self.packages.${system}.radicle-node; }; - apps.radicle-httpd = flake-utils.lib.mkApp { - name = "radicle-httpd"; - drv = self.packages.${system}.radicle-httpd; - }; - devShells.default = craneLib.devShell { # Extra inputs can be added here; cargo and rustc are provided by default. packages = with pkgs; [ diff --git a/radicle-httpd.1.adoc b/radicle-httpd.1.adoc deleted file mode 100644 index 3be21bc3..00000000 --- a/radicle-httpd.1.adoc +++ /dev/null @@ -1,25 +0,0 @@ -= radicle-httpd(1) -The Radicle Team -:doctype: manpage -:revnumber: 1.0.0 -:revdate: 2024-04-22 -:mansource: rad {revnumber} -:manmanual: Radicle CLI Manual - -== Name - -radicle-httpd - Radicle HTTP daemon - -== Synopsis - -*radicle-httpd* --help - -== Description - -A Radicle HTTP daemon exposing a JSON HTTP API that allows someone to browse local -repositories on a Radicle node via their web browser. This manual page is a -placeholder to point you at the *--help* option. - -== SEE ALSO == - -*rad*(1) diff --git a/radicle-httpd/Cargo.toml b/radicle-httpd/Cargo.toml deleted file mode 100644 index e242fa76..00000000 --- a/radicle-httpd/Cargo.toml +++ /dev/null @@ -1,67 +0,0 @@ -[package] -name = "radicle-httpd" -description = "Radicle HTTP daemon" -homepage = "https://radicle.xyz" -license = "MIT OR Apache-2.0" -version = "0.10.0" -authors = ["cloudhead "] -edition = "2021" -default-run = "radicle-httpd" -build = "build.rs" - -[features] -default = [] -logfmt = [ - "tracing-logfmt", - "tracing-subscriber/env-filter" -] - -[[bin]] -name = "radicle-httpd" -path = "src/main.rs" - -[[bin]] -name = "rad-web" -path = "src/bin/rad-web.rs" - -[dependencies] -anyhow = { version = "1" } -axum = { version = "0.7.2", default-features = false, features = ["json", "query", "tokio", "http1"] } -axum-auth = { version= "0.7.0", default-features = false, features = ["auth-bearer"] } -axum-server = { version = "0.6.0", default-features = false } -base64 = "0.21.3" -chrono = { version = "0.4.22", default-features = false, features = ["clock"] } -fastrand = { version = "2.0.0" } -flate2 = { version = "1" } -hyper = { version = "1.0.1", default-features = false } -lexopt = { version = "0.3.0" } -lru = { version = "0.12.0" } -nonempty = { version = "0.9.0", features = ["serialize"] } -radicle-surf = { version = "0.21.0", default-features = false, features = ["serde"] } -serde = { version = "1", features = ["derive"] } -serde_json = { version = "1", features = ["preserve_order"] } -thiserror = { version = "1" } -time = { version = "0.3.17", features = ["parsing", "serde"] } -tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] } -tower-http = { version = "0.5", default-features = false, features = ["trace", "cors", "set-header"] } -tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] } -tracing-logfmt = { version = "0.3", optional = true } -tracing-subscriber = { version = "0.3", default-features = false, features = ["std", "ansi", "fmt"] } -ureq = { version = "2.9", default-features = false, features = ["json"] } -url = { version = "2.5.0" } - -[dependencies.radicle] -version = "0.11.0" - -[dependencies.radicle-term] -version = "0.10.0" - -[dependencies.radicle-cli] -version = "0.10.0" - -[dev-dependencies] -hyper = { version = "1.0.1", default-features = false, features = ["client"] } -pretty_assertions = { version = "1.3.0" } -radicle-crypto = { version = "0.10.0", features = ["test"] } -tempfile = { version = "3.3.0" } -tower = { version = "0.4", features = ["util"] } diff --git a/radicle-httpd/build.rs b/radicle-httpd/build.rs deleted file mode 120000 index 10238032..00000000 --- a/radicle-httpd/build.rs +++ /dev/null @@ -1 +0,0 @@ -../build.rs \ No newline at end of file diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs deleted file mode 100644 index 36bb531c..00000000 --- a/radicle-httpd/src/api.rs +++ /dev/null @@ -1,261 +0,0 @@ -pub mod auth; - -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; - -use axum::http::header::{AUTHORIZATION, CONTENT_TYPE}; -use axum::http::Method; -use axum::response::{IntoResponse, Json}; -use axum::routing::get; -use axum::Router; -use radicle::issue::cache::Issues as _; -use radicle::patch::cache::Patches as _; -use radicle::storage::git::Repository; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use tokio::sync::RwLock; -use tower_http::cors::{self, CorsLayer}; - -use radicle::cob::{issue, patch, Author}; -use radicle::identity::{DocAt, RepoId}; -use radicle::node::policy::Scope; -use radicle::node::routing::Store; -use radicle::node::AliasStore; -use radicle::node::{Handle, NodeId}; -use radicle::storage::{ReadRepository, ReadStorage}; -use radicle::{Node, Profile}; - -mod error; -mod json; -mod v1; - -use crate::api::error::Error; -use crate::cache::Cache; -use crate::Options; - -pub const RADICLE_VERSION: &str = env!("RADICLE_VERSION"); -// This version has to be updated on every breaking change to the radicle-httpd API. -pub const API_VERSION: &str = "0.1.0"; - -/// Identifier for sessions -type SessionId = String; - -#[derive(Clone)] -pub struct Context { - profile: Arc, - sessions: Arc>>, - cache: Option, -} - -impl Context { - pub fn new(profile: Arc, options: &Options) -> Self { - Self { - profile, - sessions: Default::default(), - cache: options.cache.map(Cache::new), - } - } - - pub fn project_info( - &self, - repo: &R, - doc: DocAt, - ) -> Result { - let (_, head) = repo.head()?; - let DocAt { doc, .. } = doc; - let id = repo.id(); - - let payload = doc.project()?; - let aliases = self.profile.aliases(); - let delegates = doc - .delegates - .into_iter() - .map(|did| json::author(&Author::new(did), aliases.alias(did.as_key()))) - .collect::>(); - let issues = self.profile.issues(repo)?.counts()?; - let patches = self.profile.patches(repo)?.counts()?; - let db = &self.profile.database()?; - let seeding = db.count(&id).unwrap_or_default(); - - Ok(project::Info { - payload, - delegates, - threshold: doc.threshold, - visibility: doc.visibility, - head, - issues, - patches, - id, - seeding, - }) - } - - /// Get a repository by RID, checking to make sure we're allowed to view it. - pub fn repo(&self, rid: RepoId) -> Result<(Repository, DocAt), error::Error> { - let repo = self.profile.storage.repository(rid)?; - let doc = repo.identity_doc()?; - // Don't allow accessing private repos. - if doc.visibility.is_private() { - return Err(Error::NotFound); - } - Ok((repo, doc)) - } - - #[cfg(test)] - pub fn profile(&self) -> &Arc { - &self.profile - } - - #[cfg(test)] - pub fn sessions(&self) -> &Arc>> { - &self.sessions - } -} - -pub fn router(ctx: Context) -> Router { - Router::new() - .route("/", get(root_handler)) - .merge(v1::router(ctx)) - .layer( - CorsLayer::new() - .max_age(Duration::from_secs(86400)) - .allow_origin(cors::Any) - .allow_methods([ - Method::GET, - Method::POST, - Method::PATCH, - Method::PUT, - Method::DELETE, - ]) - .allow_headers([CONTENT_TYPE, AUTHORIZATION]), - ) -} - -async fn root_handler() -> impl IntoResponse { - let response = json!({ - "path": "/api", - "links": [ - { - "href": "/v1", - "rel": "v1", - "type": "GET" - } - ] - }); - - Json(response) -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct PaginationQuery { - #[serde(default)] - pub show: ProjectQuery, - pub page: Option, - pub per_page: Option, -} - -#[derive(Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub enum ProjectQuery { - All, - #[default] - Pinned, -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct RawQuery { - pub mime: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct CobsQuery { - pub page: Option, - pub per_page: Option, - pub state: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct PoliciesQuery { - /// The NID from which to fetch from after tracking a repo. - pub from: Option, - pub scope: Option, -} - -#[derive(Default, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub enum IssueState { - Closed, - #[default] - Open, -} - -impl IssueState { - pub fn matches(&self, issue: &issue::State) -> bool { - match self { - Self::Open => matches!(issue, issue::State::Open), - Self::Closed => matches!(issue, issue::State::Closed { .. }), - } - } -} - -#[derive(Default, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub enum PatchState { - #[default] - Open, - Draft, - Archived, - Merged, -} - -impl PatchState { - pub fn matches(&self, patch: &patch::State) -> bool { - match self { - Self::Open => matches!(patch, patch::State::Open { .. }), - Self::Draft => matches!(patch, patch::State::Draft), - Self::Archived => matches!(patch, patch::State::Archived), - Self::Merged => matches!(patch, patch::State::Merged { .. }), - } - } -} - -mod project { - use serde::Serialize; - use serde_json::Value; - - use radicle::cob; - use radicle::git::Oid; - use radicle::identity::project::Project; - use radicle::identity::{RepoId, Visibility}; - - /// Project info. - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - pub struct Info { - /// Project metadata. - #[serde(flatten)] - pub payload: Project, - pub delegates: Vec, - pub threshold: usize, - pub visibility: Visibility, - pub head: Oid, - pub patches: cob::patch::PatchCounts, - pub issues: cob::issue::IssueCounts, - pub id: RepoId, - pub seeding: usize, - } -} - -/// Announce refs to the network for the given RID. -pub fn announce_refs(mut node: Node, rid: RepoId) -> Result<(), Error> { - match node.announce_refs(rid) { - Ok(_) => Ok(()), - Err(e) if e.is_connection_err() => Ok(()), - Err(e) => Err(e.into()), - } -} diff --git a/radicle-httpd/src/api/auth.rs b/radicle-httpd/src/api/auth.rs deleted file mode 100644 index 255de775..00000000 --- a/radicle-httpd/src/api/auth.rs +++ /dev/null @@ -1,44 +0,0 @@ -use serde::{Deserialize, Serialize}; -use time::serde::timestamp; -use time::{Duration, OffsetDateTime}; - -use radicle::crypto::PublicKey; -use radicle::node::Alias; - -use crate::api::error::Error; -use crate::api::Context; - -pub const UNAUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::seconds(60); -pub const AUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::weeks(1); - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum AuthState { - Authorized, - Unauthorized, -} - -#[derive(Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Session { - pub status: AuthState, - pub public_key: PublicKey, - pub alias: Alias, - #[serde(with = "timestamp")] - pub issued_at: OffsetDateTime, - #[serde(with = "timestamp")] - pub expires_at: OffsetDateTime, -} - -pub async fn validate(ctx: &Context, token: &str) -> Result<(), Error> { - let sessions_store = ctx.sessions.read().await; - let session = sessions_store - .get(token) - .ok_or(Error::Auth("Unauthorized"))?; - - if session.status != AuthState::Authorized || session.expires_at <= OffsetDateTime::now_utc() { - return Err(Error::Auth("Unauthorized")); - } - - Ok(()) -} diff --git a/radicle-httpd/src/api/error.rs b/radicle-httpd/src/api/error.rs deleted file mode 100644 index da100566..00000000 --- a/radicle-httpd/src/api/error.rs +++ /dev/null @@ -1,158 +0,0 @@ -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use axum::Json; -use serde_json::json; - -/// Errors relating to the API backend. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// The entity was not found. - #[error("entity not found")] - NotFound, - - /// An error occurred during an authentication process. - #[error("could not authenticate: {0}")] - Auth(&'static str), - - /// An error occurred with env variables. - #[error(transparent)] - Env(#[from] std::env::VarError), - - /// Profile error. - #[error(transparent)] - Profile(#[from] radicle::profile::Error), - - /// Crypto error. - #[error(transparent)] - Crypto(#[from] radicle::crypto::Error), - - /// Storage error. - #[error(transparent)] - Storage(#[from] radicle::storage::Error), - - /// Cob cache error. - #[error(transparent)] - CobCache(#[from] radicle::cob::cache::Error), - - /// Cob issue cache error. - #[error(transparent)] - CacheIssue(#[from] radicle::cob::issue::cache::Error), - - /// Cob issue error. - #[error(transparent)] - CobIssue(#[from] radicle::cob::issue::Error), - - /// Cob patch error. - #[error(transparent)] - CobPatch(#[from] radicle::cob::patch::Error), - - /// Cob patch cache error. - #[error(transparent)] - CachePatch(#[from] radicle::cob::patch::cache::Error), - - /// Cob store error. - #[error(transparent)] - CobStore(#[from] radicle::cob::store::Error), - - /// Repository error. - #[error(transparent)] - Repository(#[from] radicle::storage::RepositoryError), - - /// Routing error. - #[error(transparent)] - Routing(#[from] radicle::node::routing::Error), - - /// Project doc error. - #[error(transparent)] - ProjectDoc(#[from] radicle::identity::doc::PayloadError), - - /// Surf directory error. - #[error(transparent)] - SurfDir(#[from] radicle_surf::fs::error::Directory), - - /// Surf error. - #[error(transparent)] - Surf(#[from] radicle_surf::Error), - - /// Git2 error. - #[error(transparent)] - Git2(#[from] radicle::git::raw::Error), - - /// Storage refs error. - #[error(transparent)] - StorageRef(#[from] radicle::storage::refs::Error), - - /// Identity doc error. - #[error(transparent)] - IdentityDoc(#[from] radicle::identity::doc::DocError), - - /// Tracking store error. - #[error(transparent)] - TrackingStore(#[from] radicle::node::policy::store::Error), - - /// Node database error. - #[error(transparent)] - Database(#[from] radicle::node::db::Error), - - /// Node error. - #[error(transparent)] - Node(#[from] radicle::node::Error), - - /// Invalid update to issue or patch. - #[error("{0}")] - BadRequest(String), -} - -impl IntoResponse for Error { - fn into_response(self) -> Response { - let message = self.to_string(); - let (status, msg) = match self { - Error::NotFound => (StatusCode::NOT_FOUND, None), - Error::CobStore(e @ radicle::cob::store::Error::NotFound(_, _)) => { - (StatusCode::NOT_FOUND, Some(e.to_string())) - } - Error::Auth(msg) => (StatusCode::UNAUTHORIZED, Some(msg.to_string())), - Error::Crypto(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())), - Error::Surf(radicle_surf::Error::Git(e)) if radicle::git::is_not_found_err(&e) => { - (StatusCode::NOT_FOUND, Some(e.message().to_owned())) - } - Error::Surf(radicle_surf::Error::Directory( - e @ radicle_surf::fs::error::Directory::PathNotFound(_), - )) => (StatusCode::NOT_FOUND, Some(e.to_string())), - Error::Git2(e) if radicle::git::is_not_found_err(&e) => { - (StatusCode::NOT_FOUND, Some(e.message().to_owned())) - } - Error::Git2(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Some(e.message().to_owned()), - ), - Error::Storage(err) if err.is_not_found() => { - (StatusCode::NOT_FOUND, Some(err.to_string())) - } - Error::Repository(err) if err.is_not_found() => { - (StatusCode::NOT_FOUND, Some(err.to_string())) - } - Error::StorageRef(err) if err.is_not_found() => { - (StatusCode::NOT_FOUND, Some(err.to_string())) - } - Error::BadRequest(msg) => (StatusCode::BAD_REQUEST, Some(msg)), - other => { - tracing::error!("Error: {message}"); - tracing::debug!("Error Debug: {:?}", other); - - if cfg!(debug_assertions) { - (StatusCode::INTERNAL_SERVER_ERROR, Some(other.to_string())) - } else { - (StatusCode::INTERNAL_SERVER_ERROR, None) - } - } - }; - - let body = Json(json!({ - "error": msg.or_else(|| status.canonical_reason().map(|r| r.to_string())), - "code": status.as_u16() - })); - - (status, body).into_response() - } -} diff --git a/radicle-httpd/src/api/json.rs b/radicle-httpd/src/api/json.rs deleted file mode 100644 index 2686d306..00000000 --- a/radicle-httpd/src/api/json.rs +++ /dev/null @@ -1,312 +0,0 @@ -//! Utilities for building JSON responses of our API. - -use std::collections::BTreeMap; -use std::path::Path; -use std::str; - -use base64::prelude::{Engine, BASE64_STANDARD}; -use radicle::cob::{CodeLocation, Reaction}; -use radicle::patch::ReviewId; -use serde_json::{json, Value}; - -use radicle::cob::issue::{Issue, IssueId}; -use radicle::cob::patch::{Merge, Patch, PatchId, Review}; -use radicle::cob::thread::{Comment, CommentId, Edit}; -use radicle::cob::{ActorId, Author}; -use radicle::git::RefString; -use radicle::node::{Alias, AliasStore}; -use radicle::prelude::NodeId; -use radicle::storage::{git, refs, RemoteRepository}; -use radicle_surf::blob::Blob; -use radicle_surf::tree::{EntryKind, Tree}; -use radicle_surf::{Commit, Oid}; - -use crate::api::auth::Session; - -/// Returns JSON of a commit. -pub(crate) fn commit(commit: &Commit) -> Value { - json!({ - "id": commit.id, - "author": { - "name": commit.author.name, - "email": commit.author.email - }, - "summary": commit.summary, - "description": commit.description(), - "parents": commit.parents, - "committer": { - "name": commit.committer.name, - "email": commit.committer.email, - "time": commit.committer.time.seconds() - } - }) -} - -/// Returns JSON of a session. -pub(crate) fn session(session_id: String, session: &Session) -> Value { - json!({ - "sessionId": session_id, - "status": session.status, - "publicKey": session.public_key, - "alias": session.alias, - "issuedAt": session.issued_at.unix_timestamp(), - "expiresAt": session.expires_at.unix_timestamp() - }) -} - -/// Returns JSON for a blob with a given `path`. -pub(crate) fn blob>(blob: &Blob, path: &str) -> Value { - json!({ - "binary": blob.is_binary(), - "name": name_in_path(path), - "content": blob_content(blob), - "path": path, - "lastCommit": commit(blob.commit()) - }) -} - -/// Returns a string for the blob content, encoded in base64 if binary. -pub fn blob_content>(blob: &Blob) -> String { - match str::from_utf8(blob.content()) { - Ok(s) => s.to_owned(), - Err(_) => BASE64_STANDARD.encode(blob.content()), - } -} - -/// Returns JSON for a tree with a given `path` and `stats`. -pub(crate) fn tree(tree: &Tree, path: &str) -> Value { - let prefix = Path::new(path); - let entries = tree - .entries() - .iter() - .map(|entry| { - json!({ - "path": prefix.join(entry.name()), - "oid": entry.object_id(), - "name": entry.name(), - "kind": match entry.entry() { - EntryKind::Tree(_) => "tree", - EntryKind::Blob(_) => "blob", - EntryKind::Submodule { .. } => "submodule" - }, - }) - }) - .collect::>(); - - json!({ - "entries": &entries, - "lastCommit": commit(tree.commit()), - "name": name_in_path(path), - "path": path, - }) -} - -/// Returns JSON for an `issue`. -pub(crate) fn issue(id: IssueId, issue: Issue, aliases: &impl AliasStore) -> Value { - json!({ - "id": id.to_string(), - "author": author(&issue.author(), aliases.alias(issue.author().id())), - "title": issue.title(), - "state": issue.state(), - "assignees": issue.assignees().map(|assignee| - author(&Author::from(*assignee.as_key()), aliases.alias(assignee)) - ).collect::>(), - "discussion": issue.comments().map(|(id, c)| issue_comment(id, c, aliases)).collect::>(), - "labels": issue.labels().collect::>(), - }) -} - -/// Returns JSON for a `patch`. -pub(crate) fn patch( - id: PatchId, - patch: Patch, - repo: &git::Repository, - aliases: &impl AliasStore, -) -> Value { - json!({ - "id": id.to_string(), - "author": author(patch.author(), aliases.alias(patch.author().id())), - "title": patch.title(), - "state": patch.state(), - "target": patch.target(), - "labels": patch.labels().collect::>(), - "merges": patch.merges().map(|(nid, m)| merge(nid, m, aliases)).collect::>(), - "assignees": patch.assignees().map(|assignee| - author(&Author::from(*assignee), aliases.alias(&assignee)) - ).collect::>(), - "revisions": patch.revisions().map(|(id, rev)| { - json!({ - "id": id, - "author": author(rev.author(), aliases.alias(rev.author().id())), - "description": rev.description(), - "edits": rev.edits().map(|e| edit(e, aliases)).collect::>(), - "reactions": rev.reactions().iter().flat_map(|(location, reaction)| { - reactions(reaction.iter().fold(BTreeMap::new(), |mut acc: BTreeMap<&Reaction, Vec<_>>, (author, emoji)| { - acc.entry(emoji).or_default().push(author); - acc - }), location.as_ref(), aliases) - }).collect::>(), - "base": rev.base(), - "oid": rev.head(), - "refs": get_refs(repo, patch.author().id(), &rev.head()).unwrap_or_default(), - "discussions": rev.discussion().comments().map(|(id, c)| { - patch_comment(id, c, aliases) - }).collect::>(), - "timestamp": rev.timestamp().as_secs(), - "reviews": patch.reviews_of(id).map(move |(id, r)| { - review(id, r, aliases) - }).collect::>(), - }) - }).collect::>(), - }) -} - -/// Returns JSON for a `reaction`. -fn reactions( - reactions: BTreeMap<&Reaction, Vec<&ActorId>>, - location: Option<&CodeLocation>, - aliases: &impl AliasStore, -) -> Vec { - reactions - .into_iter() - .map(|(emoji, authors)| { - if let Some(l) = location { - json!({ "location": l, "emoji": emoji, "authors": authors.into_iter().map(|a| - author(&Author::from(*a), aliases.alias(a)) - ).collect::>()}) - } else { - json!({ "emoji": emoji, "authors": authors.into_iter().map(|a| - author(&Author::from(*a), aliases.alias(a)) - ).collect::>()}) - } - }) - .collect::>() -} - -/// Returns JSON for an `author` and fills in `alias` when present. -pub(crate) fn author(author: &Author, alias: Option) -> Value { - match alias { - Some(alias) => json!({ - "id": author.id, - "alias": alias, - }), - None => json!(author), - } -} - -/// Returns JSON for a patch `Merge` and fills in `alias` when present. -fn merge(nid: &NodeId, merge: &Merge, aliases: &impl AliasStore) -> Value { - json!({ - "author": author(&Author::from(*nid), aliases.alias(nid)), - "commit": merge.commit, - "timestamp": merge.timestamp.as_secs(), - "revision": merge.revision, - }) -} - -/// Returns JSON for a patch `Review` and fills in `alias` when present. -fn review(id: &ReviewId, review: &Review, aliases: &impl AliasStore) -> Value { - let a = review.author(); - json!({ - "id": id, - "author": author(a, aliases.alias(a.id())), - "verdict": review.verdict(), - "summary": review.summary(), - "comments": review.comments().map(|(id, c)| review_comment(id, c, aliases)).collect::>(), - "timestamp": review.timestamp().as_secs(), - }) -} - -/// Returns JSON for an `Edit`. -fn edit(edit: &Edit, aliases: &impl AliasStore) -> Value { - json!({ - "author": author(&Author::from(edit.author), aliases.alias(&edit.author)), - "body": edit.body, - "timestamp": edit.timestamp.as_secs(), - "embeds": edit.embeds, - }) -} - -/// Returns JSON for a Issue `Comment`. -fn issue_comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -> Value { - json!({ - "id": *id, - "author": author(&Author::from(comment.author()), aliases.alias(&comment.author())), - "body": comment.body(), - "edits": comment.edits().map(|e| edit(e, aliases)).collect::>(), - "embeds": comment.embeds().to_vec(), - "reactions": reactions(comment.reactions(), None, aliases), - "timestamp": comment.timestamp().as_secs(), - "replyTo": comment.reply_to(), - "resolved": comment.is_resolved(), - }) -} - -/// Returns JSON for a Patch `Comment`. -fn patch_comment( - id: &CommentId, - comment: &Comment, - aliases: &impl AliasStore, -) -> Value { - json!({ - "id": *id, - "author": author(&Author::from(comment.author()), aliases.alias(&comment.author())), - "body": comment.body(), - "edits": comment.edits().map(|e| edit(e, aliases)).collect::>(), - "embeds": comment.embeds().to_vec(), - "reactions": reactions(comment.reactions(), None, aliases), - "timestamp": comment.timestamp().as_secs(), - "replyTo": comment.reply_to(), - "location": comment.location(), - "resolved": comment.is_resolved(), - }) -} - -/// Returns JSON for a `Review`. -fn review_comment( - id: &CommentId, - comment: &Comment, - aliases: &impl AliasStore, -) -> Value { - json!({ - "id": *id, - "author": author(&Author::from(comment.author()), aliases.alias(&comment.author())), - "body": comment.body(), - "edits": comment.edits().map(|e| edit(e, aliases)).collect::>(), - "embeds": comment.embeds().to_vec(), - "reactions": reactions(comment.reactions(), None, aliases), - "timestamp": comment.timestamp().as_secs(), - "replyTo": comment.reply_to(), - "location": comment.location(), - "resolved": comment.is_resolved(), - }) -} - -/// Returns the name part of a path string. -fn name_in_path(path: &str) -> &str { - match path.rsplit('/').next() { - Some(name) => name, - None => path, - } -} - -fn get_refs( - repo: &git::Repository, - id: &ActorId, - head: &Oid, -) -> Result, refs::Error> { - let remote = repo.remote(id)?; - let refs = remote - .refs - .iter() - .filter_map(|(name, o)| { - if o == head { - Some(name.to_owned()) - } else { - None - } - }) - .collect::>(); - - Ok(refs) -} diff --git a/radicle-httpd/src/api/v1.rs b/radicle-httpd/src/api/v1.rs deleted file mode 100644 index 3815091b..00000000 --- a/radicle-httpd/src/api/v1.rs +++ /dev/null @@ -1,71 +0,0 @@ -mod delegates; -mod node; -mod profile; -mod projects; -mod sessions; -mod stats; - -use axum::extract::State; -use axum::response::{IntoResponse, Json}; -use axum::routing::get; -use axum::Router; -use serde_json::json; - -use crate::api::{Context, API_VERSION, RADICLE_VERSION}; - -pub fn router(ctx: Context) -> Router { - let root_router = Router::new() - .route("/", get(root_handler)) - .with_state(ctx.clone()); - - let routes = Router::new() - .merge(root_router) - .merge(node::router(ctx.clone())) - .merge(profile::router(ctx.clone())) - .merge(sessions::router(ctx.clone())) - .merge(delegates::router(ctx.clone())) - .merge(projects::router(ctx.clone())) - .merge(stats::router(ctx)); - - Router::new().nest("/v1", routes) -} - -async fn root_handler(State(ctx): State) -> impl IntoResponse { - let response = json!({ - "message": "Welcome!", - "service": "radicle-httpd", - "version": format!("{}-{}", RADICLE_VERSION, env!("GIT_HEAD")), - "apiVersion": API_VERSION, - "nid": ctx.profile.public_key, - "path": "/api/v1", - "links": [ - { - "href": "/projects", - "rel": "projects", - "type": "GET" - }, - { - "href": "/node", - "rel": "node", - "type": "GET" - }, - { - "href": "/delegates/:did/projects", - "rel": "projects", - "type": "GET" - }, - { - "href": "/profile", - "rel": "profile", - "type": "GET" - }, - { - "href": "/stats", - "rel": "stats", - "type": "GET" - } - ] - }); - - Json(response) -} diff --git a/radicle-httpd/src/api/v1/delegates.rs b/radicle-httpd/src/api/v1/delegates.rs deleted file mode 100644 index bfd53bb5..00000000 --- a/radicle-httpd/src/api/v1/delegates.rs +++ /dev/null @@ -1,225 +0,0 @@ -use axum::extract::State; -use axum::response::IntoResponse; -use axum::routing::get; -use axum::{Json, Router}; - -use radicle::cob::Author; -use radicle::identity::Did; -use radicle::issue::cache::Issues as _; -use radicle::node::routing::Store; -use radicle::node::AliasStore; -use radicle::patch::cache::Patches as _; -use radicle::storage::{ReadRepository, ReadStorage}; - -use crate::api::error::Error; -use crate::api::json; -use crate::api::project::Info; -use crate::api::Context; -use crate::api::{PaginationQuery, ProjectQuery}; -use crate::axum_extra::{Path, Query}; - -pub fn router(ctx: Context) -> Router { - Router::new() - .route( - "/delegates/:delegate/projects", - get(delegates_projects_handler), - ) - .with_state(ctx) -} - -/// List all projects which delegate is a part of. -/// `GET /delegates/:delegate/projects` -async fn delegates_projects_handler( - State(ctx): State, - Path(delegate): Path, - Query(qs): Query, -) -> impl IntoResponse { - let PaginationQuery { - show, - page, - per_page, - } = qs; - let page = page.unwrap_or(0); - let per_page = per_page.unwrap_or(10); - let storage = &ctx.profile.storage; - let db = &ctx.profile.database()?; - let pinned = &ctx.profile.config.web.pinned; - let mut projects = match show { - ProjectQuery::All => storage - .repositories()? - .into_iter() - .filter(|repo| repo.doc.visibility.is_public()) - .collect::>(), - ProjectQuery::Pinned => storage.repositories_by_id(pinned.repositories.iter())?, - }; - projects.sort_by_key(|p| p.rid); - - let infos = projects - .into_iter() - .filter_map(|id| { - if !id.doc.delegates.iter().any(|d| *d == delegate) { - return None; - } - let Ok(repo) = storage.repository(id.rid) else { - return None; - }; - let Ok((_, head)) = repo.head() else { - return None; - }; - let Ok(payload) = id.doc.project() else { - return None; - }; - let Ok(issues) = ctx.profile.issues(&repo) else { - return None; - }; - let Ok(issues) = issues.counts() else { - return None; - }; - let Ok(patches) = ctx.profile.patches(&repo) else { - return None; - }; - let Ok(patches) = patches.counts() else { - return None; - }; - - let aliases = ctx.profile.aliases(); - let delegates = id - .doc - .delegates - .into_iter() - .map(|did| json::author(&Author::new(did), aliases.alias(did.as_key()))) - .collect::>(); - let seeding = db.count(&id.rid).unwrap_or_default(); - - Some(Info { - payload, - delegates, - threshold: id.doc.threshold, - visibility: id.doc.visibility, - head, - issues, - patches, - id: id.rid, - seeding, - }) - }) - .skip(page * per_page) - .take(per_page) - .collect::>(); - - Ok::<_, Error>(Json(infos)) -} - -#[cfg(test)] -mod routes { - use std::net::SocketAddr; - - use axum::extract::connect_info::MockConnectInfo; - use axum::http::StatusCode; - use serde_json::json; - - use crate::test::{self, get, HEAD, RID}; - - #[tokio::test] - async fn test_delegates_projects() { - let tmp = tempfile::tempdir().unwrap(); - let seed = test::seed(tmp.path()); - let app = super::router(seed.clone()) - .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080)))); - let response = get( - &app, - "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all", - ) - .await; - - assert_eq!( - response.status(), - StatusCode::OK, - "failed response: {:?}", - response.json().await - ); - assert_eq!( - response.json().await, - json!([ - { - "name": "hello-world", - "description": "Rad repository for tests", - "defaultBranch": "master", - "delegates": [ - { - "id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", - "alias": "seed" - } - ], - "threshold": 1, - "visibility": { - "type": "public" - }, - "head": HEAD, - "patches": { - "open": 1, - "draft": 0, - "archived": 0, - "merged": 0, - }, - "issues": { - "open": 1, - "closed": 0, - }, - "id": RID, - "seeding": 0, - }, - ]) - ); - - let app = super::router(seed).layer(MockConnectInfo(SocketAddr::from(( - [192, 168, 13, 37], - 8080, - )))); - let response = get( - &app, - "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all", - ) - .await; - - assert_eq!( - response.status(), - StatusCode::OK, - "failed response: {:?}", - response.json().await - ); - assert_eq!( - response.json().await, - json!([ - { - "name": "hello-world", - "description": "Rad repository for tests", - "defaultBranch": "master", - "delegates": [ - { - "id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", - "alias": "seed" - } - ], - "threshold": 1, - "visibility": { - "type": "public" - }, - "head": HEAD, - "patches": { - "open": 1, - "draft": 0, - "archived": 0, - "merged": 0, - }, - "issues": { - "open": 1, - "closed": 0, - }, - "id": RID, - "seeding": 0, - } - ]) - ); - } -} diff --git a/radicle-httpd/src/api/v1/node.rs b/radicle-httpd/src/api/v1/node.rs deleted file mode 100644 index 3e238238..00000000 --- a/radicle-httpd/src/api/v1/node.rs +++ /dev/null @@ -1,135 +0,0 @@ -use axum::extract::State; -use axum::response::IntoResponse; -use axum::routing::{get, put}; -use axum::{Json, Router}; -use axum_auth::AuthBearer; -use hyper::StatusCode; -use serde_json::json; - -use radicle::identity::RepoId; -use radicle::node::routing::Store; -use radicle::node::{ - policy::{Policy, SeedPolicy}, - AliasStore, Handle, NodeId, DEFAULT_TIMEOUT, -}; -use radicle::Node; - -use crate::api::error::Error; -use crate::api::{self, Context, PoliciesQuery, RADICLE_VERSION}; -use crate::axum_extra::{Path, Query}; - -pub fn router(ctx: Context) -> Router { - Router::new() - .route("/node", get(node_handler)) - .route("/node/policies/repos", get(node_policies_repos_handler)) - .route( - "/node/policies/repos/:rid", - put(node_policies_seed_handler).delete(node_policies_unseed_handler), - ) - .route("/nodes/:nid", get(nodes_handler)) - .route("/nodes/:nid/inventory", get(nodes_inventory_handler)) - .with_state(ctx) -} - -/// Return local node information. -/// `GET /node` -async fn node_handler(State(ctx): State) -> impl IntoResponse { - let node = Node::new(ctx.profile.socket()); - let node_id = ctx.profile.public_key; - let node_state = if node.is_running() { - "running" - } else { - "stopped" - }; - let config = match node.config() { - Ok(config) => Some(config), - Err(err) => { - tracing::error!("Error getting node config: {:#}", err); - None - } - }; - let response = json!({ - "id": node_id.to_string(), - "version": format!("{}-{}", RADICLE_VERSION, env!("GIT_HEAD")), - "config": config, - "state": node_state, - }); - - Ok::<_, Error>(Json(response)) -} - -/// Return stored information about other nodes. -/// `GET /nodes/:nid` -async fn nodes_handler(State(ctx): State, Path(nid): Path) -> impl IntoResponse { - let aliases = ctx.profile.aliases(); - let response = json!({ - "alias": aliases.alias(&nid), - }); - - Ok::<_, Error>(Json(response)) -} - -/// Return stored information about other nodes. -/// `GET /nodes/:nid/inventory` -async fn nodes_inventory_handler( - State(ctx): State, - Path(nid): Path, -) -> impl IntoResponse { - let db = &ctx.profile.database()?; - let resources = db.get_resources(&nid)?; - - Ok::<_, Error>(Json(resources)) -} - -/// Return local repo policies information. -/// `GET /node/policies/repos` -async fn node_policies_repos_handler(State(ctx): State) -> impl IntoResponse { - let policies = ctx.profile.policies()?; - let mut repos = Vec::new(); - - for SeedPolicy { rid: id, policy } in policies.seed_policies()? { - repos.push(json!({ - "id": id, - "scope": policy.scope().unwrap_or_default(), - "policy": Policy::from(policy), - })); - } - - Ok::<_, Error>(Json(repos)) -} - -/// Seed a new repo. -/// `PUT /node/policies/repos/:rid` -async fn node_policies_seed_handler( - State(ctx): State, - AuthBearer(token): AuthBearer, - Path(project): Path, - Query(qs): Query, -) -> impl IntoResponse { - api::auth::validate(&ctx, &token).await?; - let mut node = Node::new(ctx.profile.socket()); - node.seed(project, qs.scope.unwrap_or_default())?; - - if let Some(from) = qs.from { - let results = node.fetch(project, from, DEFAULT_TIMEOUT)?; - return Ok::<_, Error>(( - StatusCode::OK, - Json(json!({ "success": true, "results": results })), - )); - } - Ok::<_, Error>((StatusCode::OK, Json(json!({ "success": true })))) -} - -/// Unseed a repo. -/// `DELETE /node/policies/repos/:rid` -async fn node_policies_unseed_handler( - State(ctx): State, - AuthBearer(token): AuthBearer, - Path(project): Path, -) -> impl IntoResponse { - api::auth::validate(&ctx, &token).await?; - let mut node = Node::new(ctx.profile.socket()); - node.unseed(project)?; - - Ok::<_, Error>((StatusCode::OK, Json(json!({ "success": true })))) -} diff --git a/radicle-httpd/src/api/v1/profile.rs b/radicle-httpd/src/api/v1/profile.rs deleted file mode 100644 index 6b869af1..00000000 --- a/radicle-httpd/src/api/v1/profile.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::net::SocketAddr; - -use axum::extract::{ConnectInfo, State}; -use axum::response::IntoResponse; -use axum::routing::get; -use axum::{Json, Router}; -use serde_json::json; - -use crate::api::error::Error; -use crate::api::Context; - -pub fn router(ctx: Context) -> Router { - Router::new() - .route("/profile", get(profile_handler)) - .with_state(ctx) -} - -/// Return local profile information. -/// `GET /profile` -async fn profile_handler( - State(ctx): State, - ConnectInfo(addr): ConnectInfo, -) -> impl IntoResponse { - if !addr.ip().is_loopback() { - return Err(Error::Auth("Profile data is only shown for localhost")); - } - - Ok::<_, Error>(Json( - json!({ "config": ctx.profile.config, "home": ctx.profile.home.path() }), - )) -} - -#[cfg(test)] -mod routes { - use std::net::SocketAddr; - - use axum::extract::connect_info::MockConnectInfo; - use axum::http::StatusCode; - use serde_json::json; - - use crate::test::{self, get}; - - #[tokio::test] - async fn test_remote_profile() { - let tmp = tempfile::tempdir().unwrap(); - let seed = test::seed(tmp.path()); - let app = super::router(seed.clone()) - .layer(MockConnectInfo(SocketAddr::from(([192, 168, 1, 1], 8080)))); - let response = get(&app, "/profile").await; - - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - assert_eq!( - response.json().await, - json!({ - "error": "Profile data is only shown for localhost", - "code": 401 - }) - ) - } - - #[tokio::test] - async fn test_profile() { - let tmp = tempfile::tempdir().unwrap(); - let seed = test::seed(tmp.path()); - let app = super::router(seed.clone()) - .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080)))); - let response = get(&app, "/profile").await; - - assert_eq!(response.status(), StatusCode::OK); - assert_eq!( - response.json().await, - json!({ - "config": { - "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path", - "preferredSeeds": [ - "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776", - "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@ash.radicle.garden:8776" - ], - "web": { "pinned": { "repositories": [] } }, - "cli": { - "hints": true - }, - "node": { - "alias": "seed", - "listen": [], - "peers": { "type": "dynamic" }, - "connect": [], - "externalAddresses": [], - "network": "main", - "log": "INFO", - "relay": "auto", - "limits": { - "routingMaxSize": 1000, - "routingMaxAge": 604800, - "gossipMaxAge": 1209600, - "fetchConcurrency": 1, - "maxOpenFiles": 4096, - "rate": { - "inbound": { - "fillRate": 5.0, - "capacity": 1024 - }, - "outbound": { - "fillRate": 10.0, - "capacity": 2048 - } - }, - "connection": { - "inbound": 128, - "outbound": 16 - } - }, - "workers": 8, - "seedingPolicy": { - "default": "block", - } - } - }, - "home": seed.profile.path() - }) - ); - } -} diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs deleted file mode 100644 index 9efa7052..00000000 --- a/radicle-httpd/src/api/v1/projects.rs +++ /dev/null @@ -1,3565 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use axum::extract::{DefaultBodyLimit, State}; -use axum::handler::Handler; -use axum::http::{header, HeaderValue}; -use axum::response::{IntoResponse, Response}; -use axum::routing::{get, patch, post}; -use axum::{Json, Router}; -use axum_auth::AuthBearer; -use hyper::StatusCode; -use radicle_surf::blob::BlobRef; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use tower_http::set_header::SetResponseHeaderLayer; - -use radicle::cob::{ - issue, issue::cache::Issues as _, patch, patch::cache::Patches as _, resolve_embed, Author, - Embed, Label, Uri, -}; -use radicle::identity::{Did, RepoId}; -use radicle::node::routing::Store; -use radicle::node::{AliasStore, Node, NodeId}; -use radicle::storage::{ReadRepository, ReadStorage, RemoteRepository, WriteRepository}; -use radicle_surf::{diff, Glob, Oid, Repository}; - -use crate::api::error::Error; -use crate::api::project::Info; -use crate::api::{self, announce_refs, CobsQuery, Context, PaginationQuery, ProjectQuery}; -use crate::axum_extra::{immutable_response, Path, Query}; - -const CACHE_1_HOUR: &str = "public, max-age=3600, must-revalidate"; -const MAX_BODY_LIMIT: usize = 4_194_304; - -pub fn router(ctx: Context) -> Router { - Router::new() - .route("/projects", get(project_root_handler)) - .route("/projects/:project", get(project_handler)) - .route("/projects/:project/commits", get(history_handler)) - .route("/projects/:project/commits/:sha", get(commit_handler)) - .route("/projects/:project/diff/:base/:oid", get(diff_handler)) - .route( - "/projects/:project/activity", - get( - activity_handler.layer(SetResponseHeaderLayer::if_not_present( - header::CACHE_CONTROL, - |response: &Response| { - response - .status() - .is_success() - .then_some(HeaderValue::from_static(CACHE_1_HOUR)) - }, - )), - ), - ) - .route("/projects/:project/tree/:sha/", get(tree_handler_root)) - .route("/projects/:project/tree/:sha/*path", get(tree_handler)) - .route( - "/projects/:project/stats/tree/:sha", - get(stats_tree_handler), - ) - .route("/projects/:project/remotes", get(remotes_handler)) - .route("/projects/:project/remotes/:peer", get(remote_handler)) - .route("/projects/:project/blob/:sha/*path", get(blob_handler)) - .route("/projects/:project/readme/:sha", get(readme_handler)) - .route( - "/projects/:project/issues", - post(issue_create_handler).get(issues_handler), - ) - .route( - "/projects/:project/issues/:id", - patch(issue_update_handler).get(issue_handler), - ) - .route( - "/projects/:project/patches", - post(patch_create_handler).get(patches_handler), - ) - .route( - "/projects/:project/patches/:id", - patch(patch_update_handler).get(patch_handler), - ) - .with_state(ctx) - .layer(DefaultBodyLimit::max(MAX_BODY_LIMIT)) -} - -/// List all projects. -/// `GET /projects` -async fn project_root_handler( - State(ctx): State, - Query(qs): Query, -) -> impl IntoResponse { - let PaginationQuery { - show, - page, - per_page, - } = qs; - let page = page.unwrap_or(0); - let per_page = per_page.unwrap_or(10); - let storage = &ctx.profile.storage; - let db = &ctx.profile.database()?; - let pinned = &ctx.profile.config.web.pinned; - let policies = ctx.profile.policies()?; - - let mut projects = match show { - ProjectQuery::All => storage - .repositories()? - .into_iter() - .filter(|repo| repo.doc.visibility.is_public()) - .collect::>(), - ProjectQuery::Pinned => storage - .repositories_by_id(pinned.repositories.iter())? - .into_iter() - .filter(|repo| repo.doc.visibility.is_public()) - .collect::>(), - }; - projects.sort_by_key(|p| p.rid); - - let infos = projects - .into_iter() - .filter_map(|info| { - if !policies.is_seeding(&info.rid).unwrap_or_default() { - return None; - } - let Ok(repo) = storage.repository(info.rid) else { - return None; - }; - let Ok((_, head)) = repo.head() else { - return None; - }; - let Ok(payload) = info.doc.project() else { - return None; - }; - let Ok(issues) = ctx.profile.issues(&repo) else { - return None; - }; - let Ok(issues) = issues.counts() else { - return None; - }; - let Ok(patches) = ctx.profile.patches(&repo) else { - return None; - }; - let Ok(patches) = patches.counts() else { - return None; - }; - let aliases = ctx.profile.aliases(); - let delegates = info - .doc - .delegates - .into_iter() - .map(|did| api::json::author(&Author::new(did), aliases.alias(did.as_key()))) - .collect::>(); - let seeding = db.count(&info.rid).unwrap_or_default(); - - Some(Info { - payload, - delegates, - head, - threshold: info.doc.threshold, - visibility: info.doc.visibility, - issues, - patches, - id: info.rid, - seeding, - }) - }) - .skip(page * per_page) - .take(per_page) - .collect::>(); - - Ok::<_, Error>(Json(infos)) -} - -/// Get project metadata. -/// `GET /projects/:project` -async fn project_handler(State(ctx): State, Path(rid): Path) -> impl IntoResponse { - let (repo, doc) = ctx.repo(rid)?; - let info = ctx.project_info(&repo, doc)?; - - Ok::<_, Error>(Json(info)) -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct CommitsQueryString { - pub parent: Option, - pub since: Option, - pub until: Option, - pub page: Option, - pub per_page: Option, -} - -/// Get project commit range. -/// `GET /projects/:project/commits?parent=` -async fn history_handler( - State(ctx): State, - Path(rid): Path, - Query(qs): Query, -) -> impl IntoResponse { - let (repo, doc) = ctx.repo(rid)?; - let CommitsQueryString { - since, - until, - parent, - page, - per_page, - } = qs; - - // If the parent commit is provided, the response depends only on the query - // string and not on the state of the repository. This means we can instruct - // the caches to treat the response as immutable. - let is_immutable = parent.is_some(); - - let sha = match parent { - Some(commit) => commit, - None => ctx.project_info(&repo, doc)?.head.to_string(), - }; - let repo = Repository::open(repo.path())?; - - // If a pagination is defined, we do not want to paginate the commits, and we return all of them on the first page. - let page = page.unwrap_or(0); - let per_page = if per_page.is_none() && (since.is_some() || until.is_some()) { - usize::MAX - } else { - per_page.unwrap_or(30) - }; - - let commits = repo - .history(&sha)? - .filter_map(|commit| { - let commit = commit.ok()?; - let time = commit.committer.time.seconds(); - let commit = api::json::commit(&commit); - match (since, until) { - (Some(since), Some(until)) if time >= since && time < until => Some(commit), - (Some(since), None) if time >= since => Some(commit), - (None, Some(until)) if time < until => Some(commit), - (None, None) => Some(commit), - _ => None, - } - }) - .skip(page * per_page) - .take(per_page) - .collect::>(); - - if is_immutable { - Ok::<_, Error>(immutable_response(commits).into_response()) - } else { - Ok::<_, Error>(Json(commits).into_response()) - } -} - -/// Get project commit. -/// `GET /projects/:project/commits/:sha` -async fn commit_handler( - State(ctx): State, - Path((project, sha)): Path<(RepoId, Oid)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let repo = Repository::open(repo.path())?; - let commit = repo.commit(sha)?; - - let diff = repo.diff_commit(commit.id)?; - let glob = Glob::all_heads().branches().and(Glob::all_remotes()); - let branches: Vec = repo - .revision_branches(commit.id, glob)? - .iter() - .map(|b| b.refname().to_string()) - .collect(); - - let mut files: HashMap> = HashMap::new(); - diff.files().for_each(|file_diff| match file_diff { - diff::FileDiff::Added(added) => { - if let Ok(blob) = repo.blob_ref(added.new.oid) { - files.insert(blob.id(), blob); - } - } - diff::FileDiff::Deleted(deleted) => { - if let Ok(old_blob) = repo.blob_ref(deleted.old.oid) { - files.insert(old_blob.id(), old_blob); - } - } - diff::FileDiff::Modified(modified) => { - if let (Ok(old_blob), Ok(new_blob)) = ( - repo.blob_ref(modified.old.oid), - repo.blob_ref(modified.new.oid), - ) { - files.insert(old_blob.id(), old_blob); - files.insert(new_blob.id(), new_blob); - } - } - diff::FileDiff::Moved(moved) => { - if let (Ok(old_blob), Ok(new_blob)) = - (repo.blob_ref(moved.old.oid), repo.blob_ref(moved.new.oid)) - { - files.insert(old_blob.id(), old_blob); - files.insert(new_blob.id(), new_blob); - } - } - diff::FileDiff::Copied(copied) => { - if let (Ok(old_blob), Ok(new_blob)) = - (repo.blob_ref(copied.old.oid), repo.blob_ref(copied.new.oid)) - { - files.insert(old_blob.id(), old_blob); - files.insert(new_blob.id(), new_blob); - } - } - }); - - let response: serde_json::Value = json!({ - "commit": api::json::commit(&commit), - "diff": diff, - "files": files, - "branches": branches - }); - Ok::<_, Error>(immutable_response(response)) -} - -/// Get diff between two commits -/// `GET /projects/:project/diff/:base/:oid` -async fn diff_handler( - State(ctx): State, - Path((project, base, oid)): Path<(RepoId, Oid, Oid)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let repo = Repository::open(repo.path())?; - let base = repo.commit(base)?; - let commit = repo.commit(oid)?; - let diff = repo.diff(base.id, commit.id)?; - let mut files: HashMap> = HashMap::new(); - diff.files().for_each(|file_diff| match file_diff { - diff::FileDiff::Added(added) => { - if let Ok(new_blob) = repo.blob_ref(added.new.oid) { - files.insert(new_blob.id(), new_blob); - } - } - diff::FileDiff::Deleted(deleted) => { - if let Ok(old_blob) = repo.blob_ref(deleted.old.oid) { - files.insert(old_blob.id(), old_blob); - } - } - diff::FileDiff::Modified(modified) => { - if let (Ok(new_blob), Ok(old_blob)) = ( - repo.blob_ref(modified.old.oid), - repo.blob_ref(modified.new.oid), - ) { - files.insert(new_blob.id(), new_blob); - files.insert(old_blob.id(), old_blob); - } - } - diff::FileDiff::Moved(moved) => { - if let (Ok(new_blob), Ok(old_blob)) = - (repo.blob_ref(moved.new.oid), repo.blob_ref(moved.old.oid)) - { - files.insert(new_blob.id(), new_blob); - files.insert(old_blob.id(), old_blob); - } - } - diff::FileDiff::Copied(copied) => { - if let (Ok(new_blob), Ok(old_blob)) = - (repo.blob_ref(copied.new.oid), repo.blob_ref(copied.old.oid)) - { - files.insert(new_blob.id(), new_blob); - files.insert(old_blob.id(), old_blob); - } - } - }); - - let commits = repo - .history(commit.id)? - .take_while(|c| { - if let Ok(c) = c { - c.id != base.id - } else { - false - } - }) - .map(|r| r.map(|c| api::json::commit(&c))) - .collect::, _>>()?; - - let response = json!({ "diff": diff, "files": files, "commits": commits }); - - Ok::<_, Error>(immutable_response(response)) -} - -/// Get project activity for the past year. -/// `GET /projects/:project/activity` -async fn activity_handler( - State(ctx): State, - Path(project): Path, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let current_date = chrono::Utc::now().timestamp(); - // SAFETY: The number of weeks is static and not out of bounds. - #[allow(clippy::unwrap_used)] - let one_year_ago = chrono::Duration::try_weeks(52).unwrap(); - let repo = Repository::open(repo.path())?; - let head = repo.head()?; - let timestamps = repo - .history(head)? - .filter_map(|a| { - if let Ok(a) = a { - let seconds = a.committer.time.seconds(); - if seconds > current_date - one_year_ago.num_seconds() { - return Some(seconds); - } - } - None - }) - .collect::>(); - - Ok::<_, Error>((StatusCode::OK, Json(json!({ "activity": timestamps })))) -} - -/// Get project source tree for '/' path. -/// `GET /projects/:project/tree/:sha/` -async fn tree_handler_root( - State(ctx): State, - Path((rid, sha)): Path<(RepoId, Oid)>, -) -> impl IntoResponse { - tree_handler(State(ctx), Path((rid, sha, String::new()))).await -} - -/// Get project source tree. -/// `GET /projects/:project/tree/:sha/*path` -async fn tree_handler( - State(ctx): State, - Path((project, sha, path)): Path<(RepoId, Oid, String)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - - if let Some(ref cache) = ctx.cache { - let cache = &mut cache.tree.lock().await; - if let Some(response) = cache.get(&(project, sha, path.clone())) { - return Ok::<_, Error>(immutable_response(response.clone())); - } - } - - let repo = Repository::open(repo.path())?; - let tree = repo.tree(sha, &path)?; - let response = api::json::tree(&tree, &path); - - if let Some(cache) = &ctx.cache { - let cache = &mut cache.tree.lock().await; - cache.put((project, sha, path.clone()), response.clone()); - } - - Ok::<_, Error>(immutable_response(response)) -} - -/// Get project source tree stats. -/// `GET /projects/:project/stats/tree/:sha` -async fn stats_tree_handler( - State(ctx): State, - Path((project, sha)): Path<(RepoId, Oid)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let repo = Repository::open(repo.path())?; - let stats = repo.stats_from(&sha)?; - - Ok::<_, Error>(immutable_response(stats)) -} - -/// Get all project remotes. -/// `GET /projects/:project/remotes` -async fn remotes_handler( - State(ctx): State, - Path(project): Path, -) -> impl IntoResponse { - let (repo, doc) = ctx.repo(project)?; - let delegates = &doc.delegates; - let aliases = &ctx.profile.aliases(); - let remotes = repo - .remotes()? - .filter_map(|r| r.map(|r| r.1).ok()) - .map(|remote| { - let refs = remote - .refs - .iter() - .filter_map(|(r, oid)| { - r.as_str() - .strip_prefix("refs/heads/") - .map(|head| (head.to_string(), oid)) - }) - .collect::>(); - - match aliases.alias(&remote.id) { - Some(alias) => json!({ - "id": remote.id, - "alias": alias, - "heads": refs, - "delegate": delegates.contains(&remote.id.into()), - }), - None => json!({ - "id": remote.id, - "heads": refs, - "delegate": delegates.contains(&remote.id.into()), - }), - } - }) - .collect::>(); - - Ok::<_, Error>(Json(remotes)) -} - -/// Get project remote. -/// `GET /projects/:project/remotes/:peer` -async fn remote_handler( - State(ctx): State, - Path((project, node_id)): Path<(RepoId, NodeId)>, -) -> impl IntoResponse { - let (repo, doc) = ctx.repo(project)?; - let delegates = &doc.delegates; - let remote = repo.remote(&node_id)?; - let refs = remote - .refs - .iter() - .filter_map(|(r, oid)| { - r.as_str() - .strip_prefix("refs/heads/") - .map(|head| (head.to_string(), oid)) - }) - .collect::>(); - let remote = json!({ - "id": remote.id, - "heads": refs, - "delegate": delegates.contains(&remote.id.into()), - }); - - Ok::<_, Error>(Json(remote)) -} - -/// Get project source file. -/// `GET /projects/:project/blob/:sha/*path` -async fn blob_handler( - State(ctx): State, - Path((project, sha, path)): Path<(RepoId, Oid, String)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let repo = Repository::open(repo.path())?; - let blob = repo.blob(sha, &path)?; - - if blob.size() > MAX_BODY_LIMIT { - return Ok::<_, Error>( - ( - StatusCode::PAYLOAD_TOO_LARGE, - [(header::CACHE_CONTROL, "no-cache")], - Json(json!([])), - ) - .into_response(), - ); - } - Ok::<_, Error>(immutable_response(api::json::blob(&blob, &path)).into_response()) -} - -/// Get project readme. -/// `GET /projects/:project/readme/:sha` -async fn readme_handler( - State(ctx): State, - Path((project, sha)): Path<(RepoId, Oid)>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let repo = Repository::open(repo.path())?; - let paths = [ - "README", - "README.md", - "README.markdown", - "README.txt", - "README.rst", - "Readme.md", - ]; - - for path in paths - .iter() - .map(ToString::to_string) - .chain(paths.iter().map(|p| p.to_lowercase())) - { - if let Ok(blob) = repo.blob(sha, &path) { - if blob.size() > MAX_BODY_LIMIT { - return Ok::<_, Error>( - ( - StatusCode::PAYLOAD_TOO_LARGE, - [(header::CACHE_CONTROL, "no-cache")], - Json(json!([])), - ) - .into_response(), - ); - } - - return Ok::<_, Error>( - immutable_response(api::json::blob(&blob, &path)).into_response(), - ); - } - } - - Err(Error::NotFound) -} - -/// Get project issues list. -/// `GET /projects/:project/issues` -async fn issues_handler( - State(ctx): State, - Path(project): Path, - Query(qs): Query>, -) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; - let CobsQuery { - page, - per_page, - state, - } = qs; - let page = page.unwrap_or(0); - let per_page = per_page.unwrap_or(10); - let state = state.unwrap_or_default(); - let issues = ctx.profile.issues(&repo)?; - let mut issues: Vec<_> = issues - .list()? - .filter_map(|r| { - let (id, issue) = r.ok()?; - (state.matches(issue.state())).then_some((id, issue)) - }) - .collect::>(); - - issues.sort_by(|(_, a), (_, b)| b.timestamp().cmp(&a.timestamp())); - let aliases = &ctx.profile.aliases(); - let issues = issues - .into_iter() - .map(|(id, issue)| api::json::issue(id, issue, aliases)) - .skip(page * per_page) - .take(per_page) - .collect::>(); - - Ok::<_, Error>(Json(issues)) -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct IssueCreate { - pub title: String, - pub description: String, - pub labels: Vec