diff --git a/Cargo.lock b/Cargo.lock index 30c9c2a..f95a141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -72,6 +87,12 @@ version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "async-trait" version = "0.1.82" @@ -110,6 +131,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -149,6 +176,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "clap" version = "4.5.17" @@ -211,12 +251,27 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "dotenv" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -278,6 +333,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -285,6 +355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -293,6 +364,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -311,10 +410,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -324,8 +429,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -356,6 +463,7 @@ dependencies = [ "clap", "dotenv", "gitea-rs", + "octocrab", "serde", "tokio", "toml", @@ -462,6 +570,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "log", + "rustls 0.22.4", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.27.3" @@ -472,10 +599,23 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", + "rustls 0.23.13", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", "tower-service", ] @@ -515,6 +655,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -541,6 +704,16 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "iri-string" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f755bd3806e06ad4f366f92639415d99a339a2c7ecf8c26ccea2097c11cb6" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -562,6 +735,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -666,6 +854,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.4" @@ -675,6 +897,45 @@ dependencies = [ "memchr", ] +[[package]] +name = "octocrab" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9305e4c99543ecd0f42bd659c9e9d6ca7115fe5e37d5c85a7277b1db0d4c4101" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.22.1", + "bytes", + "cfg-if", + "chrono", + "either", + "futures", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls 0.26.0", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -754,6 +1015,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -798,6 +1069,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.86" @@ -831,7 +1108,7 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -841,7 +1118,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", + "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", "ipnet", @@ -903,6 +1180,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.13" @@ -916,13 +1207,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] @@ -964,6 +1268,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -1019,6 +1332,16 @@ 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_repr" version = "0.1.19" @@ -1075,6 +1398,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -1090,6 +1425,27 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.5.7" @@ -1172,6 +1528,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1182,6 +1558,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -1236,13 +1643,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -1305,8 +1723,30 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", + "tokio-util", "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", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -1430,6 +1870,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1580,6 +2021,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-registry" version = "0.2.0" diff --git a/crates/gitnow/Cargo.toml b/crates/gitnow/Cargo.toml index b897043..3e872bf 100644 --- a/crates/gitnow/Cargo.toml +++ b/crates/gitnow/Cargo.toml @@ -19,3 +19,4 @@ toml = "0.8.19" gitea-rs = { git = "https://git.front.kjuulh.io/kjuulh/gitea-rs", version = "1.22.1" } url = "2.5.2" +octocrab = "0.39.0" diff --git a/crates/gitnow/src/commands/root.rs b/crates/gitnow/src/commands/root.rs index d8896f9..8005b73 100644 --- a/crates/gitnow/src/commands/root.rs +++ b/crates/gitnow/src/commands/root.rs @@ -1,10 +1,4 @@ -use crate::{ - app::App, - git_provider::{ - gitea::GiteaProviderApp, github::GitHubProviderApp, GitProvider, VecRepositoryExt, - }, - projects_list::ProjectsListApp, -}; +use crate::{ app::App, projects_list::ProjectsListApp}; #[derive(Debug, Clone)] pub struct RootCommand { diff --git a/crates/gitnow/src/config.rs b/crates/gitnow/src/config.rs index c1db09c..7b20eaf 100644 --- a/crates/gitnow/src/config.rs +++ b/crates/gitnow/src/config.rs @@ -19,6 +19,14 @@ pub struct Providers { #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct GitHub { + #[serde(default)] + pub url: Option, + + pub access_token: GitHubAccessToken, + + #[serde(default)] + pub current_user: Option, + #[serde(default)] pub users: Vec, #[serde(default)] @@ -28,12 +36,38 @@ pub struct GitHub { #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct GitHubUser(String); +impl From for String { + fn from(value: GitHubUser) -> Self { + value.0 + } +} + +impl<'a> From<&'a GitHubUser> for &'a str { + fn from(value: &'a GitHubUser) -> Self { + value.0.as_str() + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct GitHubOrganisation(String); +impl From for String { + fn from(value: GitHubOrganisation) -> Self { + value.0 + } +} + +impl<'a> From<&'a GitHubOrganisation> for &'a str { + fn from(value: &'a GitHubOrganisation) -> Self { + value.0.as_str() + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Gitea { pub url: String, + + #[serde(default)] pub access_token: Option, #[serde(default)] @@ -52,6 +86,13 @@ pub enum GiteaAccessToken { Env { env: String }, } +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum GitHubAccessToken { + Direct(String), + Env { env: String }, +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct GiteaUser(String); @@ -138,11 +179,19 @@ mod test { github: vec![ GitHub { users: vec![GitHubUser("kjuulh".into())], - organisations: vec![GitHubOrganisation("lunarway".into())] + organisations: vec![GitHubOrganisation("lunarway".into())], + url: None, + access_token: GitHubAccessToken::Direct("some-token".into()), + current_user: Some("kjuulh".into()) }, GitHub { users: vec![GitHubUser("other".into())], - organisations: vec![GitHubOrganisation("org".into())] + organisations: vec![GitHubOrganisation("org".into())], + url: None, + access_token: GitHubAccessToken::Env { + env: "something".into() + }, + current_user: None } ], gitea: vec![ diff --git a/crates/gitnow/src/git_provider.rs b/crates/gitnow/src/git_provider.rs index fb6e30f..144ce78 100644 --- a/crates/gitnow/src/git_provider.rs +++ b/crates/gitnow/src/git_provider.rs @@ -1,6 +1,4 @@ -use std::{collections::HashMap, path::PathBuf, str::FromStr}; - -use async_trait::async_trait; +use std::path::PathBuf; #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct Repository { @@ -31,14 +29,5 @@ impl VecRepositoryExt for Vec { } } -#[async_trait] -pub trait GitProvider { - async fn list_repositories_for_user(&self, user: &str) -> anyhow::Result>; - async fn list_repositories_for_organisation( - &self, - organisation: &str, - ) -> anyhow::Result>; -} - pub mod gitea; pub mod github; diff --git a/crates/gitnow/src/git_provider/gitea.rs b/crates/gitnow/src/git_provider/gitea.rs index d8b9979..67e0f6e 100644 --- a/crates/gitnow/src/git_provider/gitea.rs +++ b/crates/gitnow/src/git_provider/gitea.rs @@ -1,11 +1,12 @@ use anyhow::Context; -use gitea_rs::apis::configuration::{ApiKey, Configuration}; +use gitea_rs::apis::configuration::Configuration; use url::Url; use crate::{app::App, config::GiteaAccessToken}; #[derive(Debug)] pub struct GiteaProvider { + #[allow(dead_code)] app: &'static App, } @@ -14,14 +15,12 @@ impl GiteaProvider { GiteaProvider { app } } - #[tracing::instrument(skip(self))] pub async fn list_repositories_for_current_user( &self, - user: &str, api: &str, access_token: Option<&GiteaAccessToken>, ) -> anyhow::Result> { - tracing::debug!("fetching gitea repositories for user"); + tracing::debug!("fetching gitea repositories for current user"); let config = self.get_config(api, access_token)?; @@ -29,7 +28,7 @@ impl GiteaProvider { let mut page = 1; loop { let mut repos = self - .list_repositories_for_current_user_with_page(user, &config, page) + .list_repositories_for_current_user_with_page(&config, page) .await?; if repos.is_empty() { @@ -65,10 +64,8 @@ impl GiteaProvider { Ok(provider.into()) } - #[tracing::instrument(skip(self))] async fn list_repositories_for_current_user_with_page( &self, - user: &str, config: &Configuration, page: usize, ) -> anyhow::Result> { @@ -80,14 +77,13 @@ impl GiteaProvider { Ok(repos) } - #[tracing::instrument(skip(self))] pub async fn list_repositories_for_user( &self, user: &str, api: &str, access_token: Option<&GiteaAccessToken>, ) -> anyhow::Result> { - tracing::debug!("fetching gitea repositories for user"); + tracing::debug!(user = user, "fetching gitea repositories for user"); let config = self.get_config(api, access_token)?; @@ -146,6 +142,10 @@ impl GiteaProvider { api: &str, access_token: Option<&GiteaAccessToken>, ) -> anyhow::Result> { + tracing::debug!( + organisation = organisation, + "fetching gitea repositories for organisation" + ); let config = self.get_config(api, access_token)?; let mut repositories = Vec::new(); diff --git a/crates/gitnow/src/git_provider/github.rs b/crates/gitnow/src/git_provider/github.rs index 4e6eb78..d681e28 100644 --- a/crates/gitnow/src/git_provider/github.rs +++ b/crates/gitnow/src/git_provider/github.rs @@ -1,10 +1,15 @@ -use async_trait::async_trait; +use anyhow::Context; +use octocrab::{ + auth::Auth, + models::{hooks::Config, Repository}, + params::repos::Sort, + NoSvc, Octocrab, Page, +}; -use crate::app::App; - -use super::GitProvider; +use crate::{app::App, config::GitHubAccessToken}; pub struct GitHubProvider { + #[allow(dead_code)] app: &'static App, } @@ -12,22 +17,159 @@ impl GitHubProvider { pub fn new(app: &'static App) -> GitHubProvider { GitHubProvider { app } } -} -#[async_trait] -impl GitProvider for GitHubProvider { - async fn list_repositories_for_user( + pub async fn list_repositories_for_current_user( &self, - user: &str, + url: Option<&String>, + access_token: &GitHubAccessToken, ) -> anyhow::Result> { - todo!() + tracing::debug!("fetching github repositories for current user"); + + let client = self.get_client(url, access_token)?; + + let current_page = client + .current() + .list_repos_for_authenticated_user() + .type_("all") + .per_page(100) + .sort("full_name") + .send() + .await?; + + let repos = self.unfold_pages(client, current_page).await?; + + Ok(repos + .into_iter() + .filter_map(|repo| { + Some(super::Repository { + provider: self.get_url(url), + owner: repo.owner.map(|su| su.login)?, + repo_name: repo.name, + ssh_url: repo.ssh_url?, + }) + }) + .collect()) } - async fn list_repositories_for_organisation( + pub async fn list_repositories_for_user( + &self, + user: &str, + url: Option<&String>, + access_token: &GitHubAccessToken, + ) -> anyhow::Result> { + tracing::debug!(user = user, "fetching github repositories for user"); + + let client = self.get_client(url, access_token)?; + + let current_page = client + .users(user) + .repos() + .r#type(octocrab::params::users::repos::Type::All) + .sort(Sort::FullName) + .per_page(100) + .send() + .await?; + + let repos = self.unfold_pages(client, current_page).await?; + + Ok(repos + .into_iter() + .filter_map(|repo| { + Some(super::Repository { + provider: self.get_url(url), + owner: repo.owner.map(|su| su.login)?, + repo_name: repo.name, + ssh_url: repo.ssh_url?, + }) + }) + .collect()) + } + + pub async fn list_repositories_for_organisation( &self, organisation: &str, + url: Option<&String>, + access_token: &GitHubAccessToken, ) -> anyhow::Result> { - todo!() + tracing::debug!( + organisation = organisation, + "fetching github repositories for organisation" + ); + + let client = self.get_client(url, access_token)?; + + let current_page = client + .orgs(organisation) + .list_repos() + .repo_type(Some(octocrab::params::repos::Type::All)) + .sort(Sort::FullName) + .per_page(100) + .send() + .await?; + + let repos = self.unfold_pages(client, current_page).await?; + + Ok(repos + .into_iter() + .filter_map(|repo| { + Some(super::Repository { + provider: self.get_url(url), + owner: repo.owner.map(|su| su.login)?, + repo_name: repo.name, + ssh_url: repo.ssh_url?, + }) + }) + .collect()) + } + + async fn unfold_pages( + &self, + client: octocrab::Octocrab, + page: Page, + ) -> anyhow::Result> { + let mut current_page = page; + + let mut repos = current_page.take_items(); + while let Ok(Some(mut new_page)) = client.get_page(¤t_page.next).await { + repos.extend(new_page.take_items()); + + current_page = new_page; + } + + Ok(repos) + } + + fn get_url(&self, url: Option<&String>) -> String { + let default_domain = "github.com".to_string(); + + if let Some(url) = url { + let Some(url) = url::Url::parse(url).ok() else { + return default_domain; + }; + + let Some(domain) = url.domain().map(|d| d.to_string()) else { + return default_domain; + }; + + domain + } else { + default_domain + } + } + + fn get_client( + &self, + url: Option<&String>, + access_token: &GitHubAccessToken, + ) -> anyhow::Result { + let client = octocrab::Octocrab::builder() + .personal_token(match access_token { + GitHubAccessToken::Direct(token) => token.to_owned(), + GitHubAccessToken::Env { env } => std::env::var(env)?, + }) + .build()?; + + Ok(client) } } diff --git a/crates/gitnow/src/projects_list.rs b/crates/gitnow/src/projects_list.rs index 54edb89..ac41fbe 100644 --- a/crates/gitnow/src/projects_list.rs +++ b/crates/gitnow/src/projects_list.rs @@ -1,6 +1,8 @@ use crate::{ app::App, - git_provider::{gitea::GiteaProviderApp, Repository, VecRepositoryExt}, + git_provider::{ + gitea::GiteaProviderApp, github::GitHubProviderApp, Repository, VecRepositoryExt, + }, }; pub struct ProjectsList { @@ -16,6 +18,7 @@ impl ProjectsList { let mut repositories = Vec::new(); repositories.extend(self.get_gitea_projects().await?); + repositories.extend(self.get_github_projects().await?); repositories.collect_unique(); @@ -27,13 +30,9 @@ impl ProjectsList { let mut repositories = Vec::new(); for gitea in self.app.config.providers.gitea.iter() { - if let Some(user) = &gitea.current_user { + if let Some(_user) = &gitea.current_user { let mut repos = gitea_provider - .list_repositories_for_current_user( - user, - &gitea.url, - gitea.access_token.as_ref(), - ) + .list_repositories_for_current_user(&gitea.url, gitea.access_token.as_ref()) .await?; repositories.append(&mut repos); @@ -66,6 +65,47 @@ impl ProjectsList { Ok(repositories) } + + async fn get_github_projects(&self) -> anyhow::Result> { + let github_provider = self.app.github_provider(); + + let mut repositories = Vec::new(); + for github in self.app.config.providers.github.iter() { + if let Some(_user) = &github.current_user { + let mut repos = github_provider + .list_repositories_for_current_user(github.url.as_ref(), &github.access_token) + .await?; + + repositories.append(&mut repos); + } + + for github_user in github.users.iter() { + let mut repos = github_provider + .list_repositories_for_user( + github_user.into(), + github.url.as_ref(), + &github.access_token, + ) + .await?; + + repositories.append(&mut repos); + } + + for github_org in github.organisations.iter() { + let mut repos = github_provider + .list_repositories_for_organisation( + github_org.into(), + github.url.as_ref(), + &github.access_token, + ) + .await?; + + repositories.append(&mut repos); + } + } + + Ok(repositories) + } } pub trait ProjectsListApp {