diff --git a/.gitignore b/.gitignore index a44616b..b2147d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target .idea logs/** -config/** \ No newline at end of file +config/** +data/** \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7212863..9d1647a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -724,132 +724,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "background-jobs" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a10cb6081f857fdad834d8b7fa588bc4637e28afb3f19b79eee3b24c194c6b0" -dependencies = [ - "background-jobs-actix", - "background-jobs-core", - "background-jobs-metrics", - "background-jobs-postgres", - "background-jobs-sled", - "background-jobs-tokio", -] - -[[package]] -name = "background-jobs-actix" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a65756883718c1c7c09882ae4b02dfbe7fe0bf2c4ea048e96a5c5e7a14ba237" -dependencies = [ - "actix-rt", - "async-trait", - "background-jobs-core", - "metrics", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "background-jobs-core" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e50ca661b42d735dc16745f1cda817d7b6f175a2ff98a1308d7de2aca326f" -dependencies = [ - "async-trait", - "event-listener", - "metrics", - "serde", - "serde_json", - "thiserror 1.0.69", - "time", - "tracing", - "uuid", -] - -[[package]] -name = "background-jobs-metrics" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f8df02e12efea214f90b8a4cb24da832cd92ade113ecaebeac0d7e8135125" -dependencies = [ - "async-trait", - "background-jobs-core", - "metrics", - "metrics-util", - "tracing", - "uuid", -] - -[[package]] -name = "background-jobs-postgres" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3389d127a99cec569f475dfeaa6bfdda2a100265473f0bc8326c17c714f9d08b" -dependencies = [ - "async-trait", - "background-jobs-core", - "barrel", - "dashmap", - "deadpool 0.9.5", - "diesel", - "diesel-async", - "diesel-derive-enum", - "flume", - "futures-core", - "metrics", - "pin-project-lite", - "refinery", - "serde_json", - "time", - "tokio", - "tokio-postgres", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "background-jobs-sled" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac884579e71bf086fbd1c5f31549697955209f3fd45777a998dd26a56cda495" -dependencies = [ - "async-trait", - "background-jobs-core", - "bincode", - "serde", - "serde_cbor", - "sled", - "thiserror 1.0.69", - "time", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "background-jobs-tokio" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6d5cee6c6fecc4c4b66af9f7734529a9990386cff5bdc20289ca6131a376b3" -dependencies = [ - "async-trait", - "background-jobs-core", - "metrics", - "serde", - "serde_json", - "tokio", - "tracing", - "uuid", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -865,12 +739,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "barrel" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9e605929a6964efbec5ac0884bd0fe93f12a3b1eb271f52c251316640c68d9" - [[package]] name = "base16ct" version = "0.2.0" @@ -920,15 +788,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bit-vec" version = "0.6.3" @@ -1173,12 +1032,52 @@ dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "clap" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "colorchoice" version = "1.0.3" @@ -1475,37 +1374,45 @@ dependencies = [ ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "darling" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.10", + "darling_core", + "darling_macro", ] [[package]] -name = "data-encoding" -version = "2.6.0" +name = "darling_core" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.96", +] [[package]] -name = "deadpool" -version = "0.9.5" +name = "darling_macro" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "async-trait", - "deadpool-runtime", - "num_cpus", - "retain_mut", - "tokio", + "darling_core", + "quote", + "syn 2.0.96", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "deadpool" version = "0.12.1" @@ -1524,7 +1431,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ed4f481f6a4770b95e09b91e183ee5ed6e1d4a34c0b09814012b3ee5e585f70" dependencies = [ - "deadpool 0.12.1", + "deadpool", "redis", "serde", ] @@ -1613,69 +1520,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "diesel" -version = "2.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" -dependencies = [ - "bitflags 2.7.0", - "byteorder", - "diesel_derives", - "itoa", - "serde_json", - "time", - "uuid", -] - -[[package]] -name = "diesel-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acada1517534c92d3f382217b485db8a8638f111b0e3f2a2a8e26165050f77be" -dependencies = [ - "async-trait", - "deadpool 0.9.5", - "diesel", - "futures-util", - "scoped-futures", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "diesel-derive-enum" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81c5131a2895ef64741dad1d483f358c2a229a3a2d1b256778cdc5e146db64d4" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "diesel_derives" -version = "2.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14701062d6bed917b5c7103bdffaee1e4609279e240488ad24e7bd979ca6866c" -dependencies = [ - "diesel_table_macro_syntax", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" -dependencies = [ - "syn 2.0.96", -] - [[package]] name = "digest" version = "0.10.7" @@ -1705,7 +1549,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" dependencies = [ - "clap", + "clap 2.34.0", ] [[package]] @@ -1808,12 +1652,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "env_filter" version = "0.1.3" @@ -1881,12 +1719,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fastrand" version = "2.3.0" @@ -1933,7 +1765,6 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "nanorand", "spin", ] @@ -1945,7 +1776,7 @@ checksum = "cf5efcf77a4da27927d3ab0509dec5b0954bb3bc59da5a1de9e52642ebd4cdf9" dependencies = [ "ahash 0.8.11", "num_cpus", - "parking_lot 0.12.3", + "parking_lot", "seize", ] @@ -1985,16 +1816,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "funty" version = "2.0.0" @@ -2051,7 +1872,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.3", + "parking_lot", ] [[package]] @@ -2120,15 +1941,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2214,6 +2026,7 @@ dependencies = [ "pin-project", "prost", "sea-orm", + "sea-orm-migration", "serde", "serde_json", "serde_yaml", @@ -2247,8 +2060,6 @@ dependencies = [ "apalis", "apalis-cron", "apalis-sql", - "background-jobs", - "background-jobs-actix", "base64 0.22.1", "chrono", "deadpool-redis", @@ -2256,6 +2067,7 @@ dependencies = [ "lettre", "rand", "sea-orm", + "sea-orm-migration", "serde", "serde_json", "sha256", @@ -2798,6 +2610,12 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -2866,15 +2684,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-uring" version = "0.6.4" @@ -3129,35 +2938,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "metrics" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" -dependencies = [ - "ahash 0.8.11", - "portable-atomic", -] - -[[package]] -name = "metrics-util" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" -dependencies = [ - "aho-corasick", - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.14.5", - "indexmap 2.7.0", - "metrics", - "num_cpus", - "ordered-float 4.6.0", - "quanta", - "radix_trie", - "sketches-ddsketch", -] - [[package]] name = "mime" version = "0.3.17" @@ -3207,15 +2987,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "native-tls" version = "0.2.12" @@ -3233,15 +3004,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - [[package]] name = "nom" version = "7.1.3" @@ -3485,15 +3247,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - [[package]] name = "ouroboros" version = "0.18.5" @@ -3581,18 +3334,7 @@ dependencies = [ name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -3601,21 +3343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -3626,7 +3354,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -3672,24 +3400,6 @@ dependencies = [ "indexmap 2.7.0", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "1.1.8" @@ -3789,59 +3499,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[package]] -name = "postgres" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c918733159f4d55d2ceb262950f00b0aebd6af4aa97b5a47bb0655120475ed" -dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator", - "hmac", - "md-5", - "memchr", - "rand", - "sha2", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", - "serde", - "serde_json", - "time", - "uuid", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -4010,21 +3667,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "quanta" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi", - "web-sys", - "winapi", -] - [[package]] name = "quote" version = "1.0.38" @@ -4046,16 +3688,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.8.5" @@ -4086,15 +3718,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "raw-cpuid" -version = "11.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" -dependencies = [ - "bitflags 2.7.0", -] - [[package]] name = "rayon" version = "1.10.0" @@ -4141,15 +3764,6 @@ dependencies = [ "url", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.8" @@ -4159,52 +3773,6 @@ dependencies = [ "bitflags 2.7.0", ] -[[package]] -name = "refinery" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0904191f0566c3d3e0091d5cc8dec22e663d77def2d247b16e7a438b188bf75d" -dependencies = [ - "refinery-core", - "refinery-macros", -] - -[[package]] -name = "refinery-core" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf253999e1899ae476c910b994959e341d84c4389ba9533d3dacbe06df04825" -dependencies = [ - "async-trait", - "cfg-if", - "log", - "postgres", - "regex", - "serde", - "siphasher", - "thiserror 1.0.69", - "time", - "tokio", - "tokio-postgres", - "toml", - "url", - "walkdir", -] - -[[package]] -name = "refinery-macros" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd81f69687fe8a1fa10995108b3ffc7cdbd63e682a4f8fbfd1020130780d7e17" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "refinery-core", - "regex", - "syn 2.0.96", -] - [[package]] name = "regex" version = "1.11.1" @@ -4264,12 +3832,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "rfc6979" version = "0.4.0" @@ -4610,15 +4172,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" version = "0.1.27" @@ -4628,15 +4181,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "scoped-futures" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" -dependencies = [ - "pin-project-lite", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -4695,6 +4239,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "sea-orm-cli" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e6e0e741bfdf434e6f6aadab156ba4d439e78c9449048698d98fa377871224a" +dependencies = [ + "chrono", + "clap 4.5.26", + "dotenvy", + "glob", + "regex", + "sea-schema", + "tracing", + "tracing-subscriber", + "url", +] + [[package]] name = "sea-orm-macros" version = "1.1.4" @@ -4709,6 +4270,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sea-orm-migration" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bb76ba314552ce15e3a24778cf9c116fc1225fa406e48b0a36e5a3cdbc1e21" +dependencies = [ + "async-trait", + "clap 4.5.26", + "dotenvy", + "futures", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + [[package]] name = "sea-query" version = "0.32.1" @@ -4718,8 +4296,9 @@ dependencies = [ "bigdecimal", "chrono", "inherent", - "ordered-float 3.9.2", + "ordered-float", "rust_decimal", + "sea-query-derive", "serde_json", "time", "uuid", @@ -4741,6 +4320,43 @@ dependencies = [ "uuid", ] +[[package]] +name = "sea-query-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.96", + "thiserror 1.0.69", +] + +[[package]] +name = "sea-schema" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef5dd7848c993f3789d09a2616484c72c9330cae2b048df59d8c9b8c0343e95" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "seahash" version = "4.1.0" @@ -4818,16 +4434,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_cbor_2" version = "0.12.0-dev" @@ -4993,18 +4599,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" - [[package]] name = "slab" version = "0.4.9" @@ -5014,22 +4608,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -5378,6 +4956,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -5597,12 +5181,11 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot 0.12.3", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.8", "tokio-macros", - "tracing", "windows-sys 0.52.0", ] @@ -5627,32 +5210,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot 0.12.3", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand", - "socket2 0.5.8", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-retry" version = "0.3.0" @@ -5873,7 +5430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "parking_lot 0.12.3", + "parking_lot", "thiserror 1.0.69", "time", "tracing-subscriber", @@ -6143,16 +5700,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -6276,9 +5823,8 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.8", + "redox_syscall", "wasite", - "web-sys", ] [[package]] @@ -6297,15 +5843,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 406e236..29ebca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ tokio = "1" pin-project = "1" http-body = "1.0.1" sea-orm = { version = "1", features = ["sqlx-all", "runtime-tokio"] } +sea-orm-migration = { version = "1", features = ["runtime-tokio","sqlx-postgres", "cli"] } uuid = { version = "1.11.0", features = ["serde","v4"] } chrono = { version = "0.4.39", features = ["clock","std","serde"] } serde_json = "1" diff --git a/gitdata-apis/Cargo.toml b/gitdata-apis/Cargo.toml index 358715a..77d285c 100644 --- a/gitdata-apis/Cargo.toml +++ b/gitdata-apis/Cargo.toml @@ -18,14 +18,13 @@ actix-session = { version = "0.10.1", features = ["redis-pool","redis-session"," actix-session-ext = { version = "0.1.0", features = [] } actix-identity = { version = "0.8.0", features = [] } actix-settings = { version = "0.8.0", features = [] } -background-jobs = { version = "0.19.0", features = ["tokio","sled","postgres"] } -background-jobs-actix = { version = "0.19.0", features = [] } deadpool-redis = { version = "~0.16.0", features = ["rt_tokio_1","cluster","serde"] } anyhow = { version = "1", features = ["backtrace", "std"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = { version = "1.0.134", features = ["alloc"] } tonic = { version = "0.12.3", features = ["default"] } sea-orm = { version = "1", features = ["sqlx-all","runtime-tokio"] } +sea-orm-migration = { version = "1", features = ["runtime-tokio","sqlx-postgres","cli","default"] } sha256 = { version = "1.5.0", features = ["tokio"] } lettre = {version = "0.11.10",features = [ "smtp-transport", diff --git a/gitdata-apis/config/rpc.toml b/gitdata-apis/config/rpc.toml new file mode 100644 index 0000000..51b9146 --- /dev/null +++ b/gitdata-apis/config/rpc.toml @@ -0,0 +1,17 @@ +[[nodes]] +[nodes.Health] +node_id = "Health-0" +host ="http://127.0.0.1" +port = 50051 + +[[nodes]] +[nodes.CoreGit] +node_id = "CoreGit-0" +host = "http://127.0.0.1" +port = 50051 + +[[nodes]] +[nodes.GitCore] +node_id = "GitCore-0" +host = "http://127.0.0.1" +port = 50051 diff --git a/gitdata-apis/src/apis/app_router.rs b/gitdata-apis/src/apis/app_router.rs index 72cfcf3..2494671 100644 --- a/gitdata-apis/src/apis/app_router.rs +++ b/gitdata-apis/src/apis/app_router.rs @@ -1,46 +1,101 @@ +use actix_web::web::delete; +use actix_web::web::get; use actix_web::web::post; use actix_web::web::scope; -use super::v1::auth::logout::api_v1_auth_logout; -use super::v1::auth::passwd::api_v1_auth_passwd; -use super::v1::email::captcha::api_v1_email_captcha_check; -use super::v1::email::captcha::api_v1_email_captcha_post; - #[allow(non_snake_case)] -pub async fn AppRouter(cfg : &mut actix_web::web::ServiceConfig) { +pub fn AppRouter(cfg : &mut actix_web::web::ServiceConfig) { cfg.service( scope("/api").service( scope("/v1") .service( scope("/auth") - .route("/passwd", post().to(api_v1_auth_passwd)) - .route("/logout", post().to(api_v1_auth_logout)), + .route( + "/passwd", + post().to(crate::apis::v1::auth::passwd::api_v1_auth_passwd), + ) + .route( + "/logout", + post().to(crate::apis::v1::auth::logout::api_v1_auth_logout), + ), ) .service( scope("/email") - .route("/captcha", post().to(api_v1_email_captcha_post)) - .route("/captcha/check", post().to(api_v1_email_captcha_check)), + .route( + "/captcha", + post().to(crate::apis::v1::email::captcha::api_v1_email_captcha_post), + ) + .route( + "/captcha/check", + post().to(crate::apis::v1::email::captcha::api_v1_email_captcha_check), + ), ) .service( - scope("/user").service( - scope("/setting") - .route( - "/basic", - post().to(super::v1::user::setting::api_v1_user_setting_basic), - ) - .route( - "/topic", - post().to(super::v1::user::setting::api_v1_user_setting_topic), - ) - .route( - "/avatar", - post().to(super::v1::user::setting::api_v1_user_setting_avatar), - ) - .route( - "/pined", - post().to(super::v1::user::setting::api_v1_user_setting_pinned), - ), - ), + scope("/user") + .service( + scope("/setting") + .route( + "/basic", + post().to(super::v1::user::setting::api_v1_user_setting_basic), + ) + .route( + "/topic", + post().to(super::v1::user::setting::api_v1_user_setting_topic), + ) + .route( + "/avatar", + post().to(super::v1::user::setting::api_v1_user_setting_avatar), + ) + .route( + "/pined", + post().to(super::v1::user::setting::api_v1_user_setting_pinned), + ), + ) + .route( + "/apply", + post().to(super::v1::user::apply::api_v1_user_apply), + ) + .service( + scope("/info") + .route("", get().to(super::v1::user::info::api_v1_users_info)) + .route( + "/{username}", + get().to(super::v1::user::info::api_v1_users_info_by_username), + ), + ) + .service( + scope("/ssh") + .route( + "", + get().to(super::v1::user::ssh_keys::api_v1_user_ssh_key_list), + ) + .route( + "/{ssh_key_uid}", + delete() + .to(super::v1::user::ssh_keys::api_v1_user_ssh_key_delete), + ) + .route( + "", + post() + .to(super::v1::user::ssh_keys::api_v1_user_ssh_key_create), + ), + ) + .service( + scope("token") + .route( + "", + post().to(super::v1::user::token_key::api_v1_user_token_create), + ) + .route( + "/{token_uid}", + delete() + .to(super::v1::user::token_key::api_v1_user_token_delete), + ) + .route( + "", + get().to(super::v1::user::token_key::api_v1_user_token_list), + ), + ), ), ), ); diff --git a/gitdata-apis/src/apis/v1/auth/passwd.rs b/gitdata-apis/src/apis/v1/auth/passwd.rs index 4392c83..5b825be 100644 --- a/gitdata-apis/src/apis/v1/auth/passwd.rs +++ b/gitdata-apis/src/apis/v1/auth/passwd.rs @@ -4,6 +4,7 @@ use actix_web::HttpMessage; use actix_web::HttpRequest; use actix_web::Responder; use actix_web::web; +use sha256::Sha256Digest; use crate::apis::app_writer::AppWrite; use crate::service::AppState; @@ -23,6 +24,7 @@ pub async fn api_v1_auth_passwd( return AppWrite::fail(err.to_string()); } }; + dbg!(inner.clone().password.digest()); match app_state.auth_by_passwd(inner).await { Ok(token) => { session.insert("token", token.clone()).unwrap(); diff --git a/gitdata-apis/src/apis/v1/user/ssh_keys.rs b/gitdata-apis/src/apis/v1/user/ssh_keys.rs index c4a42cc..1c14f79 100644 --- a/gitdata-apis/src/apis/v1/user/ssh_keys.rs +++ b/gitdata-apis/src/apis/v1/user/ssh_keys.rs @@ -33,7 +33,7 @@ pub async fn api_v1_user_ssh_key_create( /// /api/v1/user/ssh DELETE pub async fn api_v1_user_ssh_key_delete( session : Session, - param : web::Json, + param : web::Query, app_state : web::Data, ) -> impl Responder { let ident = match UsersInfoReplay::from_session(session) { diff --git a/gitdata-apis/src/jobs/email.rs b/gitdata-apis/src/jobs/email.rs index 57266be..4207b88 100644 --- a/gitdata-apis/src/jobs/email.rs +++ b/gitdata-apis/src/jobs/email.rs @@ -1,8 +1,18 @@ use std::io; +use gitdata::config::email::EmailConfig; +use lettre::AsyncSmtpTransport; +use lettre::AsyncTransport; +use lettre::Message; +use lettre::Tokio1Executor; use lettre::message::Mailbox; +use lettre::message::header::ContentType; +use lettre::transport::smtp::authentication::Credentials; use serde::Deserialize; use serde::Serialize; +use tokio::sync::OnceCell; +use tracing::error; +use tracing::info; use crate::public::CAPTCHA; use crate::public::USER_FOR_GET_PASSWD; @@ -14,6 +24,7 @@ pub struct EmailJobs { pub to : Mailbox, pub subject : String, pub body : EmailType, + pub code : String, } impl EmailJobs { @@ -23,6 +34,7 @@ impl EmailJobs { to : Mailbox, subject : String, body : EmailType, + code : String, ) -> Self { EmailJobs { from, @@ -30,12 +42,25 @@ impl EmailJobs { to, subject, body, + code, } } } -pub async fn send_email(job : EmailJobs) -> Result<(), io::Error> { - dbg!(job); +pub async fn send_email(tx : EmailJobs) -> Result<(), io::Error> { + let email_server = EmailServer::get().await; + let email = Message::builder() + .from(tx.from) + .reply_to(tx.reply) + .to(tx.to.clone()) + .subject(tx.subject) + .header(ContentType::TEXT_HTML) + .body(tx.body.to_email().replace("123456", &tx.code)) + .unwrap(); + match email_server.cred.send(email).await { + Ok(_) => info!("Email sent {} successfully!", tx.to.to_string()), + Err(e) => error!("Could not send email: {e:?}"), + } Ok(()) } @@ -53,3 +78,29 @@ impl EmailType { } } } + +static EMAIL : OnceCell = OnceCell::const_new(); + +#[derive(Clone)] +struct EmailServer { + cred : AsyncSmtpTransport, +} + +impl EmailServer { + pub fn init() -> EmailServer { + let config = EmailConfig::load().expect("Failed to load email config"); + let creds = Credentials::new(config.username.to_owned(), config.password.to_owned()); + let mailer : AsyncSmtpTransport = + AsyncSmtpTransport::::relay(&config.smtp) + .unwrap() + .credentials(creds) + .build(); + EmailServer { cred : mailer } + } + pub async fn get() -> Self { + EMAIL + .get_or_init(|| async { EmailServer::init() }) + .await + .clone() + } +} diff --git a/gitdata-apis/src/jobs/mod.rs b/gitdata-apis/src/jobs/mod.rs index aa5f45d..3815311 100644 --- a/gitdata-apis/src/jobs/mod.rs +++ b/gitdata-apis/src/jobs/mod.rs @@ -1 +1,2 @@ pub mod email; +pub mod sync_repo; \ No newline at end of file diff --git a/gitdata-apis/src/jobs/sync_repo.rs b/gitdata-apis/src/jobs/sync_repo.rs new file mode 100644 index 0000000..a4ff508 --- /dev/null +++ b/gitdata-apis/src/jobs/sync_repo.rs @@ -0,0 +1,41 @@ +use crate::service::AppState; +use apalis::prelude::Data; +use serde::{Deserialize, Serialize}; +use std::io; + +#[derive(Clone,Deserialize,Serialize,Debug)] +pub struct SyncRepoMessage { + pub node: String, + pub path: String, +} + +pub(crate) async fn sync_repo( + message: SyncRepoMessage, + data: Data, +) -> io::Result<()> { + SyncRepo{ + node: message.node, + path: message.path, + state: data, + }.run().await + .map_err(|x| { + io::Error::new(io::ErrorKind::Other, x) + })?; + Ok(()) +} + +pub struct SyncRepo { + pub node: String, + pub path: String, + pub state: Data, +} + + +impl SyncRepo { + + pub async fn run(self) -> anyhow::Result<()> { + + + Ok(()) + } +} \ No newline at end of file diff --git a/gitdata-apis/src/lib.rs b/gitdata-apis/src/lib.rs index f2ab957..6d060b4 100644 --- a/gitdata-apis/src/lib.rs +++ b/gitdata-apis/src/lib.rs @@ -3,5 +3,4 @@ pub mod fields; pub mod jobs; pub mod middleware; pub mod public; -pub mod rpc_server; pub mod service; diff --git a/gitdata-apis/src/main.rs b/gitdata-apis/src/main.rs index 5d48ac5..44fb062 100644 --- a/gitdata-apis/src/main.rs +++ b/gitdata-apis/src/main.rs @@ -1,7 +1,15 @@ +use actix_session::SessionMiddleware; +use actix_session::config::CookieContentSecurity; +use actix_session::config::PersistentSession; +use actix_session::config::TtlExtensionPolicy; +use actix_session::storage::RedisSessionStore; use actix_settings::ApplySettings; use actix_web::App; use actix_web::HttpServer; +use actix_web::cookie::Key; +use actix_web::cookie::time::Duration; use actix_web::web; +use gitdata_apis::apis::app_router::AppRouter; use gitdata_apis::service::AppState; use tracing::info; @@ -18,11 +26,36 @@ async fn main() -> anyhow::Result<()> { return Err(anyhow::anyhow!("Please configure the configuration file")); }; let state = AppState::init().await?; - HttpServer::new(move || App::new().app_data(web::Data::new(state.clone()))) - .try_apply_settings(&api_config) - .expect("Failed to apply settings") - .run() - .await - .expect("Failed to start server"); + let session = RedisSessionStore::builder_pooled(state.redis_pool.clone()) + .build() + .await?; + info!("Redis session store initialized."); + info!("API server started."); + HttpServer::new(move || { + App::new() + .wrap(actix_identity::IdentityMiddleware::default()) + .wrap( + SessionMiddleware::builder(session.clone(), Key::from(&[0; 64])) + .cookie_name("SessionID".to_string()) + .cookie_path("/".to_string()) + .cookie_http_only(false) + .cookie_content_security(CookieContentSecurity::Private) + .session_lifecycle( + PersistentSession::default() + .session_ttl(Duration::days(30)) + .session_ttl_extension_policy(TtlExtensionPolicy::OnEveryRequest), + ) + .cookie_secure(false) + .build(), + ) + .wrap(actix_web::middleware::Logger::default()) + .app_data(web::Data::new(state.clone())) + .configure(AppRouter) + }) + .try_apply_settings(&api_config) + .expect("Failed to apply settings") + .run() + .await + .expect("Failed to start server"); Ok(()) } diff --git a/gitdata-apis/src/public/captcha.html b/gitdata-apis/src/public/captcha.html new file mode 100644 index 0000000..40059be --- /dev/null +++ b/gitdata-apis/src/public/captcha.html @@ -0,0 +1,51 @@ + + + + + + email-template + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ GitData +
+

您好,

+

Hello,


非常感谢您的加入!您的验证码为:
Thanks for your signing up!Your verification code is: +
+ + + + +
123456
+
GitData.AI是一个用于数据产品(例如AI模型)的开发、管理、交易的一站式协作平台,
帮助您高效地开发和探索数据产品。
GitData.AI is a one-stop collaborative platform for the development,management,and trading of data products(such as AI + models)
to help you efficiently develop and explore data products.
Copyright©2024|GitData.AI
+
+ + + \ No newline at end of file diff --git a/gitdata-apis/src/public/mod.rs b/gitdata-apis/src/public/mod.rs new file mode 100644 index 0000000..2438698 --- /dev/null +++ b/gitdata-apis/src/public/mod.rs @@ -0,0 +1,12 @@ +/* + * + * * Copyright (c) 2024-2025, GitDataAI Ltd. + * * All rights reserved. + * * + * * Licensed under your choice of the GitDataAI Source Available License 1.0 + * * (Licensed_GSALv1) or the Server Side Public License v1 (Licensed_SSPLv1). + * + */ + +pub const CAPTCHA : &str = include_str!("captcha.html"); +pub const USER_FOR_GET_PASSWD : &str = include_str!("users_forgetpasswd.html"); diff --git a/gitdata-apis/src/public/users_forgetpasswd.html b/gitdata-apis/src/public/users_forgetpasswd.html new file mode 100644 index 0000000..9bb8e35 --- /dev/null +++ b/gitdata-apis/src/public/users_forgetpasswd.html @@ -0,0 +1,61 @@ + + + + + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1.0"> + <title>email-template + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ GitData + +
+

