From 58fa65fb6b4b4cfec79b6e722690e0ccde9713c8 Mon Sep 17 00:00:00 2001 From: missing Date: Tue, 2 Aug 2022 12:28:52 -0500 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 1619 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 26 + README.md | 51 ++ src/create.sql | 60 ++ src/db.rs | 147 +++++ src/download.rs | 29 + src/index.rs | 188 ++++++ src/main.rs | 256 ++++++++ src/new_crate.rs | 251 +++++++ src/owners.rs | 135 ++++ src/search.rs | 95 +++ src/yank.rs | 86 +++ 13 files changed, 2944 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/create.sql create mode 100644 src/db.rs create mode 100644 src/download.rs create mode 100644 src/index.rs create mode 100644 src/main.rs create mode 100644 src/new_crate.rs create mode 100644 src/owners.rs create mode 100644 src/search.rs create mode 100644 src/yank.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b9ba169 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1619 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" + +[[package]] +name = "crossbeam-queue" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd29dbba58ee5314f3ec570066d78a3f4772bf45b322efcf2ce2a43af69a4d85" + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "md-5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[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.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "registry" +version = "0.1.0" +dependencies = [ + "async-trait", + "axum", + "futures-core", + "futures-util", + "hmac-sha256", + "once_cell", + "semver", + "serde", + "serde_json", + "sqlx", + "tokio", + "tokio-util", + "toml", + "tower", + "tower-http", + "tracing-subscriber", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f82cbe94f41641d6c410ded25bbf5097c240cefdf8e3b06d04198d0a96af6a4" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" +dependencies = [ + "ahash", + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dirs", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hkdf", + "hmac", + "indexmap", + "itoa", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha-1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "webpki-roots", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f" +dependencies = [ + "dotenv", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "web-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..be9ddf7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "registry" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.56" +axum = "0.5.13" +futures-core = "0.3.21" +futures-util = "0.3.21" +hmac-sha256 = "1.1.4" +once_cell = "1.13.0" +semver = { version = "1.0.12", features = ["serde"] } +serde = { version = "1.0.140", features = ["derive"] } +serde_json = "1.0.82" +sqlx = { version = "0.6.0", features = ["runtime-tokio-rustls", "postgres"] } +tokio = { version = "1.20.1", features = ["full"] } +tokio-util = { version = "0.7.3", features = ["io"] } +toml = "0.5.9" +tower = "0.4.13" +tower-http = { version = "0.3.4", features = ["trace"] } +tracing-subscriber = "0.3.15" + +[features] diff --git a/README.md b/README.md new file mode 100644 index 0000000..820e195 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# warehouse + +A ~~simple~~ cargo registry. + +To run this, you need to: + - Create a `registry.toml` file in the current directory with the following contents: + ```toml + working_dir = "/path/to/some/directory" + postgres_uri = "postgres://1.2.3.4/db_name" + listen_uri = "0.0.0.0:1234" + ``` + + - Run a PostgreSQL database at the specified URI. + + - Initialize a (not bare) git repository at `the_working_dir/index` + + - Create a `index/config.json` file, with the following contents: + ```json + { + "dl": "http://your.website:1234/api/v1/crates", + "api": "http://your.website:1234" + } + ``` + HTTPS is currently broken for an unknown reason. + + - Write the following to `index/.git/hooks/post-commit`: + ```sh + #!/bin/sh + + exec git update-server-info + ``` + + - Commit the `config.json` to git. + + - Run any webserver at `index/.git`. (another method of serving git would also work) + + Note: you may need to set `net.git-fetch-with-cli = true` in your `~/.cargo/config.toml` + +The URL of that webserver/git repository is the URL of the registry. + +Users must add the following to their `~/.cargo/config.toml`: +```toml +[registries] +foobarbaz = { index = "http://the.index.url" } +``` + +Creating accounts is currently not managed by this code. You will have to run the following query to add a user: +```sql +INSERT INTO users (login, credential) VALUES ('username', 'account token') +``` +You can then give out the token for someone else to use with `cargo login`. \ No newline at end of file diff --git a/src/create.sql b/src/create.sql new file mode 100644 index 0000000..0dd7667 --- /dev/null +++ b/src/create.sql @@ -0,0 +1,60 @@ +CREATE TABLE IF NOT EXISTS users ( + id int NOT NULL UNIQUE GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + login text NOT NULL UNIQUE, + credential text NOT NULL UNIQUE, + name text +); + +CREATE TABLE IF NOT EXISTS crates ( + id int NOT NULL UNIQUE GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name text NOT NULL UNIQUE, + publisher int NOT NULL REFERENCES users (id), + owners int[] NOT NULL +); + +CREATE TYPE version_feature AS ( + feature text, + enables text[] +); + +CREATE TABLE IF NOT EXISTS versions ( + id int NOT NULL UNIQUE GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + vers text NOT NULL, + cksum char(64) NOT NULL, + yanked boolean NOT NULL, + links text, + crate_id int NOT NULL REFERENCES crates (id), + features version_feature[] NOT NULL, + authors text[] NOT NULL, + description text, + documentation text, + homepage text, + readme text, + readme_file text, + keywords text[] NOT NULL, + categories text[] NOT NULL, + license text, + license_file text, + repository text, + badges jsonb NOT NULL +); + +CREATE TYPE dependency_kind AS ENUM ( + 'dev', + 'build', + 'normal' +); + +CREATE TABLE IF NOT EXISTS deps ( + id int NOT NULL UNIQUE GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name text NOT NULL REFERENCES crates (name), + version_req text NOT NULL, + optional boolean NOT NULL, + default_features boolean NOT NULL, + target text, + kind dependency_kind NOT NULL, + registry text, + package text, + features text[] NOT NULL, + version_id int NOT NULL REFERENCES versions (id) +); diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..78aadad --- /dev/null +++ b/src/db.rs @@ -0,0 +1,147 @@ +use std::collections::HashMap; + +use once_cell::sync::OnceCell; +use sqlx::{ + error::BoxDynError, + postgres::{types::Oid, PgHasArrayType, PgTypeInfo}, + query_as, + types::Json, + Decode, Encode, Executor, FromRow, Postgres, Type, +}; + +#[derive(Clone, Copy, Default, Debug)] +#[repr(transparent)] +pub struct PgU32(pub u32); + +impl PgU32 { + fn to_i32(self) -> i32 { + i32::from_ne_bytes(self.0.to_ne_bytes()) + } + + fn from_i32(v: i32) -> Self { + Self(u32::from_ne_bytes(v.to_ne_bytes())) + } +} + +impl<'r> Decode<'r, Postgres> for PgU32 { + fn decode( + value: >::ValueRef, + ) -> Result { + i32::decode(value).map(Self::from_i32) + } +} + +impl<'q> Encode<'q, Postgres> for PgU32 { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + self.to_i32().encode(buf) + } + + fn produces(&self) -> Option<::TypeInfo> { + self.to_i32().produces() + } + + fn size_hint(&self) -> usize { + self.to_i32().size_hint() + } +} + +impl Type for PgU32 { + fn type_info() -> ::TypeInfo { + i32::type_info() + } +} + +impl PgHasArrayType for PgU32 { + fn array_type_info() -> PgTypeInfo { + i32::array_type_info() + } +} + +#[derive(FromRow)] +pub struct DbUser { + pub id: PgU32, + pub login: String, + pub credential: String, + pub name: Option, +} + +#[derive(FromRow)] +pub struct DbCrate { + pub id: PgU32, + pub name: String, + pub publisher: PgU32, + pub owners: Vec, +} + +#[derive(FromRow)] +pub struct DbVersion { + pub id: PgU32, + pub vers: String, + pub cksum: String, + pub yanked: bool, + pub links: Option, + pub crate_id: PgU32, + pub features: Vec, + pub authors: Vec, + pub description: Option, + pub documentation: Option, + pub homepage: Option, + pub readme: Option, + pub readme_file: Option, + pub keywords: Vec, + pub categories: Vec, + pub license: Option, + pub license_file: Option, + pub repository: Option, + pub badges: Json>>, +} + +#[derive(FromRow)] +pub struct DbDep { + pub id: PgU32, + pub name: String, + pub version_req: String, + pub optional: bool, + pub default_features: bool, + pub target: Option, + pub kind: String, + pub registry: Option, + pub package: Option, + pub features: Vec, + pub version_id: PgU32, +} + +#[derive(Type)] +#[sqlx(type_name = "version_feature")] +pub struct DbVersionFeature { + pub feature: String, + pub enables: Vec, +} + +static VERSION_FEATURE_ARRAY_OID: OnceCell = OnceCell::new(); + +pub async fn init<'c, E: Executor<'c, Database = Postgres> + Copy>( + e: E, +) -> Result<(), BoxDynError> { + // explicitly ignore the result, since it currently throws an error if the type already exists + let _ = e.execute(include_str!("create.sql")).await; + + let (oid,): (Oid,) = query_as("SELECT typarray FROM pg_type WHERE typname = 'version_feature'") + .fetch_one(e) + .await?; + + VERSION_FEATURE_ARRAY_OID + .set(oid) + .expect("db::init called multiple times"); + + Ok(()) +} + +impl PgHasArrayType for DbVersionFeature { + fn array_type_info() -> PgTypeInfo { + PgTypeInfo::with_oid(*VERSION_FEATURE_ARRAY_OID.get().unwrap()) + } +} diff --git a/src/download.rs b/src/download.rs new file mode 100644 index 0000000..dfa0202 --- /dev/null +++ b/src/download.rs @@ -0,0 +1,29 @@ +use std::sync::Arc; + +use axum::{ + body::{BoxBody, StreamBody}, + extract::Path, + http::StatusCode, + Extension, +}; +use semver::Version; +use tokio::fs::File; +use tokio_util::io::ReaderStream; + +use crate::State; + +pub async fn download( + Path((crate_name, version)): Path<(String, Version)>, + Extension(state): Extension>, +) -> Result { + let mut path = state.crate_dir.clone(); + path.push(&crate_name); + path.push(version.to_string()); + path.push("crate.crate"); + let file = match File::open(path).await { + Ok(v) => v, + Err(_) => return Err(StatusCode::NOT_FOUND), + }; + + Ok(BoxBody::new(StreamBody::new(ReaderStream::new(file)))) +} diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..4602e18 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,188 @@ +use std::{collections::HashMap, process::Stdio, str::FromStr}; + +use futures_util::StreamExt; +use semver::{Version, VersionReq}; +use serde::{Deserialize, Serialize}; +use sqlx::query_as; +use tokio::{ + fs::{self, OpenOptions}, + io::AsyncWriteExt, + process::Command, +}; + +use crate::{ + db::{DbDep, DbVersion, PgU32}, + db_error, get_crate_prefix, internal_error, Errors, State, INDEX_LOCK, +}; + +#[derive(Serialize, Deserialize)] +pub struct CrateVersion { + pub name: String, + pub vers: Version, + pub deps: Vec, + pub cksum: String, + pub features: HashMap>, + pub yanked: bool, + pub links: Option, + /// Should always be `1` for maximum compatability + pub v: u32, + // `features2` exists, but for out purposes, it can be ignored. See https://doc.rust-lang.org/cargo/reference/registries.html +} + +#[derive(Serialize, Deserialize)] +pub struct Dependency { + #[serde(flatten)] + pub base: BaseDependency, + pub package: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct BaseDependency { + pub name: String, + pub version_req: VersionReq, + pub features: Vec, + pub optional: bool, + pub default_features: bool, + pub target: Option, + pub kind: DependencyKind, + pub registry: Option, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub enum DependencyKind { + Dev, + Build, + Normal, +} + +impl DependencyKind { + pub fn to_db_repr(self) -> &'static str { + match self { + DependencyKind::Dev => "dev", + DependencyKind::Build => "build", + DependencyKind::Normal => "normal", + } + } +} + +impl FromStr for DependencyKind { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "dev" => Ok(DependencyKind::Dev), + "build" => Ok(DependencyKind::Build), + "normal" => Ok(DependencyKind::Normal), + _ => Err(()), + } + } +} + +pub async fn update_crate_from_db( + crate_id: PgU32, + state: &State, + message: &str, +) -> Result<(), Errors> { + let lock = INDEX_LOCK.lock().await; + + let mut db = state.db.acquire().await.map_err(db_error)?; + + let (mut crate_name,): (String,) = query_as("SELECT name FROM crates WHERE id = $1") + .bind(crate_id) + .fetch_one(&mut db) + .await + .map_err(db_error)?; + + // we use `fetch_all` here since we cant use `fetch` because of conflicting mutable borrows + let versions = query_as::<_, DbVersion>("SELECT * FROM versions WHERE crate_id = $1") + .bind(crate_id) + .fetch_all(&mut db) + .await + .map_err(db_error)?; + + let mut versions2 = Vec::new(); + for version in versions { + let mut deps = query_as::<_, DbDep>("SELECT * FROM deps WHERE version_id = $1") + .bind(version.id) + .fetch(&mut db); + + let mut deps2 = Vec::new(); + while let Some(dep) = deps.next().await.transpose().map_err(db_error)? { + deps2.push(Dependency { + base: BaseDependency { + name: dep.name, + version_req: dep.version_req.parse().unwrap(), + features: dep.features, + optional: dep.optional, + default_features: dep.default_features, + target: dep.target, + kind: dep.kind.parse().unwrap(), + registry: dep.registry, + }, + package: dep.package, + }) + } + + versions2.push(CrateVersion { + name: crate_name.clone(), + vers: version.vers.parse().unwrap(), + deps: deps2, + cksum: version.cksum, + features: version + .features + .into_iter() + .map(|v| (v.feature, v.enables)) + .collect(), + yanked: version.yanked, + links: version.links, + v: 1, + }); + } + + crate_name.make_ascii_lowercase(); + + let mut path = state.index_dir.clone(); + path.push(&get_crate_prefix(&crate_name).unwrap()); + fs::create_dir_all(&path).await.map_err(internal_error)?; + path.push(&crate_name); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&path) + .await + .map_err(internal_error)?; + + for version in versions2 { + let mut buf = serde_json::to_vec(&version).map_err(internal_error)?; + buf.push(b'\n'); + file.write_all(&buf).await.map_err(internal_error)?; + } + + Command::new("git") + .arg("add") + .arg(&path) + .current_dir(&state.index_dir) + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .status() + .await + .map_err(internal_error)?; + + Command::new("git") + .arg("commit") + .arg("-m") + .arg(message) + .current_dir(&state.index_dir) + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .status() + .await + .map_err(internal_error)?; + + drop(lock); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5bf1120 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,256 @@ +mod db; +mod download; +mod index; +mod new_crate; +mod owners; +mod search; +mod yank; + +use async_trait::async_trait; +use axum::{ + body::Body, + extract::{FromRequest, RequestParts}, + http::{header::AUTHORIZATION, StatusCode}, + response::{IntoResponse, Response}, + routing::{delete, get, put}, + Extension, Json, Router, +}; +use db::DbUser; +use download::download; +use owners::{add_owners, list_owners, remove_owners}; +use search::search; +use serde::{ser::SerializeStruct, Deserialize, Serialize}; +use sqlx::{error::BoxDynError, postgres::PgPoolOptions, query_as, Pool, Postgres}; +use std::{fmt::Display, net::SocketAddr, path::PathBuf, sync::Arc}; +use tokio::{fs, sync::Mutex}; +use tower::ServiceBuilder; +use tower_http::trace::TraceLayer; +use tracing_subscriber::filter::LevelFilter; +use yank::{unyank, yank}; + +#[derive(Serialize)] +pub struct Errors { + errors: Vec, +} + +impl Errors { + fn new(v: impl ToString) -> Self { + Self { + errors: vec![SingleError { + detail: v.to_string(), + }], + } + } + + fn new_many(v: impl IntoIterator) -> Errors { + Self { + errors: v + .into_iter() + .map(|v| SingleError { + detail: v.to_string(), + }) + .collect(), + } + } +} + +impl From for Errors { + fn from(v: T) -> Self { + Self::new(v) + } +} + +impl IntoResponse for Errors { + fn into_response(self) -> Response { + Json(self).into_response() + } +} + +impl From for Response { + fn from(v: Errors) -> Self { + v.into_response() + } +} + +pub type RespResult = std::result::Result; + +#[derive(Serialize)] +struct SingleError { + detail: String, +} + +fn get_crate_prefix(s: &str) -> Result { + if s.is_empty() { + return Err("Crate name must be non-empty"); + } else if !s.is_ascii() { + return Err("Crate name must be ASCII"); + } + + let mut buf = PathBuf::new(); + if s.len() == 1 { + buf.push("1"); + } else if s.len() == 2 { + buf.push("2"); + } else if s.len() == 3 { + buf.push("3"); + let c = s.chars().next().unwrap().to_ascii_lowercase(); + let mut b = [0]; + buf.push(c.encode_utf8(&mut b)); + } else { + let mut b: [u8; 4] = s.as_bytes()[0..4].try_into().unwrap(); + let s = std::str::from_utf8_mut(&mut b).unwrap(); + s.make_ascii_lowercase(); + buf.push(&s[0..2]); + buf.push(&s[2..4]); + } + + Ok(buf) +} + +pub struct State { + index_dir: PathBuf, + crate_dir: PathBuf, + db: Pool, +} + +static INDEX_LOCK: Mutex<()> = Mutex::const_new(()); + +#[derive(Deserialize)] +pub struct Config { + working_dir: PathBuf, + postgres_uri: String, + listen_uri: SocketAddr, +} + +#[tokio::main] +async fn main() -> Result<(), BoxDynError> { + let config = toml::from_str::( + &fs::read_to_string("registry.toml") + .await + .expect("Config file missing"), + ) + .expect("Invalid config file"); + + let mut index_dir = config.working_dir.clone(); + index_dir.push("index"); + let mut crate_dir = config.working_dir; + crate_dir.push("crates"); + let state = Arc::new(State { + index_dir, + crate_dir, + db: PgPoolOptions::new().connect(&config.postgres_uri).await?, + }); + + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .init(); + + db::init(&state.db).await?; + + let app = Router::new() + .route("/api/v1/crates/new", put(new_crate::new_crate)) + .route("/api/v1/crates/:crate_name/:version/yank", delete(yank)) + .route("/api/v1/crates/:crate_name/:version/unyank", put(unyank)) + .route( + "/api/v1/crates/:crate_name/owners", + get(list_owners).put(add_owners).delete(remove_owners), + ) + .route( + "/api/v1/crates/:crate_name/:version/download", + get(download), + ) + .route("/api/v1/crates", get(search)) + .layer( + ServiceBuilder::new() + .layer(Extension(state.clone())) + .layer(TraceLayer::new_for_http()), + ); + + fs::create_dir_all(&state.index_dir).await?; + fs::create_dir_all(&state.crate_dir).await?; + + axum::Server::bind(&config.listen_uri) + .serve(app.into_make_service()) + .await + .unwrap(); + + Ok(()) +} + +pub struct Auth(DbUser); + +#[async_trait] +impl FromRequest for Auth { + type Rejection = Response; + + async fn from_request(req: &mut RequestParts) -> Result { + let Extension(state): Extension> = Extension::from_request(req) + .await + .map_err(IntoResponse::into_response)?; + + if let Some(auth) = req + .headers() + .get(AUTHORIZATION) + .and_then(|v| v.to_str().ok()) + { + if let Some(db_user) = + sqlx::query_as::<_, DbUser>("SELECT * FROM users WHERE credential = $1 LIMIT 1") + .bind(auth) + .fetch_optional(&state.db) + .await + .map_err(db_error) + .map_err(IntoResponse::into_response)? + { + Ok(Self(db_user)) + } else { + Err(StatusCode::FORBIDDEN.into_response()) + } + } else { + Err(StatusCode::FORBIDDEN.into_response()) + } + } +} + +pub async fn auth(crate_name: &str, auth_user: &DbUser, state: &State) -> Result<(), Response> { + let (is_authenticated,): (bool,) = + query_as("SELECT $1 = ANY (crates.owners) FROM crates WHERE name = $2") + .bind(auth_user.id) + .bind(&crate_name) + .fetch_one(&state.db) + .await + .map_err(db_error) + .map_err(IntoResponse::into_response)?; + + if !is_authenticated { + return Err(StatusCode::FORBIDDEN.into_response()); + } + + Ok(()) +} + +pub struct Success; + +impl Serialize for Success { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut serialize_struct = serializer.serialize_struct("Struct", 1)?; + serialize_struct.serialize_field("ok", &true)?; + serialize_struct.end() + } +} + +impl IntoResponse for Success { + fn into_response(self) -> Response { + Json(self).into_response() + } +} + +fn internal_error(e: E) -> Errors { + Errors::new(format_args!("Internal server error: {e}")) +} + +fn db_error(e: E) -> Errors { + Errors::new(format_args!("Database error: {e}")) +} diff --git a/src/new_crate.rs b/src/new_crate.rs new file mode 100644 index 0000000..baf6cf8 --- /dev/null +++ b/src/new_crate.rs @@ -0,0 +1,251 @@ +use crate::{ + auth, + db::{DbVersionFeature, PgU32}, + db_error, + index::{update_crate_from_db, BaseDependency}, + internal_error, Auth, Errors, RespResult, State, +}; + +use async_trait::async_trait; +use axum::{ + body::{Body, Bytes}, + extract::{FromRequest, RequestParts}, + Extension, Json, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use sqlx::{query, query_as, types::Json as SqlxJson, Connection}; +use std::{collections::HashMap, fmt::Write, sync::Arc}; +use tokio::fs; + +pub struct NewCrateRequest { + json_data: NewCrateJsonData, + crate_file: Vec, +} + +#[async_trait] +impl FromRequest for NewCrateRequest { + type Rejection = Errors; + + async fn from_request(req: &mut RequestParts) -> Result { + let body = Bytes::from_request(req).await.unwrap(); + + let mut offset = 0; + + let json_len_bytes = body + .get(offset..offset + 4) + .ok_or_else(|| Errors::new("Not enough bytes when reading length of JSON data"))?; + let json_len = u32::from_le_bytes(json_len_bytes.try_into().unwrap()) as usize; + offset += 4; + + let json_bytes = body + .get(offset..offset + json_len) + .ok_or_else(|| Errors::new("Not enough bytes when reading JSON data"))?; + let json_data = serde_json::from_slice::(json_bytes) + .map_err(|e| Errors::new(format_args!("Error while parsing JSON data: {e}")))?; + offset += json_len; + + let crate_len_bytes = body + .get(offset..offset + 4) + .ok_or_else(|| Errors::new("Not enough bytes when reading length of .crate file"))?; + let crate_len = u32::from_le_bytes(crate_len_bytes.try_into().unwrap()) as usize; + offset += 4; + + let crate_file = body + .get(offset..offset + crate_len) + .ok_or_else(|| Errors::new("Not enough bytes when reading .crate file"))? + .to_vec(); + offset += crate_len; + + if body.len() != offset { + return Err(Errors::new("Too much data provided")); + } + + Ok(Self { + json_data, + crate_file, + }) + } +} + +#[derive(Deserialize)] +struct NewCrateJsonData { + name: String, + vers: Version, + deps: Vec, + features: HashMap>, + authors: Vec, + description: Option, + documentation: Option, + homepage: Option, + readme: Option, + readme_file: Option, + keywords: Vec, + categories: Vec, + license: Option, + license_file: Option, + repository: Option, + badges: HashMap>, + links: Option, +} + +#[derive(Deserialize)] +struct Dependency2 { + #[serde(flatten)] + base: BaseDependency, + explicit_name_in_toml: Option, +} + +#[derive(Serialize)] +pub struct NewCrateResponse { + warnings: Warnings, +} + +#[derive(Serialize)] +struct Warnings { + invalid_categories: Vec, + invalid_badges: Vec, + other: Vec, +} + +pub async fn new_crate( + mut request: NewCrateRequest, + Auth(auth_user): Auth, + Extension(state): Extension>, +) -> RespResult> { + if !request.json_data.name.is_ascii() { + return Err(Errors::new("Crate name must be ASCII").into()); + } + + request.json_data.name.make_ascii_lowercase(); + + let mut db = state.db.acquire().await.map_err(db_error)?; + + let crate_id = { + let mut trans = db.begin().await.map_err(db_error)?; + + let (crate_id,): (PgU32,) = match query_as("SELECT id FROM crates WHERE name = $1 LIMIT 1") + .bind(&request.json_data.name) + .fetch_optional(&mut *trans) + .await + .map_err(db_error)? + { + Some(v) => { + auth(&request.json_data.name, &auth_user, &state).await?; + v + } + None => query_as( + "INSERT INTO crates (name, publisher, owners) VALUES ($1, $2, $3) RETURNING id", + ) + .bind(&request.json_data.name) + .bind(auth_user.id) + .bind(&[auth_user.id][..]) + .fetch_one(&mut *trans) + .await + .map_err(db_error)?, + }; + + let hash = hmac_sha256::Hash::hash(&request.crate_file); + let mut cksum = String::new(); + for byte in hash { + write!(cksum, "{byte:02x}").expect("Formatting to a String failed"); + } + + let (version_id,): (PgU32,) = query_as( + r"INSERT INTO versions ( + vers, cksum, yanked, links, + crate_id, features, + authors, description, documentation, + homepage, readme, readme_file, + keywords, categories, + license, license_file, + repository, badges + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) + RETURNING id", + ) + .bind(&request.json_data.vers.to_string()) + .bind(&cksum) + .bind(false) + .bind(&request.json_data.links) + .bind(crate_id) + .bind( + &request + .json_data + .features + .iter() + .map(|v| DbVersionFeature { + feature: v.0.clone(), + enables: v.1.clone(), + }) + .collect::>(), + ) + .bind(&request.json_data.authors) + .bind(&request.json_data.description) + .bind(&request.json_data.documentation) + .bind(&request.json_data.homepage) + .bind(&request.json_data.readme) + .bind(&request.json_data.readme_file) + .bind(&request.json_data.keywords) + .bind(&request.json_data.categories) + .bind(&request.json_data.license) + .bind(&request.json_data.license_file) + .bind(&request.json_data.repository) + .bind(SqlxJson(&request.json_data.badges)) + .fetch_one(&mut *trans) + .await + .map_err(db_error)?; + + for dep in &request.json_data.deps { + query( + r"INSERT INTO deps ( + name, version_req, optional, default_features, + target, kind, registry, package, feature, version_id + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + ) + .bind(&dep.base.name) + .bind(&dep.base.version_req.to_string()) + .bind(&dep.base.optional) + .bind(&dep.base.default_features) + .bind(&dep.base.target) + .bind(&dep.base.kind.to_db_repr()) + .bind(&dep.base.registry) + .bind(&dep.explicit_name_in_toml) + .bind(&dep.base.features) + .bind(version_id) + .execute(&mut *trans) + .await + .map_err(db_error)?; + } + + trans.commit().await.map_err(db_error)?; + + crate_id + }; + + update_crate_from_db( + crate_id, + &state, + &format!( + "Add version {} of '{}'", + request.json_data.vers, request.json_data.name + ), + ) + .await?; + + let mut path = state.crate_dir.clone(); + path.push(&request.json_data.name); + path.push(request.json_data.vers.to_string()); + fs::create_dir_all(&path).await.map_err(internal_error)?; + path.push("crate.crate"); + fs::write(path, request.crate_file) + .await + .map_err(internal_error)?; + + Ok(Json(NewCrateResponse { + warnings: Warnings { + invalid_categories: Vec::new(), + invalid_badges: Vec::new(), + other: Vec::new(), + }, + })) +} diff --git a/src/owners.rs b/src/owners.rs new file mode 100644 index 0000000..59272ea --- /dev/null +++ b/src/owners.rs @@ -0,0 +1,135 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use axum::{ + body::HttpBody, + extract::{FromRequest, Path, RequestParts}, + BoxError, Extension, Json as AxumJson, +}; +use futures_util::StreamExt; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use sqlx::{query, query_as}; + +use crate::{auth, db::PgU32, db_error, Auth, Errors, RespResult, State, Success}; + +// TODO: custom `Json` errors + +pub struct Json(T); + +#[async_trait] +impl FromRequest for Json +where + T: DeserializeOwned, + B: HttpBody + Send, + B::Data: Send, + B::Error: Into, +{ + type Rejection = Errors; + + async fn from_request(req: &mut RequestParts) -> Result { + let res = AxumJson::::from_request(req).await; + + match res { + Ok(AxumJson(v)) => Ok(Json(v)), + Err(e) => Err(Errors::new(format_args!("Invalid json data: {e}"))), + } + } +} + +#[derive(Serialize)] +pub struct ListOwnersResponse { + users: Vec, +} + +#[derive(Serialize)] +struct User { + id: u32, + login: String, + name: Option, +} + +pub async fn list_owners( + Path(crate_name): Path, + Auth(_auth_user): Auth, + Extension(state): Extension>, +) -> RespResult> { + let mut users = query_as("SELECT users.id, users.login, users.name FROM crates INNER JOIN users ON users.id = ANY (crates.owners) WHERE crates.name = $1") + .bind(&crate_name) + .fetch(&state.db); + + let mut users2 = Vec::new(); + while let Some(user) = users.next().await.transpose().map_err(db_error)? { + let _: (PgU32, _, _) = user; + users2.push(User { + id: user.0 .0, + login: user.1, + name: user.2, + }); + } + + Ok(AxumJson(ListOwnersResponse { users: users2 })) +} + +#[derive(Deserialize)] +pub struct AddRemoveOwnersRequest { + users: Vec, +} + +#[derive(Serialize)] +pub struct AddRemoveOwnersResponse { + #[serde(flatten)] + success: Success, + msg: String, +} + +pub async fn add_owners( + Json(request): Json, + Path(crate_name): Path, + Auth(auth_user): Auth, + Extension(state): Extension>, +) -> RespResult> { + auth(&crate_name, &auth_user, &state).await?; + + query( + r"UPDATE crates SET owners = owners || ( + SELECT array_agg(users.id) FROM users INNER JOIN unnest($1) ON users.login = unnest + ) WHERE name = $2", + ) + .bind(&request.users) + .bind(&crate_name) + .execute(&state.db) + .await + .map_err(db_error)?; + + Ok(AxumJson(AddRemoveOwnersResponse { + success: Success, + msg: format!("adding {:?} to crate {}", request.users, crate_name), + })) +} + +pub async fn remove_owners( + Json(request): Json, + Path(crate_name): Path, + Auth(auth_user): Auth, + Extension(state): Extension>, +) -> RespResult> { + auth(&crate_name, &auth_user, &state).await?; + + query( + r"UPDATE crates SET owners = ( + SELECT array_agg(unnest) FROM ( + SELECT unnest(owners) EXCEPT (SELECT users.id FROM users INNER JOIN unnest($1) ON users.login = unnest) + ) t (unnest) + ) WHERE name = $2", + ) + .bind(&request.users) + .bind(&crate_name) + .execute(&state.db) + .await + .map_err(db_error)?; + + Ok(AxumJson(AddRemoveOwnersResponse { + success: Success, + msg: format!("removed {:?} from crate {}", request.users, crate_name), + })) +} diff --git a/src/search.rs b/src/search.rs new file mode 100644 index 0000000..ca3d769 --- /dev/null +++ b/src/search.rs @@ -0,0 +1,95 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use axum::{ + extract::{FromRequest, Query as AxumQuery, RequestParts}, + Extension, Json, +}; +use semver::Version; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use sqlx::query_as; + +use crate::{db::PgU32, db_error, Errors, RespResult, State}; + +pub struct Query(T); + +#[async_trait] +impl FromRequest for Query +where + T: DeserializeOwned, + B: Send, +{ + type Rejection = Errors; + + async fn from_request(req: &mut RequestParts) -> Result { + let res = AxumQuery::::from_request(req).await; + + match res { + Ok(AxumQuery(v)) => Ok(Query(v)), + Err(e) => Err(Errors::new(format_args!("Invalid query options: {e}"))), + } + } +} + +#[derive(Deserialize)] +pub struct SearchQueryParams { + q: String, + per_page: Option, +} + +#[derive(Serialize)] +pub struct SearchResponse { + crates: Vec, + meta: Meta, +} + +#[derive(Serialize)] +pub struct Crate { + name: String, + max_version: Version, + description: String, +} + +#[derive(Serialize)] +pub struct Meta { + total: u32, +} + +pub async fn search( + Query(params): Query, + Extension(state): Extension>, +) -> RespResult> { + let crates = query_as::<_, (PgU32, String)>( + "SELECT id, name FROM crates ORDER BY SIMILARITY(name, $1) DESC LIMIT $2;", + ) + .bind(¶ms.q) + .bind(i16::from(params.per_page.unwrap_or(10))) + .fetch_all(&state.db) + .await + .map_err(db_error)?; + + let mut crates2 = Vec::new(); + for craet in crates { + let v: Option<(String, Option)> = query_as( + "SELECT vers, description FROM versions WHERE crate_id = $1 ORDER BY vers DESC LIMIT 1", + ) + .bind(craet.0) + .fetch_optional(&state.db) + .await + .map_err(db_error)?; + + if let Some((vers, desc)) = v { + crates2.push(Crate { + name: craet.1, + max_version: vers.parse().unwrap(), + description: desc.unwrap_or_else(|| "".to_owned()), + }) + } + } + + Ok(Json(SearchResponse { + crates: crates2, + // TODO: total + meta: Meta { total: 0 }, + })) +} diff --git a/src/yank.rs b/src/yank.rs new file mode 100644 index 0000000..04104d1 --- /dev/null +++ b/src/yank.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use axum::{ + extract::{FromRequest, Path as AxumPath, RequestParts}, + Extension, +}; +use semver::Version; +use serde::de::DeserializeOwned; +use sqlx::query_as; + +use crate::{ + auth, db::PgU32, db_error, index::update_crate_from_db, Auth, Errors, RespResult, State, + Success, +}; + +pub struct Path(T); + +#[async_trait] +impl FromRequest for Path +where + T: DeserializeOwned + Send, + B: Send, +{ + type Rejection = Errors; + + async fn from_request(req: &mut RequestParts) -> Result { + let res = AxumPath::::from_request(req).await; + + match res { + Ok(AxumPath(v)) => Ok(Path(v)), + Err(e) => Err(Errors::new(format_args!("Invalid path fragments: {e}"))), + } + } +} + +pub async fn yank( + Path((crate_name, version)): Path<(String, Version)>, + Auth(auth_user): Auth, + Extension(state): Extension>, +) -> RespResult { + auth(&crate_name, &auth_user, &state).await?; + + do_yank(&crate_name, &version, &state, true).await +} + +pub async fn unyank( + Path((crate_name, version)): Path<(String, Version)>, + Auth(auth_user): Auth, + Extension(state): Extension>, +) -> RespResult { + auth(&crate_name, &auth_user, &state).await?; + + do_yank(&crate_name, &version, &state, false).await +} + +async fn do_yank(crate_name: &str, version: &Version, state: &State, yank: bool) -> RespResult { + let (crate_id,): (PgU32,) = query_as( + r"UPDATE versions SET yanked = $1 + FROM crates + WHERE crates.id = versions.crate_id + and crates.name = $2 + and versions.vers = $3 + RETURNING crates.id", + ) + .bind(yank) + .bind(crate_name) + .bind(&version.to_string()) + .fetch_one(&state.db) + .await + .map_err(db_error)?; + + update_crate_from_db( + crate_id, + state, + &format!( + "{} version {} of `{}`", + if yank { "Yank" } else { "Unyank" }, + version, + crate_name + ), + ) + .await?; + + Ok(Success) +}