diff --git a/.dir-locals.el b/.dir-locals.el index 0181ae0..bb7a4a8 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,15 +1,14 @@ ;;; Directory Local Variables -*- no-byte-compile: t -*- ;;; For more information see (info "(emacs) Directory Variables") -((sql-mode - . - ((eval . (progn - (setq-local lsp-sqls-connections - `(((driver . "postgresql") - (dataSourceName . - ,(format "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable" - (getenv "DB_HOST") - (getenv "DB_PORT") - (getenv "DB_USER") - (getenv "DB_PASSWORD") - (getenv "DB_NAME"))))))))))) +((rustic-mode . ((fill-column . 80))) + (sql-mode . ((eval . (progn + (setq-local lsp-sqls-connections + `(((driver . "postgresql") + (dataSourceName \, + (format "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable" + (getenv "DB_HOST") + (getenv "DB_PORT") + (getenv "DB_USER") + (getenv "DB_PASSWORD") + (getenv "DB_NAME"))))))))))) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 8ceb586..a84ad20 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -45,8 +45,6 @@ jobs: run: nix develop --command -- just lint - name: Audit run: nix develop --command -- just audit - - name: Minimum supported Rust version check - run: nix develop --command -- just msrv - name: Tests run: nix develop --command -- just test - name: Coverage diff --git a/.gitea/workflows/publish.yaml b/.gitea/workflows/publish.yaml.bak similarity index 51% rename from .gitea/workflows/publish.yaml rename to .gitea/workflows/publish.yaml.bak index 79b1afa..8f2e1e4 100644 --- a/.gitea/workflows/publish.yaml +++ b/.gitea/workflows/publish.yaml.bak @@ -9,6 +9,7 @@ on: pull_request: branches: - 'main' + - 'develop' jobs: publish: @@ -20,14 +21,26 @@ jobs: with: username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + registry: ${{ var.REGISTRY }} - uses: cachix/install-nix-action@v27 with: nix_path: nixpkgs=channel:nixos-unstable - name: Build Docker image - run: nix develop --command -- just docker-build + run: nix develop --command -- just backend build-docker - name: Load Docker image run: docker load < result - name: Docker Metadata action - uses: docker/metadata-action@v5.5.1 + uses: docker/metadata-action@v5.6.1 with: - image: tal-backend:latest + image: gejdr-backend:latest + tags: + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + labels: | + org.opencontainers.image.title=Backend GéJDR + org.opencontainers.image.description=Backend for GéJDR + org.opencontainers.image.vendor=Lucien Cartier-Tilet diff --git a/.gitignore b/.gitignore index cd67dd7..d5816dc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .env /result /coverage/ +/gejdr-backend/result +/gejdr-bot/result diff --git a/.sqlx/query-24bbd63a324e36f4a0c559c44909621cc21b493b4c9ae4c14e30c99a6d8072bd.json b/.sqlx/query-24bbd63a324e36f4a0c559c44909621cc21b493b4c9ae4c14e30c99a6d8072bd.json new file mode 100644 index 0000000..429dc6b --- /dev/null +++ b/.sqlx/query-24bbd63a324e36f4a0c559c44909621cc21b493b4c9ae4c14e30c99a6d8072bd.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "\nINSERT INTO users (id, username, email, avatar, name, created_at, last_updated)\nVALUES ($1, $2, $3, $4, $5, $6, $7)\nRETURNING *\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "last_updated", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Timestamptz" + ] + }, + "nullable": [ + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "24bbd63a324e36f4a0c559c44909621cc21b493b4c9ae4c14e30c99a6d8072bd" +} diff --git a/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json b/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json new file mode 100644 index 0000000..412749b --- /dev/null +++ b/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM users WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7" +} diff --git a/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json b/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json new file mode 100644 index 0000000..e611514 --- /dev/null +++ b/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM users WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "last_updated", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3" +} diff --git a/.sqlx/query-c36467a81bad236a0c1a8d3fc1b5f8efda9b9cf9eaab140c10246218f0240600.json b/.sqlx/query-c36467a81bad236a0c1a8d3fc1b5f8efda9b9cf9eaab140c10246218f0240600.json new file mode 100644 index 0000000..694fea8 --- /dev/null +++ b/.sqlx/query-c36467a81bad236a0c1a8d3fc1b5f8efda9b9cf9eaab140c10246218f0240600.json @@ -0,0 +1,63 @@ +{ + "db_name": "PostgreSQL", + "query": "\nUPDATE users\nSET username = $1, email = $2, avatar = $3, name = $4, last_updated = $5\nWHERE id = $6\nRETURNING *\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "last_updated", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Text" + ] + }, + "nullable": [ + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "c36467a81bad236a0c1a8d3fc1b5f8efda9b9cf9eaab140c10246218f0240600" +} diff --git a/.tarpaulin.ci.toml b/.tarpaulin.ci.toml index 0ebe9ee..7f44b5d 100644 --- a/.tarpaulin.ci.toml +++ b/.tarpaulin.ci.toml @@ -3,3 +3,5 @@ out = ["Xml"] target-dir = "coverage" output-dir = "coverage" fail-under = 60 +exclude-files = ["target/*"] +run-types = ["AllTargets"] diff --git a/.tarpaulin.local.toml b/.tarpaulin.local.toml index 2bf0d3c..43cd834 100644 --- a/.tarpaulin.local.toml +++ b/.tarpaulin.local.toml @@ -4,3 +4,5 @@ skip-clean = true target-dir = "coverage" output-dir = "coverage" fail-under = 60 +exclude-files = ["target/*"] +run-types = ["AllTargets"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2d159b8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4125 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.2.16", + "opaque-debug", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.16", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.7.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead 0.4.3", + "chacha20", + "cipher 0.3.0", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust2", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "aes-gcm 0.10.3", + "base64 0.22.1", + "hkdf", + "hmac 0.12.1", + "percent-encoding", + "rand", + "sha2 0.10.8", + "subtle", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "csrf" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0257f83673d49bb1f149d923cb923f7d5a270248d64fdb6e847190935ecf1e0" +dependencies = [ + "aead 0.4.3", + "aes-gcm 0.9.2", + "byteorder", + "chacha20poly1305", + "chrono", + "data-encoding", + "generic-array", + "hmac 0.11.0", + "log", + "rand", + "sha2 0.9.9", +] + +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.96", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "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 = "deluxe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed332aaf752b459088acf3dd4eca323e3ef4b83c70a84ca48fb0ec5305f1488" +dependencies = [ + "deluxe-core", + "deluxe-macros", + "once_cell", + "proc-macro2", + "syn 2.0.96", +] + +[[package]] +name = "deluxe-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddada51c8576df9d6a8450c351ff63042b092c9458b8ac7d20f89cbd0ffd313" +dependencies = [ + "arrayvec", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.96", +] + +[[package]] +name = "deluxe-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87546d9c837f0b7557e47b8bd6eae52c3c223141b76aa233c345c9ab41d9117" +dependencies = [ + "deluxe-core", + "heck 0.4.1", + "if_chain", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "futures_codec" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce54d63f8b0c75023ed920d46fd71d0cbbb830b0ee012726b5b4f506fb6dea5b" +dependencies = [ + "bytes 0.5.6", + "futures", + "memchr", + "pin-project", +] + +[[package]] +name = "gejdr-backend" +version = "0.1.0" +dependencies = [ + "chrono", + "config", + "dotenvy", + "gejdr-core", + "oauth2", + "poem", + "poem-openapi", + "quote", + "reqwest 0.12.9", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "gejdr-bot" +version = "0.1.0" + +[[package]] +name = "gejdr-core" +version = "0.1.0" +dependencies = [ + "chrono", + "gejdr-macros", + "serde", + "sqlx", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "gejdr-macros" +version = "0.1.0" +dependencies = [ + "deluxe", + "proc-macro2", + "quote", + "sqlx", + "syn 2.0.96", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes 1.8.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes 1.8.0", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.1", +] + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes 1.8.0", + "headers-core", + "http 1.1.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.1.0", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes 1.8.0", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes 1.8.0", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes 1.8.0", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes 1.8.0", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes 1.8.0", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes 1.8.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes 1.8.0", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.31", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.1", + "hyper-util", + "rustls 0.23.18", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.7", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes 1.8.0", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.1", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes 1.8.0", + "encoding_rs", + "futures-util", + "http 1.1.0", + "httparse", + "memchr", + "mime", + "spin", + "tokio", + "version_check", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http 0.2.12", + "rand", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.8", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror 1.0.69", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + +[[package]] +name = "pin-project" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "poem" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d9d595efb128dddda67e32c3d6d26c10a94a81c914d6c5cf0c9f92d19136ae" +dependencies = [ + "base64 0.22.1", + "bytes 1.8.0", + "chrono", + "cookie", + "csrf", + "futures-util", + "headers", + "http 1.1.0", + "http-body-util", + "hyper 1.5.1", + "hyper-util", + "mime", + "multer", + "nix", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "priority-queue", + "quick-xml", + "rand", + "regex", + "rfc7239", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "smallvec", + "sse-codec", + "sync_wrapper 1.0.2", + "tempfile", + "thiserror 1.0.69", + "time", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tokio-util", + "tracing", + "wildmatch", +] + +[[package]] +name = "poem-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2553c04acbd3887e2ad1959ff007fb9ec05d15d67931b6fdd6eb47de138649" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "poem-openapi" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bcce356da677d56723f1f2017276457659d6b93abdcf3ebb218b5050f5bc6" +dependencies = [ + "base64 0.22.1", + "bytes 1.8.0", + "chrono", + "derive_more", + "futures-util", + "indexmap", + "mime", + "num-traits", + "poem", + "poem-openapi-derive", + "quick-xml", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "thiserror 1.0.69", + "tokio", + "uuid", +] + +[[package]] +name = "poem-openapi-derive" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c59f3a8822f162875cc5de29c8cd911e367a79078b06a9fedc081e1f259dd24" +dependencies = [ + "darling", + "http 1.1.0", + "indexmap", + "mime", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", + "thiserror 1.0.69", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.16", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.16", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.16", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "priority-queue" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" +dependencies = [ + "autocfg", + "equivalent", + "indexmap", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.22", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes 1.8.0", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.18", + "socket2", + "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes 1.8.0", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.18", + "rustls-pki-types", + "slab", + "thiserror 2.0.3", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes 1.8.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes 1.8.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-rustls 0.27.3", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.18", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.7", + "windows-registry", +] + +[[package]] +name = "rfc7239" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b106a85eeb5b0336d16d6a20eab857f92861d4fbb1eb9a239866fb98fb6a1063" +dependencies = [ + "uncased", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-ini" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "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 = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.16", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.16", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.16", + "digest 0.10.7", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +dependencies = [ + "bytes 1.8.0", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.1", + "hashlink 0.10.0", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "thiserror 2.0.3", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.96", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.96", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes 1.8.0", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.3", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.3", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sse-codec" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a59f811350c44b4a037aabeb72dc6a9591fc22aa95a036db9a96297c58085a" +dependencies = [ + "bytes 0.5.6", + "futures-io", + "futures_codec", + "memchr", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes 1.8.0", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.18", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes 1.8.0", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.20", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "wildmatch" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" + +[[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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yaml-rust2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink 0.8.4", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/Cargo.toml b/Cargo.toml index 3cae004..1950193 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,61 +1,9 @@ -[package] -name = "gege-jdr-backend" -version = "0.1.0" -edition = "2021" -publish = false -authors = ["phundrak"] -rust-version = "1.78" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -path = "src/lib.rs" - -[[bin]] -path = "src/main.rs" -name = "gege-jdr-backend" - -[dependencies] -chrono = { version = "0.4.38", features = ["serde"] } -config = { version = "0.14.0", features = ["yaml"] } -dotenvy = "0.15.7" -serde = "1.0.204" -serde_json = "1.0.120" -thiserror = "1.0.63" -tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread"] } -tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] } -uuid = { version = "1.10.0", features = ["v4", "serde"] } - -[dependencies.lettre] -version = "0.11.7" -default-features = false -features = [ - "builder", - "hostname", - "pool", - "rustls-tls", - "tokio1", - "tokio1-rustls-tls", - "smtp-transport" +members = [ + "gejdr-core", + "gejdr-bot", + "gejdr-backend", + "gejdr-macros" ] - - -[dependencies.poem] -version = "3.0.4" -default-features = false -features = [ - "csrf", - "rustls", - "cookie", - "test" -] - -[dependencies.poem-openapi] -version = "5.0.3" -features = ["chrono", "swagger-ui", "uuid"] - -[dependencies.sqlx] -version = "0.8.0" -default-features = false -features = ["postgres", "uuid", "chrono", "migrate", "runtime-tokio", "macros"] +resolver = "2" diff --git a/docker/compose.dev.yml b/docker/compose.dev.yml index 7a34f26..263ad9f 100644 --- a/docker/compose.dev.yml +++ b/docker/compose.dev.yml @@ -31,25 +31,6 @@ services: depends_on: - db - # If you run GegeJdrBackend in production, DO NOT use mailpit. - # This tool is for testing only. Instead, you should use a real SMTP - # provider, such as Mailgun, Mailwhale, or Postal. - mailpit: - image: axllent/mailpit:latest - restart: unless-stopped - container_name: gege-jdr-backend-mailpit - ports: - - 127.0.0.1:8025:8025 # WebUI - - 127.0.0.1:1025:1025 # SMTP - volumes: - - gege_jdr_backend_mailpit:/data - environment: - MP_MAX_MESSAGES: 5000 - MP_DATABASE: /data/mailpit.db - MP_SMTP_AUTH_ACCEPT_ANY: 1 - MP_SMTP_AUTH_ALLOW_INSECURE: 1 - volumes: gege_jdr_backend_db_data: gege_jdr_backend_pgadmin_data: - gege_jdr_backend_mailpit: diff --git a/docker/mod.just b/docker/mod.just new file mode 100644 index 0000000..db4d08b --- /dev/null +++ b/docker/mod.just @@ -0,0 +1,14 @@ +default: start + +start: + docker compose -f compose.dev.yml up -d + +stop: + docker compose -f compose.dev.yml down + +logs: + docker compose -f compose.dev.yml logs -f + +## Local Variables: +## mode: makefile +## End: \ No newline at end of file diff --git a/flake.lock b/flake.lock index cab089b..c96b1af 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1723175592, - "narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=", + "lastModified": 1736344531, + "narHash": "sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc+c2c=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5e0ca22929f3342b19569b21b2f3462f053e497b", + "rev": "bffc22eb12172e6db3c5dde9e3e5628f8e3e7912", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1718428119, - "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "lastModified": 1728538411, + "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1723256423, - "narHash": "sha256-9iDTrfVM+mbcad31a47oqW8t8tfSA4C/si6F8F2DO/w=", + "lastModified": 1736476219, + "narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "615cfd85b4d9c51811a8d875374268fab5bd4089", + "rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 912b28b..a59c7eb 100644 --- a/flake.nix +++ b/flake.nix @@ -18,30 +18,61 @@ rustc = rustVersion; }; - appName = "gege-jdr-backend"; + appNameBackend = "gejdr-backend"; + appNameBot = "gejdr-bot"; - appRustBuild = rustPlatform.buildRustPackage { - pname = appName; + appRustBuildBackend = rustPlatform.buildRustPackage { + pname = appNameBackend; version = "0.1.0"; src = ./.; cargoLock.lockFile = ./Cargo.lock; + buildPhase = '' +cd gejdr-backend +SQLX_OFFLINE="1" cargo build --release --bin gejdr-backend +''; + installPhase = '' +cargo install --path . --root "$out/bin/" +''; + }; + appRustBuildBot = rustPlatform.buildRustPackage { + pname = appNameBot; + version = "0.1.0"; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + buildPhase = '' +SQLX_OFFLINE="1" cargo build --release --bin gejdr-bot +''; }; - dockerImage = pkgs.dockerTools.buildLayeredImage { - name = appName; + dockerImageBackend = pkgs.dockerTools.buildLayeredImage { + name = appNameBackend; + tag = "latest"; config = { - Entrypoint = [ "${appRustBuild}/bin/${appName}" ]; + Entrypoint = [ "${appRustBuildBackend}/bin/${appNameBackend}" ]; Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]; Tag = "latest"; }; - contents = [ appRustBuild pkgs.cacert ]; + contents = [ appRustBuildBackend pkgs.cacert ]; + }; + dockerImageBot = pkgs.dockerTools.buildLayeredImage { + name = appNameBot; + tag = "latest"; + fromImageTag = "latest"; + config = { + Entrypoint = [ "${appRustBuildBot}/bin/${appNameBot}" ]; + Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]; + Tag = "latest"; + }; + contents = [ appRustBuildBot pkgs.cacert ]; }; in { packages = { - rustPackage = appRustBuild; - docker = dockerImage; + backend = appRustBuildBot; + bot = appRustBuildBot; + dockerBackend = dockerImageBackend; + dockerBot = dockerImageBot; }; - defaultPackage = dockerImage; + defaultPackage = dockerImageBackend; devShell = with pkgs; mkShell { buildInputs = [ bacon @@ -49,7 +80,6 @@ cargo-audit cargo-auditable cargo-tarpaulin - cargo-msrv just rust-analyzer (rustVersion.override { extensions = [ "rust-src" ]; }) diff --git a/gejdr-backend/.tarpaulin.ci.toml b/gejdr-backend/.tarpaulin.ci.toml new file mode 100644 index 0000000..610e47e --- /dev/null +++ b/gejdr-backend/.tarpaulin.ci.toml @@ -0,0 +1,6 @@ +[all] +out = ["Xml"] +target-dir = "coverage" +output-dir = "coverage" +fail-under = 60 +exclude-files = ["target/*"] diff --git a/gejdr-backend/.tarpaulin.local.toml b/gejdr-backend/.tarpaulin.local.toml new file mode 100644 index 0000000..1170b5c --- /dev/null +++ b/gejdr-backend/.tarpaulin.local.toml @@ -0,0 +1,7 @@ +[all] +out = ["Html", "Lcov"] +skip-clean = true +target-dir = "coverage" +output-dir = "coverage" +fail-under = 60 +exclude-files = ["target/*"] diff --git a/gejdr-backend/Cargo.toml b/gejdr-backend/Cargo.toml new file mode 100644 index 0000000..d4f9b23 --- /dev/null +++ b/gejdr-backend/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "gejdr-backend" +version = "0.1.0" +edition = "2021" +publish = false +authors = ["Lucien Cartier-Tilet "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +path = "src/lib.rs" + +[[bin]] +path = "src/main.rs" +name = "gejdr-backend" + +[dependencies] +gejdr-core = { path = "../gejdr-core" } +chrono = { version = "0.4.38", features = ["serde"] } +config = { version = "0.14.1", features = ["yaml"] } +dotenvy = "0.15.7" +oauth2 = "4.4.2" +quote = "1.0.37" +reqwest = { version = "0.12.9", default-features = false, features = ["charset", "h2", "http2", "rustls-tls", "json"] } +serde = "1.0.215" +serde_json = "1.0.133" +thiserror = "1.0.69" +tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] } +uuid = { version = "1.11.0", features = ["v4", "serde"] } + +[dependencies.poem] +version = "3.1.3" +default-features = false +features = ["csrf", "rustls", "cookie", "test", "session"] + +[dependencies.poem-openapi] +version = "5.1.2" +features = ["chrono", "swagger-ui", "redoc", "rapidoc", "uuid"] + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/gejdr-backend/backend.just b/gejdr-backend/backend.just new file mode 100644 index 0000000..175e6d9 --- /dev/null +++ b/gejdr-backend/backend.just @@ -0,0 +1,18 @@ +default: run + +build $SQLX_OFFLINE="1": + pwd + cargo auditable build --bin gejdr-backend + +build-release $SQLX_OFFLINE="1": + cargo auditable build --release --bin gejdr-backend + +build-docker: + nix build .#dockerBackend + +run: + cargo auditable run --bin gejdr-backend + +## Local Variables: +## mode: makefile +## End: diff --git a/settings/base.yaml b/gejdr-backend/settings/base.yaml similarity index 85% rename from settings/base.yaml rename to gejdr-backend/settings/base.yaml index c649ef1..851147c 100644 --- a/settings/base.yaml +++ b/gejdr-backend/settings/base.yaml @@ -16,3 +16,7 @@ email: user: user@gege-jdr-backend.example from: GegeJdrBackend password: hunter2 + +discord: + client_id: changeme + client_secret: changeme diff --git a/settings/development.yaml b/gejdr-backend/settings/development.yaml similarity index 60% rename from settings/development.yaml rename to gejdr-backend/settings/development.yaml index bf09150..a87d94f 100644 --- a/settings/development.yaml +++ b/gejdr-backend/settings/development.yaml @@ -3,5 +3,5 @@ debug: true application: protocol: http - host: 127.0.0.1 - base_url: http://127.0.0.1:3000 + host: localhost + base_url: http://localhost:3000 diff --git a/settings/production.yaml b/gejdr-backend/settings/production.yaml similarity index 100% rename from settings/production.yaml rename to gejdr-backend/settings/production.yaml diff --git a/gejdr-backend/src/api_wrapper/discord.rs b/gejdr-backend/src/api_wrapper/discord.rs new file mode 100644 index 0000000..2c7b46d --- /dev/null +++ b/gejdr-backend/src/api_wrapper/discord.rs @@ -0,0 +1,55 @@ +use super::{ApiError, DiscordErrorResponse}; +use gejdr_core::models::accounts::RemoteUser; + +static DISCORD_URL: &str = "https://discord.com/api/v10/"; + +pub async fn get_user_profile(token: &str) -> Result { + let client = reqwest::Client::new(); + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::AUTHORIZATION, + format!("Bearer {token}").parse().unwrap(), + ); + let response = client + .get(format!("{DISCORD_URL}/users/@me")) + .headers(headers) + .send() + .await; + match response { + Ok(resp) => { + if resp.status().is_success() { + resp.json::() + .await + .map_err(std::convert::Into::into) + } else { + let error_response = resp.json::().await; + match error_response { + Ok(val) => Err(ApiError::Api(val)), + Err(e) => Err(ApiError::Reqwest(e)), + } + } + } + Err(e) => Err(ApiError::Reqwest(e)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn user_profile_invalid_token_results_in_401() { + let res = get_user_profile("invalid").await; + assert!(res.is_err()); + let err = res.err().unwrap(); + println!("Error: {err:?}"); + let expected = DiscordErrorResponse { + code: 0, + message: "401: Unauthorized".into(), + }; + assert!(matches!(ApiError::Api(expected), _err)); + } + + // TODO: Find a way to mock calls to discord.com API with a + // successful reply +} diff --git a/gejdr-backend/src/api_wrapper/mod.rs b/gejdr-backend/src/api_wrapper/mod.rs new file mode 100644 index 0000000..4c04bcb --- /dev/null +++ b/gejdr-backend/src/api_wrapper/mod.rs @@ -0,0 +1,41 @@ +use reqwest::Error as ReqwestError; +use std::fmt::{self, Display}; +use thiserror::Error; + +pub mod discord; + +#[derive(Debug, serde::Deserialize, PartialEq, Eq)] +pub struct DiscordErrorResponse { + pub message: String, + pub code: u16, +} + +impl Display for DiscordErrorResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DiscordErrorResponse: {} ({})", self.message, self.code) + } +} + +#[derive(Debug, Error)] +pub enum ApiError { + #[error("Reqwest error: {0}")] + Reqwest(#[from] ReqwestError), + #[error("API Error: {0}")] + Api(DiscordErrorResponse), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn display_discord_error_response() { + let error = DiscordErrorResponse { + message: "Message".into(), + code: 42, + }; + let error_str = error.to_string(); + let expected = "DiscordErrorResponse: Message (42)".to_string(); + assert_eq!(expected, error_str); + } +} diff --git a/gejdr-backend/src/errors.rs b/gejdr-backend/src/errors.rs new file mode 100644 index 0000000..31aa74e --- /dev/null +++ b/gejdr-backend/src/errors.rs @@ -0,0 +1,14 @@ +use thiserror::Error; + +#[allow(dead_code)] +#[derive(Debug, Error)] +pub enum ApiError { + #[error("SQL error: {0}")] + Sql(#[from] gejdr_core::sqlx::Error), + #[error("OAuth token error: {0}")] + TokenError(String), + #[error("Unauthorized")] + Unauthorized, + #[error("Attempted to get a value, none found")] + OptionError, +} diff --git a/src/lib.rs b/gejdr-backend/src/lib.rs similarity index 84% rename from src/lib.rs rename to gejdr-backend/src/lib.rs index e4e2160..99786fb 100644 --- a/src/lib.rs +++ b/gejdr-backend/src/lib.rs @@ -5,10 +5,14 @@ #![allow(clippy::unused_async)] #![allow(clippy::useless_let_if_seq)] // Reason: prevents some OpenApi structs from compiling -pub mod route; -pub mod settings; -pub mod startup; -pub mod telemetry; +use gejdr_core::sqlx; + +mod api_wrapper; +mod errors; +mod oauth; +mod route; +mod settings; +mod startup; type MaybeListener = Option>; @@ -16,8 +20,8 @@ async fn prepare(listener: MaybeListener, test_db: Option) -> star dotenvy::dotenv().ok(); let settings = settings::Settings::new().expect("Failed to read settings"); if !cfg!(test) { - let subscriber = telemetry::get_subscriber(settings.clone().debug); - telemetry::init_subscriber(subscriber); + let subscriber = gejdr_core::telemetry::get_subscriber(settings.clone().debug); + gejdr_core::telemetry::init_subscriber(subscriber); } tracing::event!( target: "gege-jdr-backend", @@ -29,8 +33,8 @@ async fn prepare(listener: MaybeListener, test_db: Option) -> star tracing::event!( target: "gege-jdr-backend", tracing::Level::INFO, - "Listening on http://127.0.0.1:{}/", - application.port() + "Listening on {}", + application.settings.web_address() ); application } diff --git a/src/main.rs b/gejdr-backend/src/main.rs similarity index 71% rename from src/main.rs rename to gejdr-backend/src/main.rs index d0d1da6..6e3f1be 100644 --- a/src/main.rs +++ b/gejdr-backend/src/main.rs @@ -1,5 +1,5 @@ #[cfg(not(tarpaulin_include))] #[tokio::main] async fn main() -> Result<(), std::io::Error> { - gege_jdr_backend::run(None).await + gejdr_backend::run(None).await } diff --git a/gejdr-backend/src/oauth/discord.rs b/gejdr-backend/src/oauth/discord.rs new file mode 100644 index 0000000..681f333 --- /dev/null +++ b/gejdr-backend/src/oauth/discord.rs @@ -0,0 +1,62 @@ +use oauth2::{ + basic::BasicClient, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, + PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RevocationUrl, Scope, TokenUrl, +}; +use reqwest::Url; + +use crate::{errors::ApiError, settings::Settings}; + +use super::OauthProvider; + +#[derive(Debug, Clone)] +pub struct DiscordOauthProvider { + client: BasicClient, +} + +impl DiscordOauthProvider { + pub fn new(settings: &Settings) -> Self { + let redirect_url = format!("{}/v1/api/auth/callback/discord", settings.web_address()); + let auth_url = AuthUrl::new("https://discord.com/oauth2/authorize".to_string()) + .expect("Invalid authorization endpoint URL"); + let token_url = TokenUrl::new("https://discord.com/api/oauth2/token".to_string()) + .expect("Invalid token endpoint URL"); + let revocation_url = + RevocationUrl::new("https://discord.com/api/oauth2/token/revoke".to_string()) + .expect("Invalid revocation URL"); + let client = BasicClient::new( + ClientId::new(settings.discord.client_id.clone()), + Some(ClientSecret::new(settings.discord.client_secret.clone())), + auth_url, + Some(token_url), + ) + .set_redirect_uri(RedirectUrl::new(redirect_url).expect("Invalid redirect URL")) + .set_revocation_uri(revocation_url); + Self { client } + } +} + +impl OauthProvider for DiscordOauthProvider { + fn auth_and_csrf(&self) -> (Url, CsrfToken, PkceCodeVerifier) { + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + let (auth_url, csrf_token) = self + .client + .authorize_url(CsrfToken::new_random) + .add_scopes(["identify", "openid", "email"].map(|v| Scope::new(v.to_string()))) + .set_pkce_challenge(pkce_challenge) + .url(); + (auth_url, csrf_token, pkce_verifier) + } + + async fn token( + &self, + code: String, + verifier: PkceCodeVerifier, + ) -> Result { + self.client + .exchange_code(AuthorizationCode::new(code)) + .set_pkce_verifier(verifier) + .request_async(oauth2::reqwest::async_http_client) + .await + .map_err(|e| ApiError::TokenError(format!("{e:?}"))) + } +} diff --git a/gejdr-backend/src/oauth/mod.rs b/gejdr-backend/src/oauth/mod.rs new file mode 100644 index 0000000..2aca02a --- /dev/null +++ b/gejdr-backend/src/oauth/mod.rs @@ -0,0 +1,17 @@ +mod discord; +pub use discord::DiscordOauthProvider; + +use oauth2::{ + basic::BasicTokenType, CsrfToken, EmptyExtraTokenFields, PkceCodeVerifier, + StandardTokenResponse, +}; +use reqwest::Url; + +use crate::errors::ApiError; + +pub type Token = StandardTokenResponse; + +pub trait OauthProvider { + fn auth_and_csrf(&self) -> (Url, CsrfToken, PkceCodeVerifier); + async fn token(&self, code: String, verifier: PkceCodeVerifier) -> Result; +} diff --git a/gejdr-backend/src/route/auth.rs b/gejdr-backend/src/route/auth.rs new file mode 100644 index 0000000..0713de9 --- /dev/null +++ b/gejdr-backend/src/route/auth.rs @@ -0,0 +1,220 @@ +use gejdr_core::models::accounts::User; +use gejdr_core::sqlx::PgPool; +use oauth2::{CsrfToken, PkceCodeVerifier, TokenResponse}; +use poem::web::Data; +use poem::{session::Session, web::Form}; +use poem_openapi::payload::{Json, PlainText}; +use poem_openapi::{ApiResponse, Object, OpenApi}; + +use crate::oauth::{DiscordOauthProvider, OauthProvider}; + +use super::errors::ErrorResponse; +use super::ApiCategory; + +type Token = + oauth2::StandardTokenResponse; + +pub struct AuthApi; + +#[derive(Debug, Object, Clone, Eq, PartialEq, serde::Deserialize)] +struct DiscordCallbackRequest { + code: String, + state: String, +} + +impl DiscordCallbackRequest { + pub fn check_token(&self, token: &CsrfToken) -> Result<(), LoginStatusResponse> { + if *token.secret().to_string() == self.state { + Ok(()) + } else { + Err(LoginStatusResponse::TokenError(Json(ErrorResponse { + code: 500, + message: "OAuth token error".into(), + details: Some( + "OAuth provider did not send a message that matches what was expected".into(), + ), + }))) + } + } +} + +#[derive(ApiResponse)] +enum LoginStatusResponse { + #[oai(status = 201)] + LoggedIn(Json), + #[oai(status = 201)] + LoggedOut( + #[oai(header = "Location")] String, + #[oai(header = "Cache-Control")] String, + ), + #[oai(status = 301)] + LoginRedirect( + #[oai(header = "Location")] String, + #[oai(header = "Cache-Control")] String, + ), + #[oai(status = 500)] + TokenError(Json), + #[oai(status = 500)] + DatabaseError(Json), + #[oai(status = 503)] + DiscordError(Json), +} + +#[derive(Debug, Eq, PartialEq, serde::Serialize, Object)] +struct UserInfo { + id: String, + username: String, + display_name: Option, + avatar: Option, +} + +impl From for UserInfo { + fn from(value: User) -> Self { + Self { + id: value.id, + username: value.username, + display_name: value.name, + avatar: value.avatar, + } + } +} + +#[derive(ApiResponse)] +enum UserInfoResponse { + #[oai(status = 201)] + UserInfo(Json), + #[oai(status = 401)] + Unauthorized, + #[oai(status = 500)] + DatabaseError(Json), + #[oai(status = 503)] + DiscordError(Json), +} + +impl From for LoginStatusResponse { + fn from(value: UserInfoResponse) -> Self { + match value { + UserInfoResponse::UserInfo(json) => Self::LoggedIn(json), + UserInfoResponse::Unauthorized => unimplemented!(), + UserInfoResponse::DatabaseError(json) => Self::DatabaseError(json), + UserInfoResponse::DiscordError(json) => Self::DiscordError(json), + } + } +} + +#[derive(ApiResponse)] +enum CsrfResponse { + #[oai(status = 201)] + Token(PlainText), +} + +#[OpenApi(prefix_path = "/v1/api/auth", tag = "ApiCategory::Auth")] +impl AuthApi { + async fn fetch_remote_user( + pool: Data<&PgPool>, + token: Token, + ) -> Result { + crate::api_wrapper::discord::get_user_profile(token.access_token().secret()) + .await + .map_err(|e| { + tracing::event!( + target: "auth-discord", + tracing::Level::ERROR, + "Failed to communicate with Discord: {}", + e + ); + UserInfoResponse::DiscordError(Json(e.into())) + })? + .refresh_in_database(&pool) + .await + .map(|user| UserInfoResponse::UserInfo(Json(user.into()))) + .map_err(|e| { + tracing::event!( + target: "auth-discord", + tracing::Level::ERROR, + "Database error: {}", + e + ); + UserInfoResponse::DatabaseError(Json(e.into())) + }) + } + + #[oai(path = "/signin/discord", method = "get")] + async fn signin_discord( + &self, + oauth: Data<&DiscordOauthProvider>, + session: &Session, + ) -> LoginStatusResponse { + let (auth_url, csrf_token, pkce_verifier) = oauth.0.auth_and_csrf(); + session.set("csrf", csrf_token); + session.set("pkce", pkce_verifier); + tracing::event!( + target: "auth-discord", + tracing::Level::INFO, + "Signin through Discord", + ); + LoginStatusResponse::LoginRedirect(auth_url.to_string(), "no-cache".to_string()) + } + + #[oai(path = "/callback/discord", method = "get")] + async fn callback_discord( + &self, + Form(auth_request): Form, + oauth: Data<&DiscordOauthProvider>, + pool: Data<&PgPool>, + session: &Session, + ) -> Result { + tracing::event!( + target: "auth-discord", + tracing::Level::INFO, + "Discord callback", + ); + let csrf_token = session.get::("csrf").ok_or_else(|| { + LoginStatusResponse::TokenError(Json(ErrorResponse { + code: 500, + message: "Cannot fetch csrf token from session".to_string(), + ..Default::default() + })) + })?; + auth_request.check_token(&csrf_token)?; + let pkce_verifier = session.get::("pkce").ok_or_else(|| { + LoginStatusResponse::TokenError(Json(ErrorResponse { + code: 500, + message: "Cannot fetch pkce verifier from session".to_string(), + ..Default::default() + })) + })?; + let token = oauth + .token(auth_request.code, pkce_verifier) + .await + .map_err(|e| LoginStatusResponse::TokenError(Json(e.into())))?; + session.set("token", token.clone()); + Self::fetch_remote_user(pool, token) + .await + .map(std::convert::Into::into) + .map_err(std::convert::Into::into) + } + + #[oai(path = "/csrf", method = "get")] + async fn csrf(&self, token: &poem::web::CsrfToken) -> CsrfResponse { + CsrfResponse::Token(PlainText(token.0.clone())) + } + + #[oai(path = "/signout", method = "post")] + async fn signout(&self, session: &Session) -> LoginStatusResponse { + session.purge(); + LoginStatusResponse::LoggedOut("/".to_string(), "no-cache".to_string()) + } + + #[oai(path = "/me", method = "get")] + async fn user_info( + &self, + session: &Session, + pool: Data<&PgPool>, + ) -> Result { + let token = session + .get::("token") + .ok_or(UserInfoResponse::Unauthorized)?; + Self::fetch_remote_user(pool, token).await + } +} diff --git a/gejdr-backend/src/route/errors.rs b/gejdr-backend/src/route/errors.rs new file mode 100644 index 0000000..c02571e --- /dev/null +++ b/gejdr-backend/src/route/errors.rs @@ -0,0 +1,204 @@ +use poem_openapi::Object; +use reqwest::Error as ReqwestError; + +use crate::api_wrapper::ApiError as ApiWrapperError; +use crate::errors::ApiError; + +#[derive(Debug, serde::Serialize, Default, Object, PartialEq, Eq)] +pub struct ErrorResponse { + pub code: u16, + pub message: String, + pub details: Option, +} + +impl From for ErrorResponse { + fn from(value: ApiError) -> Self { + match value { + ApiError::Sql(e) => Self { + code: 500, + message: "SQL error".into(), + details: Some(e.to_string()), + }, + ApiError::TokenError(e) => Self { + code: 500, + message: "OAuth token error".into(), + details: Some(e), + }, + ApiError::Unauthorized => Self { + code: 401, + message: "Unauthorized!".into(), + ..Default::default() + }, + ApiError::OptionError => Self { + code: 500, + message: "Attempted to get a value, but none found".into(), + ..Default::default() + }, + } + } +} + +impl From for ErrorResponse { + fn from(value: ReqwestError) -> Self { + Self { + code: 503, + message: "Failed to communicate with Discord".into(), + details: Some(value.status().map_or_else( + || "Communication failed before we could hear back from Discord".into(), + |status| format!("Discord sent back the error code {status}"), + )), + } + } +} + +impl From for ErrorResponse { + fn from(source: ApiWrapperError) -> Self { + match source { + ApiWrapperError::Reqwest(e) => e.into(), + ApiWrapperError::Api(e) => Self { + code: if e.message.as_str().starts_with("401") { + 401 + } else { + e.code + }, + message: e.message, + details: None, + }, + } + } +} + +impl From for ErrorResponse { + fn from(_value: gejdr_core::sqlx::Error) -> Self { + Self { + code: 500, + message: "Internal database error".into(), + ..Default::default() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api_wrapper::{ApiError as ApiWrapperError, DiscordErrorResponse}; + + #[test] + fn conversion_from_sql_api_error_works() { + let sql_error = ApiError::Sql(gejdr_core::sqlx::Error::ColumnNotFound( + "COLUMN_NAME".to_string(), + )); + let final_error = ErrorResponse::from(sql_error); + let expected_error = ErrorResponse { + code: 500, + message: "SQL error".into(), + details: Some("no column found for name: COLUMN_NAME".into()), + }; + assert_eq!(expected_error, final_error); + } + + #[test] + fn conversion_from_token_error_works() { + let initial_error = ApiError::TokenError("TOKEN ERROR".into()); + let final_error: ErrorResponse = initial_error.into(); + let expected_error = ErrorResponse { + code: 500, + message: "OAuth token error".into(), + details: Some("TOKEN ERROR".into()), + }; + assert_eq!(expected_error, final_error); + } + + #[test] + fn conversion_from_unauthorized_works() { + let initial_error = ApiError::Unauthorized; + let final_error: ErrorResponse = initial_error.into(); + let expected_error = ErrorResponse { + code: 401, + message: "Unauthorized!".into(), + ..Default::default() + }; + assert_eq!(expected_error, final_error); + } + + #[test] + fn conversion_from_option_error_works() { + let initial_error = ApiError::OptionError; + let final_error: ErrorResponse = initial_error.into(); + let expected_error = ErrorResponse { + code: 500, + message: "Attempted to get a value, but none found".into(), + ..Default::default() + }; + assert_eq!(expected_error, final_error); + } + + #[tokio::test] + async fn conversion_from_reqwest_error() { + let err = reqwest::get("https://example.example/401").await; + assert!(err.is_err()); + let expected = ErrorResponse { + code: 503, + message: "Failed to communicate with Discord".into(), + details: Some("Communication failed before we could hear back from Discord".into()), + }; + let actual: ErrorResponse = err.err().unwrap().into(); + assert_eq!(expected, actual); + } + + #[tokio::test] + async fn conversion_from_apiwrappererror_with_reqwest_error() { + let err = reqwest::get("https://example.example/401").await; + assert!(err.is_err()); + let err = ApiWrapperError::Reqwest(err.err().unwrap()); + let expected = ErrorResponse { + code: 503, + message: "Failed to communicate with Discord".into(), + details: Some("Communication failed before we could hear back from Discord".into()), + }; + let actual: ErrorResponse = err.into(); + assert_eq!(expected, actual); + } + + #[test] + fn conversion_from_apiwrappererror_with_401_discord_error() { + let err = ApiWrapperError::Api(DiscordErrorResponse { + code: 0, + message: "401: Unauthorized".into(), + }); + let expected = ErrorResponse { + code: 401, + message: "401: Unauthorized".into(), + ..Default::default() + }; + let actual: ErrorResponse = err.into(); + assert_eq!(expected, actual); + } + + #[test] + fn conversion_from_apiwrappererror_with_generic_discord_error() { + let err = ApiWrapperError::Api(DiscordErrorResponse { + code: 0, + message: "Something else".into(), + }); + let expected = ErrorResponse { + code: 0, + message: "Something else".into(), + ..Default::default() + }; + let actual: ErrorResponse = err.into(); + assert_eq!(expected, actual); + } + + #[test] + fn conversion_from_database_error() { + let err = gejdr_core::sqlx::Error::PoolClosed; + let expected = ErrorResponse { + code: 500, + message: "Internal database error".into(), + ..Default::default() + }; + let actual: ErrorResponse = err.into(); + assert_eq!(expected, actual); + } +} diff --git a/src/route/health.rs b/gejdr-backend/src/route/health.rs similarity index 82% rename from src/route/health.rs rename to gejdr-backend/src/route/health.rs index 4c96079..47a8896 100644 --- a/src/route/health.rs +++ b/gejdr-backend/src/route/health.rs @@ -10,7 +10,7 @@ enum HealthResponse { pub struct HealthApi; -#[OpenApi(prefix_path = "/v1/health-check", tag = "ApiCategory::Health")] +#[OpenApi(prefix_path = "/v1/api/health-check", tag = "ApiCategory::Health")] impl HealthApi { #[oai(path = "/", method = "get")] async fn health_check(&self) -> HealthResponse { @@ -23,7 +23,7 @@ impl HealthApi { async fn health_check_works() { let app = crate::get_test_app(None).await; let cli = poem::test::TestClient::new(app); - let resp = cli.get("/v1/health-check").send().await; + let resp = cli.get("/v1/api/health-check").send().await; resp.assert_status_is_ok(); resp.assert_text("").await; } diff --git a/src/route/mod.rs b/gejdr-backend/src/route/mod.rs similarity index 73% rename from src/route/mod.rs rename to gejdr-backend/src/route/mod.rs index f2c3ca4..8a08305 100644 --- a/src/route/mod.rs +++ b/gejdr-backend/src/route/mod.rs @@ -6,13 +6,19 @@ pub use health::HealthApi; mod version; pub use version::VersionApi; +mod errors; + +mod auth; +pub use auth::AuthApi; + #[derive(Tags)] enum ApiCategory { + Auth, Health, Version, } -pub(crate) struct Api; +pub struct Api; #[OpenApi] impl Api {} diff --git a/src/route/version.rs b/gejdr-backend/src/route/version.rs similarity index 90% rename from src/route/version.rs rename to gejdr-backend/src/route/version.rs index abdacac..4450b5d 100644 --- a/src/route/version.rs +++ b/gejdr-backend/src/route/version.rs @@ -25,7 +25,7 @@ enum VersionResponse { pub struct VersionApi; -#[OpenApi(prefix_path = "/v1/version", tag = "ApiCategory::Version")] +#[OpenApi(prefix_path = "/v1/api/version", tag = "ApiCategory::Version")] impl VersionApi { #[oai(path = "/", method = "get")] async fn version(&self, settings: poem::web::Data<&Settings>) -> Result { @@ -38,7 +38,7 @@ impl VersionApi { async fn version_works() { let app = crate::get_test_app(None).await; let cli = poem::test::TestClient::new(app); - let resp = cli.get("/v1/version").send().await; + let resp = cli.get("/v1/api/version").send().await; resp.assert_status_is_ok(); let json = resp.json().await; let json_value = json.value(); diff --git a/src/settings.rs b/gejdr-backend/src/settings.rs similarity index 81% rename from src/settings.rs rename to gejdr-backend/src/settings.rs index 97e9d52..3c4d1db 100644 --- a/src/settings.rs +++ b/gejdr-backend/src/settings.rs @@ -1,9 +1,10 @@ -use sqlx::ConnectOptions; +use gejdr_core::database::Database; #[derive(Debug, serde::Deserialize, Clone, Default)] pub struct Settings { pub application: ApplicationSettings, pub database: Database, + pub discord: Discord, pub debug: bool, pub email: EmailSettings, pub frontend_url: String, @@ -12,15 +13,7 @@ pub struct Settings { impl Settings { #[must_use] pub fn web_address(&self) -> String { - if self.debug { - format!( - "{}:{}", - self.application.base_url.clone(), - self.application.port - ) - } else { - self.application.base_url.clone() - } + self.application.base_url.clone() } /// Multipurpose function that helps detect the current @@ -65,7 +58,7 @@ impl Settings { )) .add_source( config::Environment::with_prefix("APP") - .prefix_separator("_") + .prefix_separator("__") .separator("__"), ) .build()?; @@ -83,35 +76,6 @@ pub struct ApplicationSettings { pub protocol: String, } -#[derive(Debug, serde::Deserialize, Clone, Default)] -pub struct Database { - pub host: String, - pub port: u16, - pub name: String, - pub user: String, - pub password: String, - pub require_ssl: bool, -} - -impl Database { - #[must_use] - pub fn get_connect_options(&self) -> sqlx::postgres::PgConnectOptions { - let ssl_mode = if self.require_ssl { - sqlx::postgres::PgSslMode::Require - } else { - sqlx::postgres::PgSslMode::Prefer - }; - sqlx::postgres::PgConnectOptions::new() - .host(&self.host) - .username(&self.user) - .password(&self.password) - .port(self.port) - .ssl_mode(ssl_mode) - .database(&self.name) - .log_statements(tracing::log::LevelFilter::Trace) - } -} - #[derive(Debug, PartialEq, Eq)] pub enum Environment { Development, @@ -164,6 +128,12 @@ pub struct EmailSettings { pub from: String, } +#[derive(Debug, serde::Deserialize, Clone, Default)] +pub struct Discord { + pub client_id: String, + pub client_secret: String, +} + #[cfg(test)] mod tests { use super::*; @@ -228,7 +198,7 @@ mod tests { #[test] fn web_address_works() { - let mut settings = Settings { + let settings = Settings { debug: false, application: ApplicationSettings { base_url: "127.0.0.1".to_string(), @@ -237,10 +207,7 @@ mod tests { }, ..Default::default() }; - let expected_no_debug = "127.0.0.1".to_string(); - let expected_debug = "127.0.0.1:3000".to_string(); - assert_eq!(expected_no_debug, settings.web_address()); - settings.debug = true; - assert_eq!(expected_debug, settings.web_address()); + let expected = "127.0.0.1".to_string(); + assert_eq!(expected, settings.web_address()); } } diff --git a/src/startup.rs b/gejdr-backend/src/startup.rs similarity index 76% rename from src/startup.rs rename to gejdr-backend/src/startup.rs index 211c7b1..ca111c5 100644 --- a/src/startup.rs +++ b/gejdr-backend/src/startup.rs @@ -1,35 +1,36 @@ -use poem::middleware::Cors; +use gejdr_core::sqlx; + use poem::middleware::{AddDataEndpoint, CorsEndpoint}; +use poem::middleware::{CookieJarManagerEndpoint, Cors}; +use poem::session::{CookieConfig, CookieSession, CookieSessionEndpoint}; use poem::{EndpointExt, Route}; use poem_openapi::OpenApiService; +use crate::oauth::DiscordOauthProvider; +use crate::route::AuthApi; use crate::{ route::{Api, HealthApi, VersionApi}, settings::Settings, }; -#[must_use] -pub fn get_connection_pool(settings: &crate::settings::Database) -> sqlx::postgres::PgPool { - tracing::event!( - target: "startup", - tracing::Level::INFO, - "connecting to database with configuration {:?}", - settings.clone() - ); - sqlx::postgres::PgPoolOptions::new() - .acquire_timeout(std::time::Duration::from_secs(2)) - .connect_lazy_with(settings.get_connect_options()) -} - type Server = poem::Server, std::convert::Infallible>; -pub type App = AddDataEndpoint, sqlx::PgPool>, Settings>; +pub type App = AddDataEndpoint< + AddDataEndpoint< + AddDataEndpoint< + CookieJarManagerEndpoint>>, + DiscordOauthProvider, + >, + sqlx::Pool, + >, + Settings, +>; pub struct Application { server: Server, app: poem::Route, port: u16, database: sqlx::postgres::PgPool, - settings: Settings, + pub settings: Settings, } pub struct RunnableApplication { @@ -61,6 +62,8 @@ impl From for RunnableApplication { let app = val .app .with(Cors::new()) + .with(CookieSession::new(CookieConfig::default().secure(true))) + .data(crate::oauth::DiscordOauthProvider::new(&val.settings)) .data(val.database) .data(val.settings); let server = val.server; @@ -74,16 +77,16 @@ impl Application { test_pool: Option, ) -> sqlx::postgres::PgPool { let database_pool = - test_pool.map_or_else(|| get_connection_pool(&settings.database), |pool| pool); + test_pool.map_or_else(|| settings.database.get_connection_pool(), |pool| pool); if !cfg!(test) { - migrate_database(&database_pool).await; + gejdr_core::database::Database::migrate(&database_pool).await; } database_pool } fn setup_app(settings: &Settings) -> poem::Route { let api_service = OpenApiService::new( - (Api, HealthApi, VersionApi), + (Api, AuthApi, HealthApi, VersionApi), settings.application.clone().name, settings.application.clone().version, ); @@ -104,6 +107,7 @@ impl Application { }); poem::Server::new(tcp_listener) } + pub async fn build( settings: Settings, test_pool: Option, @@ -133,10 +137,3 @@ impl Application { self.port } } - -async fn migrate_database(pool: &sqlx::postgres::PgPool) { - sqlx::migrate!() - .run(pool) - .await - .expect("Failed to migrate the database"); -} diff --git a/gejdr-bot/.gitignore b/gejdr-bot/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/gejdr-bot/.gitignore @@ -0,0 +1 @@ +/target diff --git a/gejdr-bot/Cargo.toml b/gejdr-bot/Cargo.toml new file mode 100644 index 0000000..c69aabf --- /dev/null +++ b/gejdr-bot/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gejdr-bot" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/gejdr-bot/src/main.rs b/gejdr-bot/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/gejdr-bot/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/gejdr-core/.gitignore b/gejdr-core/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/gejdr-core/.gitignore @@ -0,0 +1 @@ +/target diff --git a/gejdr-core/Cargo.toml b/gejdr-core/Cargo.toml new file mode 100644 index 0000000..60756f7 --- /dev/null +++ b/gejdr-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "gejdr-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +serde = "1.0.215" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] } +uuid = { version = "1.11.0", features = ["v4", "serde"] } +gejdr-macros = { path = "../gejdr-macros" } + +[dependencies.sqlx] +version = "0.8.3" +default-features = false +features = ["postgres", "uuid", "chrono", "migrate", "runtime-tokio", "macros"] diff --git a/gejdr-core/migrations/20240809173617_users.down.sql b/gejdr-core/migrations/20240809173617_users.down.sql new file mode 100644 index 0000000..3967ed0 --- /dev/null +++ b/gejdr-core/migrations/20240809173617_users.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +DROP TABLE IF EXISTS public.users; +DROP EXTENSION IF EXISTS "uuid-ossp"; diff --git a/gejdr-core/migrations/20240809173617_users.up.sql b/gejdr-core/migrations/20240809173617_users.up.sql new file mode 100644 index 0000000..41464d9 --- /dev/null +++ b/gejdr-core/migrations/20240809173617_users.up.sql @@ -0,0 +1,15 @@ +-- Add up migration script here +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE IF NOT EXISTS public.users +( + id character varying(255) NOT NULL, + username character varying(255) NOT NULL, + email character varying(255), + avatar character varying(511), + name character varying(255), + created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_updated timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + CONSTRAINT users_email_unique UNIQUE (email) +); diff --git a/gejdr-core/src/database.rs b/gejdr-core/src/database.rs new file mode 100644 index 0000000..2a34464 --- /dev/null +++ b/gejdr-core/src/database.rs @@ -0,0 +1,50 @@ +use sqlx::ConnectOptions; + +#[derive(Debug, serde::Deserialize, Clone, Default)] +pub struct Database { + pub host: String, + pub port: u16, + pub name: String, + pub user: String, + pub password: String, + pub require_ssl: bool, +} + +impl Database { + #[must_use] + pub fn get_connect_options(&self) -> sqlx::postgres::PgConnectOptions { + let ssl_mode = if self.require_ssl { + sqlx::postgres::PgSslMode::Require + } else { + sqlx::postgres::PgSslMode::Prefer + }; + sqlx::postgres::PgConnectOptions::new() + .host(&self.host) + .username(&self.user) + .password(&self.password) + .port(self.port) + .ssl_mode(ssl_mode) + .database(&self.name) + .log_statements(tracing::log::LevelFilter::Trace) + } + + #[must_use] + pub fn get_connection_pool(&self) -> sqlx::postgres::PgPool { + tracing::event!( + target: "startup", + tracing::Level::INFO, + "connecting to database with configuration {:?}", + self.clone() + ); + sqlx::postgres::PgPoolOptions::new() + .acquire_timeout(std::time::Duration::from_secs(2)) + .connect_lazy_with(self.get_connect_options()) + } + + pub async fn migrate(pool: &sqlx::PgPool) { + sqlx::migrate!() + .run(pool) + .await + .expect("Failed to migrate the database"); + } +} diff --git a/gejdr-core/src/lib.rs b/gejdr-core/src/lib.rs new file mode 100644 index 0000000..093c493 --- /dev/null +++ b/gejdr-core/src/lib.rs @@ -0,0 +1,4 @@ +pub mod database; +pub mod models; +pub mod telemetry; +pub use sqlx; diff --git a/gejdr-core/src/models/accounts.rs b/gejdr-core/src/models/accounts.rs new file mode 100644 index 0000000..53dea05 --- /dev/null +++ b/gejdr-core/src/models/accounts.rs @@ -0,0 +1,337 @@ +use sqlx::PgPool; +use super::Crud; + +type Timestampz = chrono::DateTime; + +#[derive(serde::Deserialize, PartialEq, Eq, Debug, Clone, Default)] +pub struct RemoteUser { + id: String, + username: String, + global_name: Option, + email: Option, + avatar: Option, +} + +impl RemoteUser { + /// Refresh in database the row related to the remote user. Maybe + /// create a row for this user if needed. + pub async fn refresh_in_database(self, pool: &PgPool) -> Result { + match User::find(pool, &self.id).await? { + Some(local_user) => local_user.update_from_remote(self).update(pool).await, + None => User::from(self).create(pool).await, + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone, Crud)] +#[crud(table = "users")] +pub struct User { + #[crud(id = true)] + pub id: String, + pub username: String, + pub email: Option, + pub avatar: Option, + pub name: Option, + pub created_at: Timestampz, + pub last_updated: Timestampz, +} + +impl From for User { + fn from(value: RemoteUser) -> Self { + Self { + id: value.id, + username: value.username, + email: value.email, + avatar: value.avatar, + name: value.global_name, + created_at: chrono::offset::Utc::now(), + last_updated: chrono::offset::Utc::now(), + } + } +} + +impl PartialEq for User { + #[allow(clippy::suspicious_operation_groupings)] + fn eq(&self, other: &RemoteUser) -> bool { + self.id == other.id + && self.username == other.username + && self.email == other.email + && self.avatar == other.avatar + && self.name == other.global_name + } +} + +impl PartialEq for RemoteUser { + fn eq(&self, other: &User) -> bool { + other == self + } +} + +impl User { + pub fn update_from_remote(self, from: RemoteUser) -> Self { + if self == from { + self + } else { + Self { + username: from.username, + email: from.email, + avatar: from.avatar, + name: from.global_name, + last_updated: chrono::offset::Utc::now(), + ..self + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn convert_remote_user_to_local_user() { + let remote = RemoteUser { + id: "user-id".into(), + username: "username".into(), + global_name: None, + email: Some("user@example.com".into()), + avatar: None, + }; + let local: User = remote.into(); + let expected = User { + id: "user-id".into(), + username: "username".into(), + email: Some("user@example.com".into()), + avatar: None, + name: None, + created_at: local.created_at, + last_updated: local.last_updated, + }; + assert_eq!(expected, local); + } + + #[test] + fn can_compare_remote_and_local_user() { + let remote_same = RemoteUser { + id: "user-id".into(), + username: "username".into(), + global_name: None, + email: Some("user@example.com".into()), + avatar: None, + }; + let remote_different = RemoteUser { + id: "user-id".into(), + username: "username".into(), + global_name: None, + email: Some("user@example.com".into()), + avatar: Some("some-hash".into()), + }; + let local = User { + id: "user-id".into(), + username: "username".into(), + email: Some("user@example.com".into()), + avatar: None, + name: None, + created_at: chrono::offset::Utc::now(), + last_updated: chrono::offset::Utc::now(), + }; + assert_eq!(remote_same, local); + assert_ne!(remote_different, local); + } + + #[sqlx::test] + async fn add_new_remote_users_in_database(pool: sqlx::PgPool) -> sqlx::Result<()> { + let remote1 = RemoteUser { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + let remote2 = RemoteUser { + id: "id2".into(), + username: "user2".into(), + ..Default::default() + }; + remote1.refresh_in_database(&pool).await?; + remote2.refresh_in_database(&pool).await?; + let users = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, users.len()); + Ok(()) + } + + #[sqlx::test(fixtures("accounts"))] + async fn update_local_users_in_db_from_remote(pool: sqlx::PgPool) -> sqlx::Result<()> { + let users = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, users.len()); + let remote1 = RemoteUser { + id: "id1".into(), + username: "user1-new".into(), + ..Default::default() + }; + let remote2 = RemoteUser { + id: "id2".into(), + username: "user2-new".into(), + ..Default::default() + }; + remote1.refresh_in_database(&pool).await?; + remote2.refresh_in_database(&pool).await?; + let users = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, users.len()); + users + .iter() + .for_each(|user| assert!(user.last_updated > user.created_at)); + Ok(()) + } + + #[test] + fn update_local_user_from_identical_remote_shouldnt_change_local() { + let remote = RemoteUser { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + let local = User { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + let new_local = local.clone().update_from_remote(remote); + assert_eq!(local, new_local); + } + + #[test] + fn update_local_user_from_different_remote() { + let remote = RemoteUser { + id: "id1".into(), + username: "user2".into(), + ..Default::default() + }; + let local = User { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + let new_local = local.clone().update_from_remote(remote.clone()); + assert_ne!(remote, local); + assert_eq!(remote, new_local); + } + + #[sqlx::test] + async fn save_user_in_database(pool: sqlx::PgPool) -> sqlx::Result<()> { + let user = User { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + user.create(&pool).await?; + let users = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(1, users.len()); + assert_eq!(Some(user), users.first().cloned()); + Ok(()) + } + + #[sqlx::test(fixtures("accounts"))] + async fn update_user_in_database(pool: sqlx::PgPool) -> sqlx::Result<()> { + let db_user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = 'id1'") + .fetch_one(&pool) + .await?; + assert!(db_user.name.is_none()); + let user = User { + id: "id1".into(), + username: "user1".into(), + name: Some("Cool Name".into()), + ..Default::default() + }; + user.update(&pool).await?; + let db_user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = 'id1'") + .fetch_one(&pool) + .await?; + assert!(db_user.name.is_some()); + assert_eq!(Some("Cool Name".to_string()), db_user.name); + Ok(()) + } + + #[sqlx::test] + async fn save_or_update_saves_if_no_exist(pool: sqlx::PgPool) -> sqlx::Result<()> { + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(0, rows.len()); + let user = User { + id: "id1".into(), + username: "user1".into(), + ..Default::default() + }; + user.create_or_update(&pool).await?; + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(1, rows.len()); + let db_user = rows.first(); + assert_eq!(Some(user), db_user.cloned()); + Ok(()) + } + + #[sqlx::test(fixtures("accounts"))] + async fn save_or_update_updates_if_exists(pool: sqlx::PgPool) -> sqlx::Result<()> { + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, rows.len()); + let user = User { + id: "id1".into(), + username: "user1".into(), + name: Some("Cool Nam".into()), + ..Default::default() + }; + user.create_or_update(&pool).await?; + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, rows.len()); + let db_user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = 'id1'") + .fetch_one(&pool) + .await?; + assert_eq!(user.name, db_user.name); + Ok(()) + } + + #[sqlx::test(fixtures("accounts"))] + async fn delete_removes_account_from_db(pool: sqlx::PgPool) -> sqlx::Result<()> { + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, rows.len()); + let id = "id1".to_string(); + let deletions = User::delete_by_id(&pool, &id).await?; + assert_eq!(1, deletions); + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(1, rows.len()); + Ok(()) + } + + #[sqlx::test(fixtures("accounts"))] + async fn delete_with_wrong_id_shouldnt_delete_anything(pool: sqlx::PgPool) -> sqlx::Result<()> { + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, rows.len()); + let id = "invalid".to_string(); + let deletions = User::delete_by_id(&pool, &id).await?; + assert_eq!(0, deletions); + let rows = sqlx::query_as!(User, "SELECT * FROM users") + .fetch_all(&pool) + .await?; + assert_eq!(2, rows.len()); + Ok(()) + } +} diff --git a/gejdr-core/src/models/fixtures/accounts.sql b/gejdr-core/src/models/fixtures/accounts.sql new file mode 100644 index 0000000..31bc5a8 --- /dev/null +++ b/gejdr-core/src/models/fixtures/accounts.sql @@ -0,0 +1,2 @@ +INSERT INTO users (id, username) VALUES ('id1', 'user1'); +INSERT INTO users (id, username) VALUES ('id2', 'user2'); diff --git a/gejdr-core/src/models/mod.rs b/gejdr-core/src/models/mod.rs new file mode 100644 index 0000000..eabca60 --- /dev/null +++ b/gejdr-core/src/models/mod.rs @@ -0,0 +1,73 @@ +pub mod accounts; +pub use gejdr_macros::Crud; + +pub trait Crud { + /// Find the entiy in the database based on its identifier. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn find( + pool: &sqlx::PgPool, + id: &Id, + ) -> impl std::future::Future>> + Send + where + Self: Sized; + + /// Create the entity in the database. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn create( + &self, + pool: &sqlx::PgPool, + ) -> impl std::future::Future> + Send + where + Self: Sized; + + /// Update an entity with a matching identifier in the database. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn update( + &self, + pool: &sqlx::PgPool, + ) -> impl std::future::Future> + Send + where + Self: Sized; + + /// Update an entity with a matching identifier in the database if + /// it exists, create it otherwise. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn create_or_update( + &self, + pool: &sqlx::PgPool, + ) -> impl std::future::Future> + Send + where + Self: Sized; + + /// Delete the entity from the database if it exists. + /// + /// # Returns + /// Returns the amount of rows affected by the deletion. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn delete( + &self, + pool: &sqlx::PgPool, + ) -> impl std::future::Future> + Send; + + /// Delete any entity with the identifier `id`. + /// + /// # Returns + /// Returns the amount of rows affected by the deletion. + /// + /// # Errors + /// Returns any error Postgres may have encountered + fn delete_by_id( + pool: &sqlx::PgPool, + id: &Id, + ) -> impl std::future::Future> + Send; +} diff --git a/src/telemetry.rs b/gejdr-core/src/telemetry.rs similarity index 92% rename from src/telemetry.rs rename to gejdr-core/src/telemetry.rs index 7dfafed..29e0381 100644 --- a/src/telemetry.rs +++ b/gejdr-core/src/telemetry.rs @@ -5,7 +5,7 @@ pub fn get_subscriber(debug: bool) -> impl tracing::Subscriber + Send + Sync { let env_filter = if debug { "debug" } else { "info" }.to_string(); let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(env_filter)); - let stdout_log = tracing_subscriber::fmt::layer().pretty(); + let stdout_log = tracing_subscriber::fmt::layer().pretty().with_test_writer(); let subscriber = tracing_subscriber::Registry::default() .with(env_filter) .with(stdout_log); diff --git a/gejdr-macros/Cargo.toml b/gejdr-macros/Cargo.toml new file mode 100644 index 0000000..5c4fdeb --- /dev/null +++ b/gejdr-macros/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gejdr-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +deluxe = "0.5.0" +proc-macro2 = "1.0.93" +quote = "1.0.38" +syn = "2.0.96" + +[dependencies.sqlx] +version = "0.8.3" +default-features = false +features = ["postgres", "uuid", "chrono", "migrate", "runtime-tokio", "macros"] diff --git a/gejdr-macros/src/crud/ir.rs b/gejdr-macros/src/crud/ir.rs new file mode 100644 index 0000000..ecaf7d6 --- /dev/null +++ b/gejdr-macros/src/crud/ir.rs @@ -0,0 +1,23 @@ +#[derive(deluxe::ExtractAttributes)] +#[deluxe(attributes(crud))] +pub struct CrudStructAttributes { + pub table: String, +} + +#[derive(deluxe::ExtractAttributes, Clone)] +#[deluxe(attributes(crud))] +pub struct CrudFieldAttributes { + #[deluxe(default = false)] + pub id: bool, + #[deluxe(default = None)] + pub column: Option, +} + +#[derive(Clone)] +pub struct CrudField { + pub ident: syn::Ident, + pub field: syn::Field, + pub column: String, + pub id: bool, + pub ty: syn::Type +} diff --git a/gejdr-macros/src/crud/mod.rs b/gejdr-macros/src/crud/mod.rs new file mode 100644 index 0000000..e1be25f --- /dev/null +++ b/gejdr-macros/src/crud/mod.rs @@ -0,0 +1,195 @@ +use ir::{CrudField, CrudFieldAttributes, CrudStructAttributes}; +use quote::quote; +use syn::DeriveInput; + +mod ir; + +fn extract_crud_field_attrs( + ast: &mut DeriveInput, +) -> deluxe::Result<(Vec, CrudField)> { + let mut field_attrs: Vec = Vec::new(); + // let mut identifier: Option = None; + let mut identifier: Option = None; + let mut identifier_counter = 0; + if let syn::Data::Struct(s) = &mut ast.data { + for field in &mut s.fields { + let ident = field.clone().ident.unwrap(); + let ty = field.clone().ty; + let attrs: CrudFieldAttributes = + deluxe::extract_attributes(field).expect("Could not extract attributes from field"); + let field = CrudField { + ident: ident.clone(), + field: field.to_owned(), + column: attrs.column.unwrap_or_else(|| ident.to_string()), + id: attrs.id, + ty + }; + if attrs.id { + identifier_counter += 1; + identifier = Some(field.clone()); + } + if identifier_counter > 1 { + return Err(syn::Error::new_spanned( + field.field, + "Struct {name} can only have one identifier", + )); + } + field_attrs.push(field); + } + } + if identifier_counter < 1 { + Err(syn::Error::new_spanned( + ast, + "Struct {name} must have one identifier", + )) + } else { + Ok((field_attrs, identifier.unwrap())) + } +} + +fn generate_find_query( + table: &str, + id: &CrudField +) -> proc_macro2::TokenStream { + let find_string = format!("SELECT * FROM {} WHERE {} = $1", table, id.column); + let ty = &id.ty; + quote! { + async fn find(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result> { + ::sqlx::query_as!(Self, #find_string, id) + .fetch_optional(pool) + .await + } + } +} + +fn generate_create_query( + table: &str, + fields: &[CrudField] +) -> proc_macro2::TokenStream { + let inputs: Vec = (1..=fields.len()) + .map(|num| format!("${num}")) + .collect(); + let create_string = format!( + "INSERT INTO {} ({}) VALUES ({}) RETURNING *", + table, + fields.iter().map(|v| v.column.clone()).collect::>().join(", "), + inputs.join(", ") + ); + let field_idents: Vec = fields.iter().map(|f| f.ident.clone()).collect(); + quote! { + async fn create(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result { + ::sqlx::query_as!( + Self, + #create_string, + #(self.#field_idents),* + ) + .fetch_one(pool) + .await + } + } +} + +fn generate_update_query( + table: &str, + fields: &[CrudField], + id: &CrudField +) -> proc_macro2::TokenStream { + let mut fields: Vec<&CrudField> = fields.iter().filter(|f| !f.id).collect(); + let update_columns = fields.iter().enumerate() + .map(|(i, &field)| format!("{} = ${}", field.column, i + 1)) + .collect::>() + .join(", "); + let update_string = format!( + "UPDATE {} SET {} WHERE {} = ${} RETURNING *", + table, + update_columns, + id.column, + fields.len() + 1 + ); + fields.push(id); + let field_idents: Vec<_> = fields.iter().map(|f| f.ident.clone()).collect(); + quote! { + async fn update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result { + ::sqlx::query_as!( + Self, + #update_string, + #(self.#field_idents),* + ) + .fetch_one(pool) + .await + } + } +} + +fn generate_delete_query( + table: &str, + id: &CrudField +) -> proc_macro2::TokenStream { + let delete_string = format!("DELETE FROM {} WHERE {} = $1", table, id.column); + let ty = &id.ty; + let ident = &id.ident; + + quote! { + async fn delete_by_id(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result { + let rows_affected = ::sqlx::query!(#delete_string, id) + .execute(pool) + .await? + .rows_affected(); + Ok(rows_affected) + } + + async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result { + let rows_affected = ::sqlx::query!(#delete_string, self.#ident) + .execute(pool) + .await? + .rows_affected(); + Ok(rows_affected) + } + } +} + +pub fn crud_derive_macro2( + item: proc_macro2::TokenStream, +) -> deluxe::Result { + // parse + let mut ast: DeriveInput = syn::parse2(item).expect("Failed to parse input"); + + // extract struct attributes + let CrudStructAttributes { table } = + deluxe::extract_attributes(&mut ast).expect("Could not extract attributes from struct"); + + // extract field attributes + let (fields, id) = extract_crud_field_attrs(&mut ast)?; + let ty = &id.ty; + let id_ident = &id.ident; + + // define impl variables + let ident = &ast.ident; + let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl(); + + // generate + let find_query = generate_find_query(&table, &id); + let create_query = generate_create_query(&table, &fields); + let update_query = generate_update_query(&table, &fields, &id); + let delete_query = generate_delete_query(&table, &id); + let code = quote! { + impl #impl_generics Crud<#ty> for #ident #type_generics #where_clause { + #find_query + + #create_query + + #update_query + + async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result { + if Self::find(pool, &self.#id_ident).await?.is_some() { + self.update(pool).await + } else { + self.create(pool).await + } + } + + #delete_query + } + }; + Ok(code) +} diff --git a/gejdr-macros/src/lib.rs b/gejdr-macros/src/lib.rs new file mode 100644 index 0000000..45d0b14 --- /dev/null +++ b/gejdr-macros/src/lib.rs @@ -0,0 +1,19 @@ +#![deny(clippy::all)] +#![deny(clippy::pedantic)] +#![deny(clippy::nursery)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::unused_async)] +#![allow(clippy::useless_let_if_seq)] // Reason: prevents some OpenApi structs from compiling + +mod crud; +use crud::crud_derive_macro2; + +/// Generates CRUD code for Sqlx for a struct. +/// +/// # Panics +/// +/// May panic if errors arise while parsing and generating code. +#[proc_macro_derive(Crud, attributes(crud))] +pub fn crud_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + crud_derive_macro2(item.into()).unwrap().into() +} diff --git a/justfile b/justfile index 427a37f..0c85e5e 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,7 @@ -default: run +mod backend 'gejdr-backend/backend.just' +mod docker -prepare: - cargo sqlx prepare - -migrate: - sqlx migrate run +default: lint format: cargo fmt --all @@ -12,38 +9,30 @@ format: format-check: cargo fmt --check --all -build: - cargo auditable build +migrate: + sqlx migrate run --source gejdr-core/migrations -build-release: - cargo auditable build --release +build $SQLX_OFFLINE="1": + cargo auditable build --bin gejdr-backend + cargo auditable build --bin gejdr-bot -run: docker-start - cargo auditable run - -run-no-docker: - cargo auditable run +build-release $SQLX_OFFLINE="1": + cargo auditable build --release --bin gejdr-backend + cargo auditable build --release --bin gejdr-bot lint: cargo clippy --all-targets -msrv: - cargo msrv verify - -release-build: - cargo auditable build --release - -release-run: - cargo auditable run --release - audit: build - cargo audit bin target/debug/gege-jdr-backend + cargo audit bin target/debug/gejdr-backend + cargo audit bin target/debug/gejdr-bot audit-release: build-release - cargo audit bin target/release/gege-jdr-backend + cargo audit bin target/release/gejdr-backend + cargo audit bin target/release/gejdr-bot test: - cargo test + cargo test --all-targets --all coverage: mkdir -p coverage @@ -53,19 +42,10 @@ coverage-ci: mkdir -p coverage cargo tarpaulin --config .tarpaulin.ci.toml -check-all: format-check lint msrv coverage audit +check-all: format-check lint coverage audit -docker-build: - nix build .#docker - -docker-start: - docker compose -f docker/compose.dev.yml up -d - -docker-stop: - docker compose -f docker/compose.dev.yml down - -docker-logs: - docker compose -f docker/compose.dev.yml logs -f +docker-backend $SQLX_OFFLINE="1": + nix build .#dockerBackend ## Local Variables: ## mode: makefile diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d4fc4a3..1313875 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.78.0" +channel = "1.81.0" components = [ "rustfmt", "rust-src", "clippy", "rust-analyzer" ] profile = "default"