您好,

+

Hello,


+ + 重置密码请点击按钮:
+ To reset your password please click the button: +
+ + + + +
+ 重置密码 +
+
GitData.AI 是一个用于数据产品(例如AI模型)的开发、管理、交易的一站式协作平台,
帮助您高效地开发和探索数据产品。
GitData.AI is a one-stop collaborative platform for the development, management, and trading of data products (such as + AI models)
to help you efficiently develop and explore data products.
Copyright © 2024 | GitData.AI
+
+ + \ No newline at end of file diff --git a/gitdata-apis/src/rpc_server.rs b/gitdata-apis/src/rpc_server.rs deleted file mode 100644 index 8b13789..0000000 --- a/gitdata-apis/src/rpc_server.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/gitdata-apis/src/service/auth/passwd.rs b/gitdata-apis/src/service/auth/passwd.rs index 1c83af2..12b153b 100644 --- a/gitdata-apis/src/service/auth/passwd.rs +++ b/gitdata-apis/src/service/auth/passwd.rs @@ -5,7 +5,7 @@ use sha256::Sha256Digest; use crate::service::AppState; use crate::service::users::info::UsersInfoReplay; -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub struct UsersAuthPasswdParam { pub username : String, pub password : String, diff --git a/gitdata-apis/src/service/core_git_rpc.rs b/gitdata-apis/src/service/core_git_rpc.rs deleted file mode 100644 index 0fe5a56..0000000 --- a/gitdata-apis/src/service/core_git_rpc.rs +++ /dev/null @@ -1,26 +0,0 @@ -use gitdata::config::rpc::RpcConfig; -use gitdata::rpc::core_git::rep_repository_client::RepRepositoryClient; -use tokio::sync::OnceCell; - -pub static RPC_CLIENT : OnceCell = OnceCell::const_new(); - -pub struct CoreGitRpc { - pub client : RepRepositoryClient, -} - -impl CoreGitRpc { - pub async fn get() -> anyhow::Result<&'static CoreGitRpc> { - RPC_CLIENT.get_or_try_init(Self::connect).await - } - async fn connect() -> anyhow::Result { - let rpc = RpcConfig::load(); - if rpc.is_err() { - return Err(anyhow::anyhow!("Failed to load rpc config")); - } - let rpc = rpc?; - let core_git_rpc = RepRepositoryClient::connect(rpc.coregit_node()?.url()).await?; - Ok(CoreGitRpc { - client : core_git_rpc, - }) - } -} diff --git a/gitdata-apis/src/service/emails/captcha.rs b/gitdata-apis/src/service/emails/captcha.rs index 219d1f1..e79768f 100644 --- a/gitdata-apis/src/service/emails/captcha.rs +++ b/gitdata-apis/src/service/emails/captcha.rs @@ -40,10 +40,9 @@ impl AppState { "gitdata-bot@gitdata.ai".parse()?, "gitdata-bot@gitdata.ai".parse()?, email, - EmailType::RegistrationVerificationCode - .to_email() - .replace("123456", &captcha.to_string()), + "GitData 验证码".parse()?, EmailType::RegistrationVerificationCode, + captcha.to_string(), ); let mut email_jobs = self .email_jobs diff --git a/gitdata-apis/src/service/mod.rs b/gitdata-apis/src/service/mod.rs index 881bc54..38e6de5 100644 --- a/gitdata-apis/src/service/mod.rs +++ b/gitdata-apis/src/service/mod.rs @@ -1,3 +1,4 @@ +use std::io; use std::sync::Arc; use std::sync::Mutex; use std::time::Duration; @@ -10,17 +11,21 @@ use apalis_sql::postgres::PostgresStorage; use deadpool_redis::Runtime; use gitdata::config::database::DatabaseConfig; use gitdata::config::database::PgConfig; +use gitdata::model::migrate::DatabaseMigrate; use sea_orm::ConnectOptions; use sea_orm::Database; use sea_orm::DatabaseConnection; +use sea_orm_migration::MigratorTrait; +use tonic::transport::Server; use tracing::debug; use tracing::info; - use crate::jobs::email::EmailJobs; use crate::jobs::email::send_email; +use crate::jobs::sync_repo::{sync_repo, SyncRepoMessage}; +use crate::service::rpc::GitCoreRpc; pub mod auth; -pub mod core_git_rpc; +pub mod rpc; pub mod emails; pub mod repository; pub mod users; @@ -44,18 +49,43 @@ impl AppState { let active_write = database_conn(config.pg.get("ActiveWrite").unwrap().clone()).await?; info!("Connect Deprecated Database Pool"); let deprecated = database_conn(config.pg.get("Deprecated").unwrap().clone()).await?; + let jobs_margin = database_conn(config.pg.get("Jobs").unwrap().clone()).await?; info!("Connect Jobs Database Pool"); let jobs_pool = apalis_sql::postgres::PgPool::connect(&*config.pg.get("Jobs").unwrap().format()) .await?; + let mut sync_jobs = PostgresStorage::new_with_config( + jobs_pool.clone(), + Config::new("apalis::Repository::Sync"), + ); + let mut pg = PostgresStorage::new_with_config(jobs_pool.clone(), Config::new("apalis::Email")); + let migrator = PostgresStorage::migrations(); + let mut job_txn = jobs_margin.get_postgres_connection_pool().acquire().await?; + migrator.run(&mut job_txn).await?; let mut listener = PgListen::new(jobs_pool).await?; listener.subscribe_with(&mut pg); + listener.subscribe_with(&mut sync_jobs); tokio::spawn(async move { listener.listen().await.ok(); }); let pc = pg.clone(); + let sync_pg:PostgresStorage = sync_jobs.clone(); + info!("Connect Redis Pool"); + let redis_cfg = + deadpool_redis::Config::from_url(config.redis.get("Session").unwrap().format()); + let redis_pool = redis_cfg.create_pool(Some(Runtime::Tokio1))?; + + let state = AppState { + active_read, + active_write, + deprecated, + email_jobs : Arc::new(Mutex::new(pg)), + redis_pool, + }; + let state_replace = state.clone(); + let rpc_state = state_replace.clone(); tokio::spawn(async move { Monitor::new() .register({ @@ -65,6 +95,14 @@ impl AppState { .backend(pc.clone()) .build_fn(send_email) }) + .register({ + WorkerBuilder::new("sync-repo") + .data(state_replace.clone()) + .enable_tracing() + .retry(RetryPolicy::retries(5)) + .backend(sync_pg.clone()) + .build_fn(sync_repo) + }) .on_event(|e| debug!("{e}")) .run_with_signal(async { tokio::signal::ctrl_c().await?; @@ -74,17 +112,30 @@ impl AppState { .await .ok(); }); - info!("Connect Redis Pool"); - let redis_cfg = - deadpool_redis::Config::from_url(config.redis.get("Session").unwrap().format()); - let redis_pool = redis_cfg.create_pool(Some(Runtime::Tokio1))?; - Ok(AppState { - active_read, - active_write, - deprecated, - email_jobs : Arc::new(Mutex::new(pg)), - redis_pool, - }) + + tokio::spawn(async move { + let config = gitdata::config::rpc::RpcConfig::load().expect("failed to load rpc config"); + let git_core = GitCoreRpc::new(rpc_state.clone()); + let health = gitdata::health::service::HealthService::default(); + let addr = config.gitcore_node().expect("failed to load gitcore node").url().parse().expect("failed to parse url"); + info!("Starting GitCore RPC Server at {:?}", addr); + Server::builder() + .trace_fn(|x| { + tracing::log::info!("Url: {:?} Method: {}", x.uri(), x.method()); + tracing::Span::current() + }) + .add_service(gitdata::rpc::health::health_server::HealthServer::new( + health, + )) + .add_service( + gitdata::rpc::git_core::rep_repository_server::RepRepositoryServer::new(git_core), + ) + .serve(addr) + .await + .map_err(|x| io::Error::new(io::ErrorKind::Other, x)) + .unwrap(); + }); + Ok(state.clone()) } } @@ -98,5 +149,9 @@ async fn database_conn(url : PgConfig) -> anyhow::Result { .sqlx_logging(true) .sqlx_logging_level(url.level()); let db = Database::connect(opt).await?; + if url.dbname.to_lowercase() != "jobs".to_string() { + DatabaseMigrate::install(&db).await?; + DatabaseMigrate::up(&db, None).await?; + } Ok(db) } diff --git a/gitdata-apis/src/service/repository/access.rs b/gitdata-apis/src/service/repository/access.rs new file mode 100644 index 0000000..7f8dd03 --- /dev/null +++ b/gitdata-apis/src/service/repository/access.rs @@ -0,0 +1,71 @@ +use sea_orm::*; +use gitdata::model::repository::repository; +use gitdata::model::users::{ssh_keys, token_key, users}; +use crate::service::AppState; + +impl AppState { + pub async fn repo_access_token(&self, repo: repository::Model, tk: String) -> anyhow::Result{ + let owner_model = users::Entity::find_by_uid(repo.owner_uid) + .one(&self.active_read) + .await? + .ok_or_else(|| anyhow::anyhow!("user not found"))?; + let tokens = if owner_model.organize { + let user_uids = users::Entity::find() + .filter(users::Column::Uid.is_in(owner_model.member)) + .all(&self.active_read) + .await + .map(|users| users.into_iter().map(|user| user.uid).collect::>())?; + token_key::Entity::find() + .filter(token_key::Column::UserUid.is_in(user_uids)) + .all(&self.active_read) + .await + .map_err(|_| anyhow::anyhow!("token not found"))? + } else { + match token_key::Entity::find() + .filter(token_key::Column::UserUid.eq(owner_model.uid)) + .all(&self.active_read) + .await{ + Ok(token) => token, + Err(_) => return Err(anyhow::anyhow!("token not found")) + } + }; + for token in tokens { + if token.token == tk { + return Ok(token); + } + } + Err(anyhow::anyhow!("token not found")) + } + pub async fn repo_access_ssh(&self, repo: repository::Model, pbk: String) -> anyhow::Result{ + let owner_model = users::Entity::find_by_uid(repo.owner_uid) + .one(&self.active_read) + .await? + .ok_or_else(|| anyhow::anyhow!("user not found"))?; + let keys = if owner_model.organize { + let user_uids = users::Entity::find() + .filter(users::Column::Uid.is_in(owner_model.member)) + .all(&self.active_read) + .await + .map(|users| users.into_iter().map(|user| user.uid).collect::>())?; + ssh_keys::Entity::find() + .filter(ssh_keys::Column::UserUid.is_in(user_uids)) + .all(&self.active_read) + .await + .map_err(|_| anyhow::anyhow!("ssh key not found"))? + }else { + match ssh_keys::Entity::find() + .filter(ssh_keys::Column::UserUid.eq(owner_model.uid)) + .all(&self.active_read) + .await{ + Ok(key) => key, + Err(_) => return Err(anyhow::anyhow!("ssh key not found")) + } + }; + for key in keys { + if key.ssh_key == pbk { + return Ok(key); + } + } + Err(anyhow::anyhow!("ssh key not found")) + } +} \ No newline at end of file diff --git a/gitdata-apis/src/service/repository/info.rs b/gitdata-apis/src/service/repository/info.rs new file mode 100644 index 0000000..7afdfea --- /dev/null +++ b/gitdata-apis/src/service/repository/info.rs @@ -0,0 +1,19 @@ +use sea_orm::*; +use gitdata::model::repository::repository; +use gitdata::model::users::users; +use crate::service::AppState; + +impl AppState { + pub async fn repo_owner_name(&self, owner: String, repo: String) -> anyhow::Result { + let user_model = users::Entity::find_by_username(&owner) + .one(&self.active_read) + .await? + .ok_or_else(|| anyhow::anyhow!("user not found"))?; + let repo_model = repository::Entity::find_by_owner(user_model.uid) + .filter(repository::Column::Name.eq(repo)) + .one(&self.active_read) + .await? + .ok_or_else(|| anyhow::anyhow!("repository not found"))?; + Ok(repo_model) + } +} \ No newline at end of file diff --git a/gitdata-apis/src/service/repository/mod.rs b/gitdata-apis/src/service/repository/mod.rs index 7d8ddcb..855a520 100644 --- a/gitdata-apis/src/service/repository/mod.rs +++ b/gitdata-apis/src/service/repository/mod.rs @@ -1 +1,3 @@ pub mod new_repo; +pub mod info; +pub mod access; \ No newline at end of file diff --git a/gitdata-apis/src/service/repository/new_repo.rs b/gitdata-apis/src/service/repository/new_repo.rs index c8f9592..340ae55 100644 --- a/gitdata-apis/src/service/repository/new_repo.rs +++ b/gitdata-apis/src/service/repository/new_repo.rs @@ -1,8 +1,15 @@ +use gitdata::model::repository::repository; +use gitdata::model::users::users; +use gitdata::rpc::core_git::RepositoryAddFileRequest; +use gitdata::rpc::core_git::RepositoryStoragePosition; +use sea_orm::ActiveModelTrait; +use sea_orm::TransactionTrait; use sea_orm::prelude::Uuid; use serde::Deserialize; use serde::Serialize; use crate::service::AppState; +use crate::service::rpc::CoreGitRpc; #[derive(Deserialize, Serialize)] pub struct RepoCreateParam { @@ -12,10 +19,73 @@ pub struct RepoCreateParam { pub visible : bool, pub default_branch : Option, pub readme : bool, + pub node : String, + pub message : Option, } impl AppState { - pub async fn repository_new(&self) { - todo!() + pub async fn repository_new( + &self, + user_model : users::Model, + param : RepoCreateParam, + ) -> anyhow::Result<()> { + let active_model = repository::ActiveModel::new( + param.name.clone(), + param.owner_uid, + param.description, + param.visible, + param.default_branch.clone(), + param.node.clone(), + ); + let txn = self.active_write.begin().await?; + match active_model.clone().insert(&txn).await { + Ok(_) => {} + Err(e) => { + txn.rollback().await?; + return Err(e.into()); + } + }; + let mut client = match CoreGitRpc::get().await { + Ok(client) => client.clone(), + Err(e) => { + txn.rollback().await?; + return Err(e); + } + }; + let mut node = RepositoryStoragePosition::default(); + node.node = param.node; + node.path = active_model.uid.unwrap().to_string(); + match client.client.create(node.clone()).await { + Ok(_) => {} + Err(e) => { + txn.rollback().await?; + return Err(e.into()); + } + }; + if param.readme { + let bytes = format!("### {}", param.name); + match client + .client + .add_file(RepositoryAddFileRequest { + repository_storage_position : Some(node), + path : "/".to_string(), + content : bytes.into_bytes(), + email : user_model.main_email.clone(), + user : user_model.name.clone(), + message : param.message.unwrap_or("Create README.md".to_string()), + file_name : "README.md".to_string(), + branch : param.default_branch.unwrap_or("main".to_string()), + }) + .await + { + Ok(_) => {} + Err(e) => { + txn.rollback().await?; + return Err(e.into()); + } + } + } + txn.commit().await?; + Ok(()) } } diff --git a/gitdata-apis/src/service/rpc.rs b/gitdata-apis/src/service/rpc.rs new file mode 100644 index 0000000..5065c60 --- /dev/null +++ b/gitdata-apis/src/service/rpc.rs @@ -0,0 +1,101 @@ +use crate::service::AppState; +use gitdata::config::rpc::RpcConfig; +use gitdata::rpc; +use gitdata::rpc::core_git::rep_repository_client::RepRepositoryClient; +use gitdata::rpc::git_core::{AccessResponse, PathRequest, PublickeyRequest, RepositoryAccess, RepositoryStoragePosition, TokenRequest}; +use tokio::sync::OnceCell; +use tonic::{async_trait, Request, Response, Status}; + +pub static RPC_CLIENT : OnceCell = OnceCell::const_new(); + +#[derive(Clone, Debug)] +pub struct CoreGitRpc { + pub client : RepRepositoryClient, +} + +impl CoreGitRpc { + pub async fn get() -> anyhow::Result<&'static CoreGitRpc> { + RPC_CLIENT.get_or_try_init(Self::connect).await + } + async fn connect() -> anyhow::Result { + let rpc = RpcConfig::load(); + if rpc.is_err() { + return Err(anyhow::anyhow!("Failed to load rpc config")); + } + let rpc = rpc?; + let core_git_rpc = RepRepositoryClient::connect(rpc.coregit_node()?.url()).await?; + Ok(CoreGitRpc { + client : core_git_rpc, + }) + } +} + + + +pub struct GitCoreRpc { + state: AppState +} + +impl GitCoreRpc { + pub fn new(app_state: AppState) -> Self { + Self { + state: app_state + } + } +} + +#[async_trait] +impl rpc::git_core::rep_repository_server::RepRepository for GitCoreRpc { + async fn path(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + let path = match self.state.repo_owner_name(request.owner,request.repo).await{ + Ok(path) => path, + Err(_) => return Err(Status::not_found("Repo not found")) + }; + return Ok(Response::new(RepositoryStoragePosition{ + path: path.uid.to_string(), + node: path.storage_node, + })) + } + + async fn token(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + let model = match self.state.repo_owner_name(request.owner,request.repo).await{ + Ok(path) => path, + Err(_) => return Err(Status::not_found("Repo not found")) + }; + let tokens = match self.state.repo_access_token(model,request.token).await{ + Ok(tokens) => tokens, + Err(_) => return Err(Status::not_found("Token not found")) + }; + match tokens.access { + 1 => Ok(Response::new(AccessResponse{ + access: i32::from(RepositoryAccess::Read), + })), + 4 => Ok(Response::new(AccessResponse{ + access: i32::from(RepositoryAccess::Write), + })), + 7 => Ok(Response::new(AccessResponse{ + access: i32::from(RepositoryAccess::Admin), + })), + _ => Ok(Response::new(AccessResponse{ + access: i32::from(RepositoryAccess::None), + })) + } + } + + async fn publickey(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + let model = match self.state.repo_owner_name(request.owner,request.repo).await{ + Ok(path) => path, + Err(_) => return Err(Status::not_found("Repo not found")) + }; + match self.state.repo_access_ssh(model,request.publickey).await{ + Ok(_) => Ok(Response::new(AccessResponse{ + access: i32::from(RepositoryAccess::Admin), + })), + Err(_) => Err(Status::not_found("Token not found")) + } + } +} + diff --git a/gitdata-apis/src/service/users/apply.rs b/gitdata-apis/src/service/users/apply.rs index 28c5d1c..9693fb0 100644 --- a/gitdata-apis/src/service/users/apply.rs +++ b/gitdata-apis/src/service/users/apply.rs @@ -4,6 +4,7 @@ use sea_orm::Set; use sea_orm::prelude::Uuid; use serde::Deserialize; use serde::Serialize; +use sha256::Sha256Digest; use crate::service::AppState; @@ -61,7 +62,8 @@ impl AppState { } _ => {} }; - let mut model = users::ActiveModel::new_users(param.username, param.email, param.passwd); + let mut model = + users::ActiveModel::new_users(param.username, param.email, param.passwd.digest()); model.state = Set("Active".to_string()); loop { match self diff --git a/gitdata-gits/src/http/pack.rs b/gitdata-gits/src/http/pack.rs index 8ca6e7e..7745f7f 100644 --- a/gitdata-gits/src/http/pack.rs +++ b/gitdata-gits/src/http/pack.rs @@ -11,7 +11,6 @@ use bytes::Bytes; use futures::StreamExt; use crate::mount::StoragePool; -use crate::mount::StorageSingleton; use crate::rpc::git_core::RepositoryRpc; use crate::service::GitServiceType; @@ -66,56 +65,22 @@ pub async fn pack( response.append_header(("Transfer-Encoding", "chunked")); response.append_header(("X-Content-Type-Options", "nosniff")); let storage = storage.read().unwrap(); - match match storage.get(path.clone()) { + let storage = match storage.node.get(&path.node) { Some(storage) => storage, - None => { - return actix_web::HttpResponse::NotFound().finish(); - } - } { - StorageSingleton::S3(s) => { - match s - .pack( - path.path().to_string(), - service, - Some(version.to_string()), - gzip, - bytes, - ) - .await - { - Ok(bytes) => response.streaming(bytes), - Err(_) => actix_web::HttpResponse::NotFound().finish(), - } - } - StorageSingleton::Local(s) => { - match s - .pack( - path.path().to_string(), - service, - Some(version.to_string()), - gzip, - bytes, - ) - .await - { - Ok(bytes) => response.streaming(bytes), - Err(_) => actix_web::HttpResponse::NotFound().finish(), - } - } - StorageSingleton::Nfs(s) => { - match s - .pack( - path.path().to_string(), - service, - Some(version.to_string()), - gzip, - bytes, - ) - .await - { - Ok(bytes) => response.streaming(bytes), - Err(_) => actix_web::HttpResponse::NotFound().finish(), - } - } - } + None => return actix_web::HttpResponse::NotFound().finish(), + }; + let result = match storage + .pack( + path.path.clone(), + service, + Some(version.to_string()), + gzip, + bytes, + ) + .await + { + Ok(response) => response, + Err(_) => return actix_web::HttpResponse::NotFound().finish(), + }; + response.streaming(result) } diff --git a/gitdata-gits/src/http/refs.rs b/gitdata-gits/src/http/refs.rs index 281a12a..76b6873 100644 --- a/gitdata-gits/src/http/refs.rs +++ b/gitdata-gits/src/http/refs.rs @@ -44,28 +44,22 @@ pub async fn info_refs( .to_str() .map(|s| s.to_string()) .unwrap_or("".to_string()); - let data = match storage.get(path.clone()) { - Some(data) => match data - .refs( - path.path(), - service, - if version.is_empty() { - None - } else { - Some(&version) - }, - ) - .await - { - Ok(data) => data, - Err(_) => { - return HttpResponse::NotFound().body("Not found"); - } - }, + + let storage = match storage.node.get(&path.clone().node) { + Some(storage) => storage, None => { return HttpResponse::NotFound().body("Not found"); } }; + let data = match storage + .refs(&*path.path.clone(), service, Some(&version)) + .await + { + Ok(data) => data, + Err(_) => { + return HttpResponse::NotFound().body("Not found"); + } + }; let mut response = HttpResponseBuilder::new(StatusCode::OK); response.append_header(( "Content-Type", diff --git a/gitdata-gits/src/http/text.rs b/gitdata-gits/src/http/text.rs index 500be79..6f688bf 100644 --- a/gitdata-gits/src/http/text.rs +++ b/gitdata-gits/src/http/text.rs @@ -7,7 +7,6 @@ use actix_web::http::header::HeaderValue; use actix_web::web; use crate::mount::StoragePool; -use crate::mount::StorageSingleton; use crate::rpc::git_core::RepositoryRpc; pub(crate) async fn text( @@ -26,19 +25,15 @@ pub(crate) async fn text( .uri() .to_string() .replace(&format!("{}/{}", owner, path), ""); - let storage = match match storage.get(nodepath.clone()) { + let storage = match storage.node.get(&nodepath.node) { Some(storage) => storage, None => return HttpResponse::NotFound().finish(), - } { - StorageSingleton::S3(x) => x.text(&nodepath.path(), &file_path).await, - StorageSingleton::Local(x) => x.text(&nodepath.path(), &file_path).await, - StorageSingleton::Nfs(x) => x.text(&nodepath.path(), &file_path).await, }; - if storage.is_err() { - return HttpResponse::NotFound().finish(); - } - let storage = storage.unwrap(); - let namedfile = storage.use_last_modified(true); + let namedfile = match storage.text(&*nodepath.path, &*file_path).await { + Ok(namedfile) => namedfile, + Err(_) => return HttpResponse::NotFound().finish(), + }; + let namedfile = namedfile.use_last_modified(true); let mut response = namedfile.into_response(&request); response.headers_mut().insert( HeaderName::try_from("Pragma".to_string()).unwrap(), diff --git a/gitdata-gits/src/main.rs b/gitdata-gits/src/main.rs index c1d84e7..fb6a2b4 100644 --- a/gitdata-gits/src/main.rs +++ b/gitdata-gits/src/main.rs @@ -1,8 +1,9 @@ use std::io; -use gitdata::config; use log::info; use tonic::transport::Server; +use gitdata::config; +use crate::mount::git::NodeStorage; pub mod health; pub mod http; @@ -14,24 +15,30 @@ pub mod ssh; async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt().init(); info!("starting gitdata"); - let pool = mount::StoragePool::new(); + let mut pool = mount::StoragePool::new(); + pool.add_node("default".to_string(), NodeStorage { + root : std::env::current_dir()?.join("./data"), + }); let http = tokio::spawn(http::http(pool.clone())); let health = tokio::spawn(async move { let health = gitdata::health::service::HealthService::default(); let core_git = rpc::core_git::CoreGit::new(pool.clone()); info!("starting health service"); + let config = config::rpc::RpcConfig::load().expect("failed to load rpc config"); + let addr = config.coregit_node().expect("failed to load gitcore node").url().parse().expect("failed to parse url"); + info!("connecting to gitcore node at {:?}", addr); Server::builder() - // .trace_fn(|x|{ - // info!("Url: {:?} Method: {}", x.uri(),x.method()); - // tracing::Span::current() - // }) + .trace_fn(|x| { + info!("Url: {:?} Method: {}", x.uri(), x.method()); + tracing::Span::current() + }) .add_service(gitdata::rpc::health::health_server::HealthServer::new( health, )) .add_service( gitdata::rpc::core_git::rep_repository_server::RepRepositoryServer::new(core_git), ) - .serve("0.0.0.0:50051".parse().unwrap()) + .serve(addr) .await .map_err(|x| io::Error::new(io::ErrorKind::Other, x)) .unwrap(); diff --git a/gitdata-gits/src/mount/git.rs b/gitdata-gits/src/mount/git.rs new file mode 100644 index 0000000..160f665 --- /dev/null +++ b/gitdata-gits/src/mount/git.rs @@ -0,0 +1,508 @@ +use std::io; +use std::io::Cursor; +use std::io::Error; +use std::io::Read; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::process::Stdio; + +use actix_files::NamedFile; +use async_fn_stream::fn_stream; +use bytes::Bytes; +use flate2::read::GzDecoder; +use futures_core::Stream; +use git2::Signature; +use gitdata::rpc::core_git::RepositoryBranch; +use gitdata::rpc::core_git::RepositoryCommit; +use gitdata::rpc::core_git::RepositoryTags; +use gitdata::rpc::core_git::RepositoryTree; + +use crate::service::GitServiceType; + +#[derive(Clone)] +pub struct NodeStorage { + pub root : PathBuf, +} + +impl NodeStorage { + pub(crate) async fn refs( + &self, + path : &str, + service : GitServiceType, + version : Option<&str>, + ) -> io::Result { + let mut cmd = Command::new("git"); + cmd.arg(service.to_string()); + cmd.arg("--stateless-rpc"); + cmd.arg("--advertise-refs"); + cmd.arg("."); + cmd.current_dir(self.root.join(path)); + if !version.is_some() { + cmd.env("GIT_PROTOCOL", version.unwrap_or("")); + } + let output = match cmd.output() { + Ok(output) => output, + Err(e) => { + return Err(Error::new( + io::ErrorKind::Other, + format!("Error running command {}", e), + )); + } + }; + if !output.status.success() { + return Err(io::Error::new( + io::ErrorKind::Other, + "Error running command", + )); + } + Ok(String::from_utf8(output.stdout).unwrap_or("".to_string())) + } + pub(crate) async fn text(&self, path : &str, file_path : &str) -> io::Result { + let file_path = self.root.join(path).join(file_path); + if !file_path.exists() { + return Err(Error::new(io::ErrorKind::NotFound, "File not found")); + } + if file_path.is_dir() { + return Err(io::Error::new(io::ErrorKind::Other, "File is a directory")); + } + Ok(NamedFile::open(file_path)?) + } + pub(crate) async fn pack( + &self, + path : String, + service : GitServiceType, + version : Option, + gzip : bool, + payload : Bytes, + ) -> io::Result> + use<>> { + let mut cmd = Command::new("git"); + cmd.arg(service.to_string()); + // cmd.arg("receive-pack"); + cmd.arg("--stateless-rpc"); + cmd.arg("."); + if !version.is_some() { + cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); + } + cmd.stderr(Stdio::piped()); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.current_dir(self.root.join(path)); + let mut git_process = match cmd.spawn() { + Ok(process) => process, + Err(e) => { + return Err(Error::new( + io::ErrorKind::Other, + format!("Error running command {}", e), + )); + } + }; + let mut stdin = git_process.stdin.take().unwrap(); + let mut stdout = git_process.stdout.take().unwrap(); + let bytes = if gzip { + let mut decoder = GzDecoder::new(Cursor::new(payload)); + let mut decoded_data = Vec::new(); + if let Err(e) = io::copy(&mut decoder, &mut decoded_data) { + return Err(Error::new( + io::ErrorKind::Other, + format!("Error running command {}", e), + )); + } + decoded_data + } else { + payload.to_vec() + }; + if let Err(e) = stdin.write_all(&bytes) { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Error running command {}", e), + )); + } + drop(stdin); + Ok(fn_stream(move |emitter| async move { + let mut buffer = [0; 8192]; + loop { + match stdout.read(&mut buffer) { + Ok(0) => break, + Ok(n) => { + emitter + .emit(Ok(Bytes::copy_from_slice(&buffer[0..n]))) + .await; + } + Err(e) => { + emitter.emit(Err(Error::new(io::ErrorKind::Other, e))).await; + break; + } + } + } + })) + } + pub async fn pack_ssh( + &self, + path : String, + service : GitServiceType, + version : Option, + ) -> io::Result<( + tokio::process::ChildStdin, + (tokio::process::ChildStdout, tokio::process::ChildStderr), + )> { + let mut cmd = tokio::process::Command::new("git"); + cmd.arg(service.to_string()); + cmd.arg("."); + if !version.is_some() { + cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); + } + cmd.stderr(Stdio::piped()); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.current_dir(self.root.join(path)); + let mut git_process = match cmd.spawn() { + Ok(process) => process, + Err(e) => { + return Err(Error::new( + io::ErrorKind::Other, + format!("Error running command {}", e), + )); + } + }; + Ok(( + git_process.stdin.take().unwrap(), + ( + git_process.stdout.take().unwrap(), + git_process.stderr.take().unwrap(), + ), + )) + } + pub(crate) async fn create_repository(&self, path : String) -> anyhow::Result<()> { + if std::fs::read_dir(&self.root.join(path.clone())).is_ok() { + return Err(anyhow::anyhow!("Repository Path already exists")); + } + let git = git2::Repository::init_bare(&self.root.join(path.clone())); + if git.is_ok() { + Ok(()) + } else if let Err(r) = git { + Err(anyhow::anyhow!("{}", r)) + } else { + Err(anyhow::anyhow!("Unknown Error")) + } + } + pub(crate) async fn add_file( + &self, + path : String, + file_path : String, + bytes : Vec, + commit_email : String, + commit_users : String, + commit_message : String, + file_name : String, + branch_name : String, + ) -> anyhow::Result<()> { + use anyhow::Context; + + let path = self.root.join(path); + let tmp = tempfile::tempdir().context("Failed to create temporary directory")?; + let clone_repository = git2::Repository::clone( + match path.to_str() { + Some(x) => x, + None => return Err(anyhow::anyhow!("Path is not valid")), + }, + tmp.path(), + ) + .context("Failed to clone repository")?; + + let branch = match clone_repository.find_branch(&branch_name, git2::BranchType::Local) { + Ok(branch) => branch, + Err(_) => { + let head_commit = clone_repository + .head() + .context("Failed to get HEAD")? + .peel_to_commit() + .context("Failed to peel HEAD to commit")?; + clone_repository + .branch(&branch_name, &head_commit, false) + .context("Failed to create branch")?; + clone_repository + .find_branch(&branch_name, git2::BranchType::Local) + .context("Failed to find branch after creation")? + } + }; + + let branch_name = branch + .name() + .transpose() + .context("Failed to get branch name")? + .map_err(|_| anyhow::anyhow!("Branch name is empty"))?; + + if !branch.is_head() { + clone_repository + .set_head(&branch_name) + .context("Failed to set HEAD to branch")?; + clone_repository + .checkout_head(Some(git2::build::CheckoutBuilder::default().force())) + .context("Failed to check out HEAD")?; + } + + let full_file_path = tmp.path().join(&file_path).join(&file_name); + std::fs::create_dir_all( + full_file_path + .parent() + .context("Failed to get parent directory")?, + )?; + std::fs::write(&full_file_path, bytes).context("Failed to write file")?; + + let relative_path = full_file_path + .strip_prefix(tmp.path()) + .context("Failed to strip prefix from file path")?; + let mut index = clone_repository + .index() + .context("Failed to get repository index")?; + index + .add_path(relative_path) + .context("Failed to add file to index")?; + index.write().context("Failed to write index")?; + + let time = chrono::Utc::now().timestamp(); + let time = git2::Time::new(time, 0); + let user = Signature::new(&commit_users, &commit_email, &time) + .context("Failed to create commit signature")?; + let tree = clone_repository + .find_tree(index.write_tree().context("Failed to write tree")?) + .context("Failed to find tree")?; + let parent_commit = clone_repository + .find_commit( + branch + .get() + .target() + .context("Failed to get branch target")?, + ) + .context("Failed to find parent commit")?; + clone_repository + .commit(Some("HEAD"), &user, &user, &commit_message, &tree, &[ + &parent_commit, + ]) + .context("Failed to create commit")?; + + let refspec = format!("refs/heads/{}:refs/heads/{}", branch_name, branch_name); + clone_repository + .find_remote("origin") + .context("Failed to find remote 'origin'")? + .push(&[refspec.as_str()], Some(&mut git2::PushOptions::new())) + .context("Failed to push changes to remote")?; + + Ok(()) + } + pub(crate) async fn branch(&self, path : String) -> anyhow::Result> { + use anyhow::Context; + let repository = match git2::Repository::open(self.root.join(path)) { + Ok(repo) => repo, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let branches = repository + .branches(Some(git2::BranchType::Local)) + .context("Failed to get branches")?; + let mut result = Vec::new(); + for branch in branches { + if let Ok((branch, _)) = branch { + let commit = match branch.get().peel_to_commit() { + Ok(commit) => commit, + Err(_) => { + continue; + } + }; + let name = match branch.name() { + Ok(name) => match name { + Some(name) => name.to_string(), + None => { + continue; + } + }, + Err(_) => { + continue; + } + }; + let commit_id = commit.id().to_string(); + let count = commit.parent_count(); + result.push(RepositoryBranch { + name, + head : commit_id, + commit_nums : count as i32, + from : None, + to : None, + start : false, + }); + } + } + Ok(result) + } + pub(crate) async fn commit( + &self, + path : String, + branch_name : String, + ) -> anyhow::Result> { + let repository = match git2::Repository::open(self.root.join(path)) { + Ok(repo) => repo, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let branch = match repository.find_branch(&branch_name.clone(), git2::BranchType::Local) { + Ok(branch) => branch, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let mut commit = match branch.get().peel_to_commit() { + Ok(commit) => commit, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let mut result = Vec::new(); + loop { + let commit_id = commit.id().to_string(); + let commit_message = match commit.message() { + Some(message) => message.to_string(), + None => { + continue; + } + }; + let commit_time = commit.time().seconds(); + let commit_author = match commit.author().name() { + Some(name) => name.to_string(), + None => { + continue; + } + }; + let commit_email = match commit.author().email() { + Some(email) => email.to_string(), + None => { + continue; + } + }; + let mut file_tree = Vec::new(); + let commit_tree = match commit.tree() { + Ok(tree) => tree, + Err(_) => { + continue; + } + }; + match commit_tree.walk(git2::TreeWalkMode::PreOrder, |path, entry| { + let file_path = path.to_string(); + let file_name = entry.name().unwrap_or("").to_string(); + let hash = entry.id().to_string(); + let mode = entry.filemode(); + let size = match repository.find_blob(entry.id()) { + Ok(file) => file.size(), + Err(_) => 0, + }; + file_tree.push(RepositoryTree { + hash, + path : file_path, + size : size.to_string(), + name : file_name, + mode : mode.to_string(), + }); + 0 + }) { + Ok(_) => {} + Err(_) => { + continue; + } + } + result.push(RepositoryCommit { + hash : commit_id, + message : commit_message, + author : commit_author, + email : commit_email, + date : commit_time.to_string(), + branch : branch_name.clone(), + file_nums : file_tree.len() as i32, + tree : file_tree, + }); + commit = match commit.parent(0) { + Ok(commit) => commit, + Err(_) => { + break Ok(result); + } + }; + } + } + pub(crate) async fn get_file( + &self, + path : String, + file_path : String, + hash : String, + ) -> anyhow::Result> { + let repository = match git2::Repository::open(self.root.join(path)) { + Ok(repo) => repo, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let commit = match repository.find_commit(git2::Oid::from_str(&hash)?) { + Ok(commit) => commit, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + if let Ok(blob) = commit + .tree() + .and_then(|tree| tree.get_path(Path::new(&file_path))) + { + if let Ok(blob) = repository.find_blob(blob.id()) { + return Ok(blob.content().to_vec()); + } + } + Err(anyhow::anyhow!("File not found")) + } + pub(crate) async fn tage(&self, path : String) -> anyhow::Result> { + let repository = match git2::Repository::open(self.root.join(path)) { + Ok(repo) => repo, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + let mut result = Vec::new(); + for tag in &repository.tag_names(None)? { + if tag.is_none() { + continue; + } + let tag = tag.unwrap(); + let tag = match repository.find_tag(git2::Oid::from_str(&tag)?) { + Ok(tag) => tag, + Err(e) => { + return Err(anyhow::anyhow!("{}", e)); + } + }; + + result.push(RepositoryTags { + name : match tag.name() { + Some(name) => name.to_string(), + None => { + continue; + } + }, + hash : tag.id().to_string(), + date : match tag.target() { + Ok(target) => match target.as_commit() { + Some(commit) => commit.time().seconds().to_string(), + None => "0".to_string(), + }, + Err(_) => "0".to_string(), + }, + author : match tag.tagger() { + Some(tagger) => tagger.name().unwrap_or("N/A").to_string(), + None => "N/A".to_string(), + }, + email : match tag.tagger() { + Some(tagger) => tagger.email().unwrap_or("N/A").to_string(), + None => "N/A".to_string(), + }, + }); + } + Ok(result) + } +} diff --git a/gitdata-gits/src/mount/local.rs b/gitdata-gits/src/mount/local.rs deleted file mode 100644 index 4c706da..0000000 --- a/gitdata-gits/src/mount/local.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::io; -use std::io::Cursor; -use std::io::Error; -use std::io::Read; -use std::io::Write; -use std::path::PathBuf; -use std::process::Command; -use std::process::Stdio; - -use actix_files::NamedFile; -use async_fn_stream::fn_stream; -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures_core::Stream; - -use crate::service::GitServiceType; - -#[derive(Clone)] -pub struct LocalStorage { - pub root : PathBuf, -} - -impl LocalStorage { - pub(crate) async fn refs( - &self, - path : &str, - service : GitServiceType, - version : Option<&str>, - ) -> io::Result { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("--stateless-rpc"); - cmd.arg("--advertise-refs"); - cmd.arg("."); - cmd.current_dir(self.root.join(path)); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("")); - } - let output = match cmd.output() { - Ok(output) => output, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - if !output.status.success() { - return Err(io::Error::new( - io::ErrorKind::Other, - "Error running command", - )); - } - Ok(String::from_utf8(output.stdout).unwrap_or("".to_string())) - } - pub(crate) async fn text(&self, path : &str, file_path : &str) -> io::Result { - let file_path = self.root.join(path).join(file_path); - if !file_path.exists() { - return Err(io::Error::new(io::ErrorKind::NotFound, "File not found")); - } - if file_path.is_dir() { - return Err(io::Error::new(io::ErrorKind::Other, "File is a directory")); - } - Ok(NamedFile::open(file_path)?) - } - pub(crate) async fn pack( - &self, - path : String, - service : GitServiceType, - version : Option, - gzip : bool, - payload : Bytes, - ) -> io::Result> + use<>> { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - // cmd.arg("receive-pack"); - cmd.arg("--stateless-rpc"); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - let mut stdin = git_process.stdin.take().unwrap(); - let mut stdout = git_process.stdout.take().unwrap(); - let bytes = if gzip { - let mut decoder = GzDecoder::new(Cursor::new(payload)); - let mut decoded_data = Vec::new(); - if let Err(e) = io::copy(&mut decoder, &mut decoded_data) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - decoded_data - } else { - payload.to_vec() - }; - if let Err(e) = stdin.write_all(&bytes) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - drop(stdin); - Ok(fn_stream(move |emitter| async move { - let mut buffer = [0; 8192]; - loop { - match stdout.read(&mut buffer) { - Ok(0) => break, - Ok(n) => { - emitter - .emit(Ok(Bytes::copy_from_slice(&buffer[0..n]))) - .await; - } - Err(e) => { - emitter.emit(Err(Error::new(io::ErrorKind::Other, e))).await; - break; - } - } - } - })) - } - pub async fn pack_ssh( - &self, - path : String, - service : GitServiceType, - version : Option, - ) -> io::Result<( - tokio::process::ChildStdin, - (tokio::process::ChildStdout, tokio::process::ChildStderr), - )> { - let mut cmd = tokio::process::Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - Ok(( - git_process.stdin.take().unwrap(), - ( - git_process.stdout.take().unwrap(), - git_process.stderr.take().unwrap(), - ), - )) - } -} diff --git a/gitdata-gits/src/mount/mod.rs b/gitdata-gits/src/mount/mod.rs index 7fa1f09..8785ec2 100644 --- a/gitdata-gits/src/mount/mod.rs +++ b/gitdata-gits/src/mount/mod.rs @@ -1,27 +1,12 @@ use std::collections::HashMap; -use std::io; -use std::io::Error; -use actix_files::NamedFile; -use async_trait::async_trait; -use bytes::Bytes; -use futures_core::Stream; +use crate::mount::git::NodeStorage; -use crate::mount::local::LocalStorage; -use crate::mount::nfs::NfsStorage; -use crate::mount::s3::S3Storage; -use crate::rpc::RepositoryStoragePosition; -use crate::service::GitServiceType; - -pub mod local; -pub mod nfs; -pub mod s3; +pub mod git; #[derive(Clone)] pub struct StoragePool { - s3 : HashMap, - local : HashMap, - nfs : HashMap, + pub(crate) node : HashMap, } impl Default for StoragePool { @@ -33,70 +18,11 @@ impl Default for StoragePool { impl StoragePool { pub fn new() -> Self { StoragePool { - s3 : HashMap::new(), - local : HashMap::new(), - nfs : HashMap::new(), - } - } - - pub fn add_s3(&mut self, name : String, storage : S3Storage) { - self.s3.insert(name, storage); - } - - pub fn add_local(&mut self, name : String, storage : LocalStorage) { - self.local.insert(name, storage); - } - pub fn add_nfs(&mut self, name : String, storage : NfsStorage) { - self.nfs.insert(name, storage); - } - pub fn get(&self, node : RepositoryStoragePosition) -> Option { - match node { - RepositoryStoragePosition::S3(x) => { - if let Some(storage) = self.s3.get(&x.node) { - return Some(StorageSingleton::S3(storage.clone())); - } - } - RepositoryStoragePosition::Local(x) => { - if let Some(storage) = self.local.get(&x.node) { - return Some(StorageSingleton::Local(storage.clone())); - } - } - RepositoryStoragePosition::Nfs(x) => { - if let Some(storage) = self.nfs.get(&x.node) { - return Some(StorageSingleton::Nfs(storage.clone())); - } - } + node : HashMap::new(), } - return None; } -} - -#[derive(Clone)] -pub enum StorageSingleton { - S3(S3Storage), - Local(LocalStorage), - Nfs(NfsStorage), -} -impl StorageSingleton { - pub async fn refs( - &self, - path : &str, - service : GitServiceType, - version : Option<&str>, - ) -> io::Result { - match self { - StorageSingleton::S3(x) => x.refs(path, service, version).await, - StorageSingleton::Local(x) => x.refs(path, service, version).await, - StorageSingleton::Nfs(x) => x.refs(path, service, version).await, - } - } - - pub async fn text(&self, path : &str, file_path : &str) -> io::Result { - match self { - StorageSingleton::S3(x) => x.text(path, file_path).await, - StorageSingleton::Local(x) => x.text(path, file_path).await, - StorageSingleton::Nfs(x) => x.text(path, file_path).await, - } + pub fn add_node(&mut self, name : String, storage : NodeStorage) { + self.node.insert(name, storage); } } diff --git a/gitdata-gits/src/mount/nfs.rs b/gitdata-gits/src/mount/nfs.rs deleted file mode 100644 index 9d3b22e..0000000 --- a/gitdata-gits/src/mount/nfs.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::io; -use std::io::Cursor; -use std::io::Error; -use std::io::Read; -use std::io::Write; -use std::path::PathBuf; -use std::process::Command; -use std::process::Stdio; - -use actix_files::NamedFile; -use async_fn_stream::fn_stream; -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures_core::Stream; - -use crate::service::GitServiceType; - -#[derive(Clone)] -pub struct NfsStorage { - pub root : PathBuf, -} - -impl NfsStorage { - pub(crate) async fn refs( - &self, - path : &str, - service : GitServiceType, - version : Option<&str>, - ) -> io::Result { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("--stateless-rpc"); - cmd.arg("--advertise-refs"); - cmd.arg("."); - cmd.current_dir(self.root.join(path)); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("")); - } - let output = match cmd.output() { - Ok(output) => output, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - if !output.status.success() { - return Err(io::Error::new( - io::ErrorKind::Other, - "Error running command", - )); - } - Ok(String::from_utf8(output.stdout).unwrap_or("".to_string())) - } - pub(crate) async fn text(&self, path : &str, file_path : &str) -> io::Result { - let file_path = self.root.join(path).join(file_path); - if !file_path.exists() { - return Err(io::Error::new(io::ErrorKind::NotFound, "File not found")); - } - if file_path.is_dir() { - return Err(io::Error::new(io::ErrorKind::Other, "File is a directory")); - } - Ok(NamedFile::open(file_path)?) - } - pub(crate) async fn pack( - &self, - path : String, - service : GitServiceType, - version : Option, - gzip : bool, - payload : Bytes, - ) -> io::Result> + use<>> { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("receive-pack"); - cmd.arg("--stateless-rpc"); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - let mut stdin = git_process.stdin.take().unwrap(); - let mut stdout = git_process.stdout.take().unwrap(); - let bytes = if gzip { - let mut decoder = GzDecoder::new(Cursor::new(payload)); - let mut decoded_data = Vec::new(); - if let Err(e) = io::copy(&mut decoder, &mut decoded_data) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - decoded_data - } else { - payload.to_vec() - }; - if let Err(e) = stdin.write_all(&bytes) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - drop(stdin); - Ok(fn_stream(move |emitter| async move { - let mut buffer = [0; 8192]; - loop { - match stdout.read(&mut buffer) { - Ok(0) => break, // EOF - Ok(n) => { - emitter - .emit(Ok(Bytes::copy_from_slice(&buffer[0..n]))) - .await; - } - Err(e) => { - emitter.emit(Err(Error::new(io::ErrorKind::Other, e))).await; - break; - } - } - } - })) - } - pub async fn pack_ssh( - &self, - path : String, - service : GitServiceType, - version : Option, - ) -> io::Result<( - tokio::process::ChildStdin, - (tokio::process::ChildStdout, tokio::process::ChildStderr), - )> { - let mut cmd = tokio::process::Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - Ok(( - git_process.stdin.take().unwrap(), - ( - git_process.stdout.take().unwrap(), - git_process.stderr.take().unwrap(), - ), - )) - } -} diff --git a/gitdata-gits/src/mount/s3.rs b/gitdata-gits/src/mount/s3.rs deleted file mode 100644 index 1623c3e..0000000 --- a/gitdata-gits/src/mount/s3.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::io; -use std::io::Cursor; -use std::io::Error; -use std::io::Read; -use std::io::Write; -use std::path::PathBuf; -use std::process::Command; -use std::process::Stdio; - -use actix_files::NamedFile; -use async_fn_stream::fn_stream; -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures_core::Stream; - -use crate::service::GitServiceType; - -#[derive(Clone)] -pub struct S3Storage { - pub root : PathBuf, -} - -impl S3Storage { - pub(crate) async fn refs( - &self, - path : &str, - service : GitServiceType, - version : Option<&str>, - ) -> io::Result { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("--stateless-rpc"); - cmd.arg("--advertise-refs"); - cmd.arg("."); - cmd.current_dir(self.root.join(path)); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("")); - } - let output = match cmd.output() { - Ok(output) => output, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - if !output.status.success() { - return Err(io::Error::new( - io::ErrorKind::Other, - "Error running command", - )); - } - Ok(String::from_utf8(output.stdout).unwrap_or("".to_string())) - } - pub(crate) async fn text(&self, path : &str, file_path : &str) -> io::Result { - let file_path = self.root.join(path).join(file_path); - if !file_path.exists() { - return Err(io::Error::new(io::ErrorKind::NotFound, "File not found")); - } - if file_path.is_dir() { - return Err(io::Error::new(io::ErrorKind::Other, "File is a directory")); - } - Ok(NamedFile::open(file_path)?) - } - pub(crate) async fn pack( - &self, - path : String, - service : GitServiceType, - version : Option, - gzip : bool, - payload : Bytes, - ) -> io::Result> + use<>> { - let mut cmd = Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("receive-pack"); - cmd.arg("--stateless-rpc"); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - let mut stdin = git_process.stdin.take().unwrap(); - let mut stdout = git_process.stdout.take().unwrap(); - let bytes = if gzip { - let mut decoder = GzDecoder::new(Cursor::new(payload)); - let mut decoded_data = Vec::new(); - if let Err(e) = io::copy(&mut decoder, &mut decoded_data) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - decoded_data - } else { - payload.to_vec() - }; - if let Err(e) = stdin.write_all(&bytes) { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - drop(stdin); - Ok(fn_stream(move |emitter| async move { - let mut buffer = [0; 8192]; - loop { - match stdout.read(&mut buffer) { - Ok(0) => break, - Ok(n) => { - emitter - .emit(Ok(Bytes::copy_from_slice(&buffer[0..n]))) - .await; - } - Err(e) => { - emitter.emit(Err(Error::new(io::ErrorKind::Other, e))).await; - break; - } - } - } - })) - } - pub async fn pack_ssh( - &self, - path : String, - service : GitServiceType, - version : Option, - ) -> io::Result<( - tokio::process::ChildStdin, - (tokio::process::ChildStdout, tokio::process::ChildStderr), - )> { - let mut cmd = tokio::process::Command::new("git"); - cmd.arg(service.to_string()); - cmd.arg("."); - if !version.is_some() { - cmd.env("GIT_PROTOCOL", version.unwrap_or("".to_string())); - } - cmd.stderr(Stdio::piped()); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.current_dir(self.root.join(path)); - let mut git_process = match cmd.spawn() { - Ok(process) => process, - Err(e) => { - return Err(Error::new( - io::ErrorKind::Other, - format!("Error running command {}", e), - )); - } - }; - Ok(( - git_process.stdin.take().unwrap(), - ( - git_process.stdout.take().unwrap(), - git_process.stderr.take().unwrap(), - ), - )) - } -} diff --git a/gitdata-gits/src/rpc/core_git.rs b/gitdata-gits/src/rpc/core_git.rs index fbbfc4a..756062d 100644 --- a/gitdata-gits/src/rpc/core_git.rs +++ b/gitdata-gits/src/rpc/core_git.rs @@ -1,7 +1,7 @@ +use std::path::PathBuf; + use async_trait::async_trait; -use gitdata::rpc; -use gitdata::rpc::core_git::RepositoryCreate; -use gitdata::rpc::core_git::RepositoryStoragePosition; +use gitdata::rpc::core_git::*; use tonic::Request; use tonic::Response; use tonic::Status; @@ -19,11 +19,172 @@ impl CoreGit { } #[async_trait] -impl rpc::core_git::rep_repository_server::RepRepository for CoreGit { +impl rep_repository_server::RepRepository for CoreGit { async fn create( &self, - request : Request, + request : Request, ) -> Result, Status> { - todo!() + let request = request.into_inner(); + let storage = match self.storage.node.get(&request.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage.create_repository(request.path.clone()).await { + Ok(_) => { + return Ok(Response::new(RepositoryStoragePosition { + node : request.node, + path : request.path, + })); + } + Err(e) => { + return Err(Status::internal(e.to_string())); + } + } + } + async fn add_file( + &self, + request : Request, + ) -> Result, Status> { + let request = request.into_inner(); + let node = match request.repository_storage_position { + Some(node) => node, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + let storage = match self.storage.node.get(&node.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage + .add_file( + node.path.clone(), + request.path.clone(), + request.content.clone(), + request.email, + request.user, + request.message, + request.file_name, + request.branch, + ) + .await + { + Ok(_) => { + return Ok(Response::new(RepositoryAddFilesResponse { + code : "200".to_string(), + })); + } + Err(e) => { + return Err(Status::internal(e.to_string())); + } + } + } + async fn sync_branch( + &self, + request : tonic::Request, + ) -> std::result::Result, tonic::Status> { + let request = request.into_inner(); + let storage = match self.storage.node.get(&request.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage.branch(request.path.clone()).await { + Ok(x) => { + return Ok(Response::new(RepositorySyncBranchResponse { branches : x })); + } + Err(e) => { + return Err(Status::internal(e.to_string())); + } + } + } + async fn sync_commit( + &self, + request : tonic::Request, + ) -> std::result::Result, tonic::Status> { + let request = request.into_inner(); + let node = match request.position { + Some(node) => node, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + let storage = match self.storage.node.get(&node.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage.commit(node.path.clone(), request.branch).await { + Ok(x) => { + Ok(Response::new(RepositorySyncCommitResponse { commits: x })) + } + Err(e) => { + Err(Status::internal(e.to_string())) + } + } + } + async fn sync_blob( + &self, + request : tonic::Request, + ) -> std::result::Result, tonic::Status> { + let request = request.into_inner(); + let node = match request.position { + Some(node) => node, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + let storage = match self.storage.node.get(&node.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage + .get_file( + node.path.clone(), + request.path.clone(), + request.hash.clone(), + ) + .await + { + Ok(x) => Ok(Response::new(RepositoryFileResponse { + hash : request.hash, + path : request.path.clone(), + size : x.len().to_string(), + name : PathBuf::from(request.path) + .file_name() + .unwrap_or("".as_ref()) + .to_str() + .unwrap_or("") + .to_string(), + mode : "0".to_string(), + content : x, + })), + Err(e) => Err(Status::internal(e.to_string())), + } + } + async fn sync_tags( + &self, + request : tonic::Request, + ) -> std::result::Result, tonic::Status> { + let request = request.into_inner(); + + let storage = match self.storage.node.get(&request.node) { + Some(storage) => storage, + None => { + return Err(Status::invalid_argument("node not found")); + } + }; + match storage.tage(request.path.clone()).await { + Ok(x) => Ok(Response::new(RepositoryTagsResponse { tags : x })), + Err(e) => Err(Status::internal(e.to_string())), + } } } diff --git a/gitdata-gits/src/rpc/git_core.rs b/gitdata-gits/src/rpc/git_core.rs index 0da1d18..9e90e3c 100644 --- a/gitdata-gits/src/rpc/git_core.rs +++ b/gitdata-gits/src/rpc/git_core.rs @@ -1,13 +1,11 @@ use std::sync::Arc; +use crate::rpc::NodePath; use futures::io; use gitdata::rpc::git_core; -use gitdata::rpc::git_core::Repository; +use gitdata::rpc::git_core::AccessResponse; use tokio::sync::Mutex; -use crate::rpc::NodePath; -use crate::rpc::RepositoryStoragePosition; - #[derive(Clone)] pub struct RepositoryRpc { client : @@ -15,32 +13,17 @@ pub struct RepositoryRpc { } impl RepositoryRpc { - pub async fn path( - &self, - owner : String, - repo : String, - ) -> io::Result { + pub async fn path(&self, owner : String, repo : String) -> io::Result { let mut client = self.client.lock().await; - let result = match client.path(git_core::PathRequest { owner, repo }).await { - Ok(x) => x.into_inner(), - Err(e) => { - return Err(io::Error::new(io::ErrorKind::Other, e)); + match client.path(git_core::PathRequest { owner, repo }).await { + Ok(x) => { + let x = x.into_inner(); + Ok(NodePath { + node : x.node, + path : x.path, + }) } - }; - match result.r#type { - 0 => Ok(RepositoryStoragePosition::Local(NodePath { - path : result.path, - node : result.node, - })), - 1 => Ok(RepositoryStoragePosition::S3(NodePath { - path : result.path, - node : result.node, - })), - 2 => Ok(RepositoryStoragePosition::Nfs(NodePath { - path : result.path, - node : result.node, - })), - _ => Err(io::Error::new(io::ErrorKind::Other, "unknown type")), + Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), } } @@ -49,13 +32,13 @@ impl RepositoryRpc { owner : String, repo : String, token : String, - ) -> io::Result> { + ) -> io::Result { let mut client = self.client.lock().await; match client .token(git_core::TokenRequest { owner, repo, token }) .await { - Ok(x) => Ok(x.into_inner().repositories), + Ok(x) => Ok(x.into_inner()), Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), } } @@ -65,7 +48,7 @@ impl RepositoryRpc { owner : String, repo : String, publickey : String, - ) -> io::Result> { + ) -> io::Result { let mut client = self.client.lock().await; match client .publickey(git_core::PublickeyRequest { @@ -75,7 +58,7 @@ impl RepositoryRpc { }) .await { - Ok(x) => Ok(x.into_inner().repositories), + Ok(x) => Ok(x.into_inner()), Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), } } diff --git a/gitdata-gits/src/rpc/mod.rs b/gitdata-gits/src/rpc/mod.rs index dd41ef1..2f54795 100644 --- a/gitdata-gits/src/rpc/mod.rs +++ b/gitdata-gits/src/rpc/mod.rs @@ -1,10 +1,6 @@ pub mod core_git; pub mod git_core; -use std::io; - -use async_trait::async_trait; - pub enum RepositoryAccess { None, Read, @@ -12,22 +8,6 @@ pub enum RepositoryAccess { Admin, } -#[derive(Clone)] -pub enum RepositoryStoragePosition { - Local(NodePath), - S3(NodePath), - Nfs(NodePath), -} -impl RepositoryStoragePosition { - pub fn path(&self) -> &str { - match self { - RepositoryStoragePosition::Local(p) => p.path.as_str(), - RepositoryStoragePosition::S3(p) => p.path.as_str(), - RepositoryStoragePosition::Nfs(p) => p.path.as_str(), - } - } -} - #[derive(Clone, Debug)] pub struct NodePath { pub path : String, diff --git a/gitdata-health/Cargo.toml b/gitdata-health/Cargo.toml new file mode 100644 index 0000000..fab92df --- /dev/null +++ b/gitdata-health/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gitdata-health" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/gitdata-health/src/main.rs b/gitdata-health/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/gitdata-health/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/gitdata-proxy/Cargo.toml b/gitdata-proxy/Cargo.toml new file mode 100644 index 0000000..dedb7cf --- /dev/null +++ b/gitdata-proxy/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gitdata-proxy" +version = "0.1.0" +edition = "2024" + +[dependencies] +hyper-reverse-proxy = "0.5" +hyper = { version = "~0.14", features = ["full"] } +tokio = { version = "1", features = ["full"] } + +gitdata = { path = ".."} \ No newline at end of file diff --git a/gitdata-proxy/src/main.rs b/gitdata-proxy/src/main.rs new file mode 100644 index 0000000..b2638c0 --- /dev/null +++ b/gitdata-proxy/src/main.rs @@ -0,0 +1,5 @@ +use gitdata::config::database::DatabaseConfig; + +fn main() { + DatabaseConfig::default().write().unwrap(); +} diff --git a/libs/config/email.rs b/libs/config/email.rs new file mode 100644 index 0000000..1125f94 --- /dev/null +++ b/libs/config/email.rs @@ -0,0 +1,77 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct EmailConfig { + #[serde(default)] + pub smtp: String, + #[serde(default)] + pub port: u32, + #[serde(default)] + pub username: String, + #[serde(default)] + pub password: String, + #[serde(default)] + pub from: String, +} + +impl EmailConfig { + pub fn new( + smtp: String, + port: u32, + username: String, + password: String, + from: String, + ) -> Self { + EmailConfig { + smtp, + port, + username, + password, + from, + } + } + pub fn save(&self) -> anyhow::Result<()> { + let config_file = std::env::var("CONFIG_FILE").unwrap_or("./config/email.toml".to_string()); + std::fs::write(config_file, toml::to_string_pretty(self)?)?; + Ok(()) + } + pub fn load() -> anyhow::Result { + let config_file = std::env::var("CONFIG_FILE").unwrap_or("./config/email.toml".to_string()); + let config = std::fs::read_to_string(config_file)?; + Ok(toml::from_str(&config)?) + } + pub fn check(&self) -> anyhow::Result<()> { + if self.smtp.is_empty() { + Self::default().save()?; + return Err(anyhow::anyhow!("smtp is empty")); + } + if self.port == 0 { + Self::default().save()?; + return Err(anyhow::anyhow!("port is empty")); + } + if self.username.is_empty() { + Self::default().save()?; + return Err(anyhow::anyhow!("username is empty")); + } + if self.password.is_empty() { + Self::default().save()?; + return Err(anyhow::anyhow!("password is empty")); + } + if self.from.is_empty() { + Self::default().save()?; + return Err(anyhow::anyhow!("from is empty")); + } + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_email_config() { + EmailConfig::default().save().ok(); + } +} \ No newline at end of file diff --git a/libs/config/git.rs b/libs/config/git.rs index 8b13789..0d9ccee 100644 --- a/libs/config/git.rs +++ b/libs/config/git.rs @@ -1 +1,59 @@ +use log::warn; +use serde::{Deserialize, Serialize}; +use tokio::sync::OnceCell; +#[derive(Deserialize,Serialize,Clone,Debug, Default)] +pub struct GitConfig { + pub http: String, + pub ssh: String, +} + +static GIT_CONFIG: OnceCell = OnceCell::const_new(); + + +impl GitConfig { + pub fn get() -> Self { + if let Some(config) = GIT_CONFIG.get() { + return config.clone(); + } + let config = GitConfig::load().unwrap_or_default(); + GIT_CONFIG.set(config.clone()).unwrap(); + config + } + pub fn new(http: String, ssh: String) -> Self { + GitConfig { http, ssh } + } + pub fn save(&self) -> anyhow::Result<()> { + if std::fs::read_dir("./config").is_err(){ + std::fs::create_dir("./config")?; + } + std::fs::write("./config/git.toml", toml::to_string(self)?)?; + Ok(()) + } + pub fn load() -> anyhow::Result { + if std::fs::read_dir("./config").is_err(){ + std::fs::create_dir("./config")?; + Self::default().save()?; + warn!("git config not found, use default config."); + return Ok(Self::default()); + } + let config = match std::fs::read_to_string("./config/git.toml"){ + Ok(config) => config, + Err(_) => { + Self::default().save()?; + warn!("git config not found, use default config."); + return Ok(Self::default()); + } + }; + let config = match toml::from_str::(&config){ + Ok(config) => config, + Err(_) => { + Self::default().save()?; + warn!("git config not found, use default config."); + return Ok(Self::default()); + } + }; + Ok(config) + } + +} \ No newline at end of file diff --git a/libs/config/mod.rs b/libs/config/mod.rs index c9322e4..f20a7d3 100644 --- a/libs/config/mod.rs +++ b/libs/config/mod.rs @@ -7,6 +7,8 @@ pub mod api; pub mod database; pub mod git; pub mod rpc; +pub mod email; + pub struct GitDataConfig {} impl GitDataConfig { diff --git a/libs/model/migrate/mod.rs b/libs/model/migrate/mod.rs new file mode 100644 index 0000000..2925fb4 --- /dev/null +++ b/libs/model/migrate/mod.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; +use sea_orm_migration::{MigrationTrait, MigratorTrait}; + +mod users; + + +pub struct DatabaseMigrate; +#[async_trait] +impl MigratorTrait for DatabaseMigrate { + fn migrations() -> Vec> { + vec![ + Box::new(users::UsersMigration) + ] + } +} \ No newline at end of file diff --git a/libs/model/migrate/users.rs b/libs/model/migrate/users.rs new file mode 100644 index 0000000..502eed2 --- /dev/null +++ b/libs/model/migrate/users.rs @@ -0,0 +1,311 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct UsersMigration; + +#[async_trait::async_trait] +impl MigrationTrait for UsersMigration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Email::Table) + .if_not_exists() + .col( + ColumnDef::new(Email::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Email::UserUid).uuid().not_null()) + .col(ColumnDef::new(Email::Name).string().not_null()) + .col(ColumnDef::new(Email::Email).string().not_null()) + .col( + ColumnDef::new(Email::CreatedAt) + .date_time() + .not_null() + .extra("DEFAULT CURRENT_TIMESTAMP".to_string()), + ) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(Follow::Table) + .if_not_exists() + .col( + ColumnDef::new(Follow::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Follow::UserUid).uuid().not_null()) + .col(ColumnDef::new(Follow::TargetUid).uuid().not_null()) + .col( + ColumnDef::new(Follow::CreatedAt) + .date_time() + .not_null() + .extra("DEFAULT CURRENT_TIMESTAMP".to_string()), + ) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(SshKeys::Table) + .if_not_exists() + .col( + ColumnDef::new(SshKeys::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(SshKeys::UserUid).uuid().not_null()) + .col(ColumnDef::new(SshKeys::Name).string().not_null()) + .col(ColumnDef::new(SshKeys::Description).string().null()) + .col(ColumnDef::new(SshKeys::SshKey).string().not_null()) + .col(ColumnDef::new(SshKeys::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(SshKeys::UpdatedAt).big_integer().not_null()) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(Stars::Table) + .if_not_exists() + .col( + ColumnDef::new(Stars::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Stars::RepositoryUid).uuid().not_null()) + .col(ColumnDef::new(Stars::UserUid).uuid().not_null()) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(Token::Table) + .if_not_exists() + .col( + ColumnDef::new(Token::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Token::Name).string().not_null()) + .col(ColumnDef::new(Token::Description).string().null()) + .col(ColumnDef::new(Token::UserUid).uuid().not_null()) + .col(ColumnDef::new(Token::Token).string().not_null()) + .col(ColumnDef::new(Token::Access).integer().not_null()) + .col(ColumnDef::new(Token::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Token::UpdatedAt).big_integer().not_null()) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(Users::Table) + .if_not_exists() + .col( + ColumnDef::new(Users::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Users::Username).string().not_null()) + .col(ColumnDef::new(Users::Name).string().not_null()) + .col(ColumnDef::new(Users::MainEmail).string().not_null()) + .col(ColumnDef::new(Users::EmailVisible).boolean().not_null()) + .col(ColumnDef::new(Users::HashPass).string().not_null()) + .col(ColumnDef::new(Users::Mindset).string().null()) + .col(ColumnDef::new(Users::State).string().not_null()) + .col(ColumnDef::new(Users::AvatarUrl).string().null()) + .col(ColumnDef::new(Users::Company).string().null()) + .col(ColumnDef::new(Users::JobTitle).string().null()) + .col(ColumnDef::new(Users::Website).string().null()) + .col(ColumnDef::new(Users::Social).array(ColumnType::Text).null()) + .col(ColumnDef::new(Users::Bio).string().null()) + .col(ColumnDef::new(Users::Location).string().null()) + .col(ColumnDef::new(Users::Appellative).string().null()) + .col(ColumnDef::new(Users::Topic).array(ColumnType::Text).null()) + .col(ColumnDef::new(Users::Pinned).array(ColumnType::Uuid).null()) + .col(ColumnDef::new(Users::RepositoryLimit).integer().not_null()) + .col(ColumnDef::new(Users::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Users::UpdatedAt).big_integer().not_null()) + .col(ColumnDef::new(Users::LastUsed).big_integer().null()) + .col(ColumnDef::new(Users::Professional).boolean().not_null()) + .col(ColumnDef::new(Users::ProfessionalEndTime).big_integer().null()) + .col(ColumnDef::new(Users::ProfessionalStartTime).big_integer().null()) + .col(ColumnDef::new(Users::Organize).boolean().not_null()) + .col(ColumnDef::new(Users::Member).array(ColumnType::Uuid).null()) + .col(ColumnDef::new(Users::Team).array(ColumnType::Uuid).null()) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(Watch::Table) + .if_not_exists() + .col( + ColumnDef::new(Watch::Uid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Watch::UserUid).uuid().not_null()) + .col(ColumnDef::new(Watch::RepositoryUid).uuid().not_null()) + .col(ColumnDef::new(Watch::CreatedAt).big_integer().not_null()) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Email::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(Follow::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(SshKeys::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(Stars::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(Token::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(Users::Table).to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(Watch::Table).to_owned()) + .await?; + + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Email { + #[sea_orm(iden = "emails")] + Table, + Uid, + UserUid, + Name, + Email, + CreatedAt, +} + +#[derive(DeriveIden)] +enum Follow { + #[sea_orm(iden = "follows")] + Table, + Uid, + UserUid, + TargetUid, + CreatedAt, +} + +#[derive(DeriveIden)] +enum SshKeys { + #[sea_orm(iden = "ssh_keys")] + Table, + Uid, + UserUid, + Name, + Description, + SshKey, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Stars { + #[sea_orm(iden = "stars")] + Table, + Uid, + RepositoryUid, + UserUid, +} + +#[derive(DeriveIden)] +enum Token { + #[sea_orm(iden = "token_keys")] + Table, + Uid, + Name, + Description, + UserUid, + Token, + Access, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Users { + #[sea_orm(iden = "users")] + Table, + Uid, + Username, + Name, + MainEmail, + EmailVisible, + HashPass, + Mindset, + State, + AvatarUrl, + Company, + JobTitle, + Website, + Social, + Bio, + Location, + Appellative, + Topic, + Pinned, + RepositoryLimit, + CreatedAt, + UpdatedAt, + LastUsed, + Professional, + ProfessionalEndTime, + ProfessionalStartTime, + Organize, + Member, + Team, +} + +#[derive(DeriveIden)] +enum Watch { + #[sea_orm(iden = "watch")] + Table, + Uid, + UserUid, + RepositoryUid, + CreatedAt, +} \ No newline at end of file diff --git a/libs/model/mod.rs b/libs/model/mod.rs index 329e235..83a521c 100644 --- a/libs/model/mod.rs +++ b/libs/model/mod.rs @@ -17,3 +17,4 @@ pub mod pr; pub mod reactions; pub mod repository; pub mod users; +pub mod migrate; diff --git a/libs/model/repository/repository.rs b/libs/model/repository/repository.rs index dd74510..71d257f 100644 --- a/libs/model/repository/repository.rs +++ b/libs/model/repository/repository.rs @@ -11,6 +11,7 @@ use sea_orm::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::config::git::GitConfig; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeriveEntityModel)] #[sea_orm(table_name = "repository")] @@ -55,4 +56,48 @@ impl Entity { pub fn find_by_uid(uid: Uuid) -> Select { Entity::find().filter(Column::Uid.eq(uid)) } + pub fn find_by_name(name: String) -> Select { + Entity::find().filter(Column::Name.eq(name)) + } } + + +impl ActiveModel { + pub fn new( + name: String, + owner_uid: Uuid, + description: Option, + visible: bool, + default_branch: Option, + node: String, + ) -> Self { + let git_config = GitConfig::get(); + Self { + uid: Set(Uuid::new_v4()), + name: Set(name.clone()), + owner_uid: Set(owner_uid), + description: Set(description), + visible: Set(visible), + default_branch: Set(default_branch.unwrap_or("".to_string())), + template: Set(false), + mirrors: Set(false), + archive: Set(false), + archive_time: Set(None), + ssh_path: Set(format!("{}:{}/{}", git_config.ssh, owner_uid, name)), + http_path: Set(format!("{}/{}/{}", git_config.http, owner_uid, name)), + storage_node: Set(node), + fork: Set(false), + fork_uid: Set(None), + nums_star: Set(0), + nums_fork: Set(0), + nums_watch: Set(0), + nums_issue: Set(0), + nums_pull: Set(0), + nums_commit: Set(0), + head: Set("".parse().unwrap()), + license: Set(vec![]), + created_at: Set(chrono::Utc::now().timestamp()), + updated_at: Set(chrono::Utc::now().timestamp()), + } + } +} \ No newline at end of file diff --git a/libs/model/users/emails.rs b/libs/model/users/emails.rs index f0719c5..2079023 100644 --- a/libs/model/users/emails.rs +++ b/libs/model/users/emails.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "email")] +#[sea_orm(table_name = "emails")] pub struct Model { #[sea_orm(primary_key)] pub uid: Uuid, diff --git a/libs/model/users/follow.rs b/libs/model/users/follow.rs index 77c12af..7f4859f 100644 --- a/libs/model/users/follow.rs +++ b/libs/model/users/follow.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "follow")] +#[sea_orm(table_name = "follows")] pub struct Model { #[sea_orm(primary_key)] pub uid: Uuid, diff --git a/libs/model/users/token_key.rs b/libs/model/users/token_key.rs index e46b881..439f02a 100644 --- a/libs/model/users/token_key.rs +++ b/libs/model/users/token_key.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, DeriveEntityModel)] -#[sea_orm(table_name = "token")] +#[sea_orm(table_name = "token_keys")] pub struct Model { #[sea_orm(primary_key)] pub uid: Uuid, diff --git a/libs/model/users/users.rs b/libs/model/users/users.rs index cfdaa02..445f3e9 100644 --- a/libs/model/users/users.rs +++ b/libs/model/users/users.rs @@ -36,7 +36,7 @@ pub struct Model { pub topic: Vec, pub pinned: Vec, - pub repository_limit: u64, // default 20 + pub repository_limit: i32, // default 20 pub created_at: i64, pub updated_at: i64, diff --git a/libs/model/users/watch.rs b/libs/model/users/watch.rs index 4049018..74b8623 100644 --- a/libs/model/users/watch.rs +++ b/libs/model/users/watch.rs @@ -18,7 +18,7 @@ pub struct Model { pub uid: Uuid, pub user_uid: Uuid, pub repository_uid: Uuid, - pub created_at: chrono::DateTime, + pub created_at: i64, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} diff --git a/libs/rpc/core_git.rs b/libs/rpc/core_git.rs new file mode 100644 index 0000000..5d6bb3a --- /dev/null +++ b/libs/rpc/core_git.rs @@ -0,0 +1,815 @@ +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryStoragePosition { + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub node: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryAddFileRequest { + #[prost(message, optional, tag = "1")] + pub repository_storage_position: ::core::option::Option, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "3")] + pub content: ::prost::alloc::vec::Vec, + #[prost(string, tag = "4")] + pub email: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub user: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub message: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub file_name: ::prost::alloc::string::String, + #[prost(string, tag = "8")] + pub branch: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryAddFilesResponse { + #[prost(string, tag = "1")] + pub code: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositorySyncBranchResponse { + #[prost(message, repeated, tag = "1")] + pub branches: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositorySyncCommitResponse { + #[prost(message, repeated, tag = "1")] + pub commits: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryBranch { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub head: ::prost::alloc::string::String, + #[prost(int32, tag = "3")] + pub commit_nums: i32, + #[prost(string, optional, tag = "4")] + pub from: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag = "5")] + pub to: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, tag = "6")] + pub start: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryCommit { + #[prost(string, tag = "1")] + pub hash: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub author: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub email: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub date: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub branch: ::prost::alloc::string::String, + #[prost(int32, tag = "7")] + pub file_nums: i32, + #[prost(message, repeated, tag = "8")] + pub tree: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryTree { + #[prost(string, tag = "1")] + pub hash: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub size: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub mode: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryFileResponse { + #[prost(string, tag = "1")] + pub hash: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub size: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub mode: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "7")] + pub content: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryFileRequest { + #[prost(message, optional, tag = "1")] + pub position: ::core::option::Option, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub hash: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryTagsResponse { + #[prost(message, repeated, tag = "1")] + pub tags: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryTags { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub hash: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub date: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub author: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub email: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryCommitRequest { + #[prost(message, optional, tag = "1")] + pub position: ::core::option::Option, + #[prost(string, tag = "2")] + pub branch: ::prost::alloc::string::String, +} +/// Generated client implementations. +pub mod rep_repository_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct RepRepositoryClient { + inner: tonic::client::Grpc, + } + impl RepRepositoryClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl RepRepositoryClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> RepRepositoryClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + RepRepositoryClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn create( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/Create", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "Create")); + self.inner.unary(req, path, codec).await + } + pub async fn add_file( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/AddFile", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "AddFile")); + self.inner.unary(req, path, codec).await + } + pub async fn sync_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/SyncBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "SyncBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn sync_commit( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/SyncCommit", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "SyncCommit")); + self.inner.unary(req, path, codec).await + } + pub async fn sync_blob( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/SyncBlob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "SyncBlob")); + self.inner.unary(req, path, codec).await + } + pub async fn sync_tags( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/core_git.RepRepository/SyncTags", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("core_git.RepRepository", "SyncTags")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod rep_repository_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with RepRepositoryServer. + #[async_trait] + pub trait RepRepository: std::marker::Send + std::marker::Sync + 'static { + async fn create( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn add_file( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn sync_branch( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn sync_commit( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn sync_blob( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn sync_tags( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct RepRepositoryServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl RepRepositoryServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for RepRepositoryServer + where + T: RepRepository, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/core_git.RepRepository/Create" => { + #[allow(non_camel_case_types)] + struct CreateSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for CreateSvc { + type Response = super::RepositoryStoragePosition; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/core_git.RepRepository/AddFile" => { + #[allow(non_camel_case_types)] + struct AddFileSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for AddFileSvc { + type Response = super::RepositoryAddFilesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::add_file(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = AddFileSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/core_git.RepRepository/SyncBranch" => { + #[allow(non_camel_case_types)] + struct SyncBranchSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for SyncBranchSvc { + type Response = super::RepositorySyncBranchResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::sync_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SyncBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/core_git.RepRepository/SyncCommit" => { + #[allow(non_camel_case_types)] + struct SyncCommitSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for SyncCommitSvc { + type Response = super::RepositorySyncCommitResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::sync_commit(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SyncCommitSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/core_git.RepRepository/SyncBlob" => { + #[allow(non_camel_case_types)] + struct SyncBlobSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for SyncBlobSvc { + type Response = super::RepositoryFileResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::sync_blob(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SyncBlobSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/core_git.RepRepository/SyncTags" => { + #[allow(non_camel_case_types)] + struct SyncTagsSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for SyncTagsSvc { + type Response = super::RepositoryTagsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::sync_tags(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SyncTagsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for RepRepositoryServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "core_git.RepRepository"; + impl tonic::server::NamedService for RepRepositoryServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/libs/rpc/git_core.rs b/libs/rpc/git_core.rs new file mode 100644 index 0000000..8c949a2 --- /dev/null +++ b/libs/rpc/git_core.rs @@ -0,0 +1,505 @@ +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryStoragePosition { + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub node: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct AccessResponse { + #[prost(enumeration = "RepositoryAccess", tag = "1")] + pub access: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PathRequest { + #[prost(string, tag = "1")] + pub owner: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub repo: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TokenRequest { + #[prost(string, tag = "1")] + pub owner: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub repo: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub token: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PublickeyRequest { + #[prost(string, tag = "1")] + pub owner: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub repo: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub publickey: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum RepositoryAccess { + None = 0, + Read = 1, + Write = 4, + Admin = 7, +} +impl RepositoryAccess { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::None => "NONE", + Self::Read => "READ", + Self::Write => "WRITE", + Self::Admin => "ADMIN", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "NONE" => Some(Self::None), + "READ" => Some(Self::Read), + "WRITE" => Some(Self::Write), + "ADMIN" => Some(Self::Admin), + _ => None, + } + } +} +/// Generated client implementations. +pub mod rep_repository_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct RepRepositoryClient { + inner: tonic::client::Grpc, + } + impl RepRepositoryClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl RepRepositoryClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> RepRepositoryClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + RepRepositoryClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn path( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/git_core.RepRepository/Path", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("git_core.RepRepository", "Path")); + self.inner.unary(req, path, codec).await + } + pub async fn token( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/git_core.RepRepository/Token", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("git_core.RepRepository", "Token")); + self.inner.unary(req, path, codec).await + } + pub async fn publickey( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/git_core.RepRepository/Publickey", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("git_core.RepRepository", "Publickey")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod rep_repository_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with RepRepositoryServer. + #[async_trait] + pub trait RepRepository: std::marker::Send + std::marker::Sync + 'static { + async fn path( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn token( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn publickey( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct RepRepositoryServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl RepRepositoryServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for RepRepositoryServer + where + T: RepRepository, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/git_core.RepRepository/Path" => { + #[allow(non_camel_case_types)] + struct PathSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService for PathSvc { + type Response = super::RepositoryStoragePosition; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::path(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = PathSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/git_core.RepRepository/Token" => { + #[allow(non_camel_case_types)] + struct TokenSvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService for TokenSvc { + type Response = super::AccessResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::token(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = TokenSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/git_core.RepRepository/Publickey" => { + #[allow(non_camel_case_types)] + struct PublickeySvc(pub Arc); + impl< + T: RepRepository, + > tonic::server::UnaryService + for PublickeySvc { + type Response = super::AccessResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::publickey(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = PublickeySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for RepRepositoryServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "git_core.RepRepository"; + impl tonic::server::NamedService for RepRepositoryServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/proto/core-git.proto b/proto/core-git.proto index 499be49..88f5e1c 100644 --- a/proto/core-git.proto +++ b/proto/core-git.proto @@ -6,21 +6,99 @@ package core_git; -enum RepositoryStoragePositionType { - LOCAL = 0; - S3 = 1; - NFS = 2; -} + message RepositoryStoragePosition { - RepositoryStoragePositionType type = 1; string path = 2; string node = 3; } -message RepositoryCreate { - RepositoryStoragePosition storage_position = 1; + +message RepositoryAddFileRequest { + RepositoryStoragePosition RepositoryStoragePosition = 1; + string path = 2; + bytes content = 3; + string email = 4; + string user = 5; + string message = 6; + string file_name = 7; + string branch = 8; } +message RepositoryAddFilesResponse { + string code = 1; +} service RepRepository { - rpc Create(RepositoryCreate) returns (RepositoryStoragePosition) {} + rpc Create(RepositoryStoragePosition) returns (RepositoryStoragePosition) {} + rpc AddFile(RepositoryAddFileRequest) returns (RepositoryAddFilesResponse) {} + rpc SyncBranch(RepositoryStoragePosition) returns (RepositorySyncBranchResponse) {} + rpc SyncCommit(RepositoryCommitRequest) returns (RepositorySyncCommitResponse) {} + rpc SyncBlob(RepositoryFileRequest) returns (RepositoryFileResponse) {} + rpc SyncTags(RepositoryStoragePosition) returns (RepositoryTagsResponse) {} +} + +message RepositorySyncBranchResponse { + repeated RepositoryBranch branches = 1; } +message RepositorySyncCommitResponse { + repeated RepositoryCommit commits = 1; +} + +message RepositoryBranch { + string name = 1; + string head = 2; + int32 commit_nums = 3; + optional string from = 4; + optional string to = 5; + bool start = 6; +} + +message RepositoryCommit { + string hash = 1; + string message = 2; + string author = 3; + string email = 4; + string date = 5; + string branch = 6; + int32 file_nums = 7; + repeated RepositoryTree tree = 8; +} + +message RepositoryTree { + string hash = 1; + string path = 2; + string size = 4; + string name = 5; + string mode = 6; +} + +message RepositoryFileResponse { + string hash = 1; + string path = 2; + string size = 4; + string name = 5; + string mode = 6; + bytes content = 7; +} + +message RepositoryFileRequest { + RepositoryStoragePosition position = 1; + string path = 2; + string hash = 3; +} + +message RepositoryTagsResponse { + repeated RepositoryTags tags = 1; +} + +message RepositoryTags { + string name = 1; + string hash = 2; + string date = 3; + string author = 4; + string email = 5; +} + +message RepositoryCommitRequest { + RepositoryStoragePosition position = 1; + string branch = 2; +} \ No newline at end of file diff --git a/proto/git-core.proto b/proto/git-core.proto index 48a228e..5034da5 100644 --- a/proto/git-core.proto +++ b/proto/git-core.proto @@ -7,32 +7,24 @@ package git_core; enum RepositoryAccess { NONE = 0; READ = 1; - WRITE = 2; - ADMIN = 3; + WRITE = 4; + ADMIN = 7; } -enum RepositoryStoragePositionType { - LOCAL = 0; - S3 = 1; - NFS = 2; -} message RepositoryStoragePosition { - RepositoryStoragePositionType type = 1; string path = 2; string node = 3; } -message RepositoryCreate { - RepositoryStoragePosition storage_position = 1; -} service RepRepository { rpc Path(PathRequest) returns (RepositoryStoragePosition) {} - rpc Token(TokenRequest) returns (RepositoryList) {} - rpc Publickey(PublickeyRequest) returns (RepositoryList) {} - rpc Create(RepositoryCreate) returns (RepositoryStoragePosition) {} + rpc Token(TokenRequest) returns (AccessResponse) {} + rpc Publickey(PublickeyRequest) returns (AccessResponse) {} +} +message AccessResponse { + RepositoryAccess access = 1; } - message PathRequest { string owner = 1; string repo = 2; @@ -49,34 +41,3 @@ message PublickeyRequest { string repo = 2; string publickey = 3; } -message RepositoryList { - repeated Repository repositories = 1; -} -message Repository { - string uid = 1; - string name = 2; - string description = 3; - string owner_uid = 4; - string default_branch = 5; - bool visible = 6; - bool template = 7; - bool mirrors = 8; - bool archive = 9; - int64 archive_time = 10; - string ssh_path = 11; - string http_path = 12; - bool fork = 13; - string storage_node = 25; - string fork_uid = 14; - int64 nums_star = 15; - int64 nums_fork = 16; - int64 nums_watch = 17; - int64 nums_issue = 18; - int64 nums_pull = 19; - int64 nums_commit = 20; - string head = 21; - repeated string license = 22; - int64 created_at = 23; - int64 updated_at = 24; -} -