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 100% rename from .gitea/workflows/publish.yaml rename to .gitea/workflows/publish.yaml.bak 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 index a035e59..4d405d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -44,7 +44,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", "cipher 0.3.0", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "opaque-debug", ] @@ -56,7 +56,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher 0.4.4", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", ] [[package]] @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -130,14 +130,20 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.81" +name = "arraydeque" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[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.72", + "syn 2.0.89", ] [[package]] @@ -157,25 +163,31 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "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" @@ -247,15 +259,18 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.8" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -309,16 +324,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "chumsky" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" -dependencies = [ - "hashbrown 0.14.5", - "stacker", -] - [[package]] name = "cipher" version = "0.3.0" @@ -349,14 +354,13 @@ dependencies = [ [[package]] name = "config" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" dependencies = [ "async-trait", - "convert_case 0.6.0", + "convert_case", "json5", - "lazy_static", "nom", "pathdiff", "ron", @@ -364,7 +368,7 @@ dependencies = [ "serde", "serde_json", "toml", - "yaml-rust", + "yaml-rust2", ] [[package]] @@ -393,12 +397,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -427,10 +425,20 @@ dependencies = [ ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +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" @@ -443,9 +451,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -565,7 +573,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] @@ -576,7 +584,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] @@ -607,15 +615,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +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 = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.72", + "syn 2.0.89", + "unicode-xid", ] [[package]] @@ -639,6 +655,17 @@ dependencies = [ "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.89", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -663,27 +690,11 @@ dependencies = [ "serde", ] -[[package]] -name = "email-encoding" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" -dependencies = [ - "base64 0.22.1", - "memchr", -] - -[[package]] -name = "email_address" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" - [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -728,15 +739,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -760,9 +771,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -775,9 +786,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -785,15 +796,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -813,38 +824,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -871,25 +882,43 @@ dependencies = [ ] [[package]] -name = "gege-jdr-backend" +name = "gejdr-backend" version = "0.1.0" dependencies = [ "chrono", "config", "dotenvy", - "lettre", + "gejdr-core", + "oauth2", "poem", "poem-openapi", + "quote", + "reqwest 0.12.9", "serde", "serde_json", - "sqlx", - "thiserror", + "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", + "serde", + "sqlx", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -907,8 +936,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -933,22 +964,41 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.5" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "atomic-waker", - "bytes 1.7.1", + "bytes 1.8.0", "fnv", "futures-core", "futures-sink", - "http", + "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", @@ -956,12 +1006,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" @@ -972,6 +1016,21 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[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.9.1" @@ -988,9 +1047,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", - "bytes 1.7.1", + "bytes 1.8.0", "headers-core", - "http", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -1002,7 +1061,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.1.0", ] [[package]] @@ -1061,14 +1120,14 @@ dependencies = [ ] [[package]] -name = "hostname" -version = "0.4.0" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "cfg-if", - "libc", - "windows", + "bytes 1.8.0", + "fnv", + "itoa", ] [[package]] @@ -1077,19 +1136,30 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.7.1", + "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.7.1", - "http", + "bytes 1.8.0", + "http 1.1.0", ] [[package]] @@ -1098,18 +1168,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.7.1", + "bytes 1.8.0", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1119,44 +1189,105 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.1" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.7.1", + "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", - "http", - "http-body", + "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.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "bytes 1.7.1", + "bytes 1.8.0", + "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "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.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1175,6 +1306,124 @@ 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.89", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1183,22 +1432,33 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "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 = "indexmap" -version = "2.3.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -1211,16 +1471,22 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.11" +name = "ipnet" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +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.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1245,71 +1511,41 @@ dependencies = [ "spin", ] -[[package]] -name = "lettre" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a62049a808f1c4e2356a2a380bd5f2aca3b011b0b482cf3b914ba1731426969" -dependencies = [ - "async-trait", - "base64 0.22.1", - "chumsky", - "email-encoding", - "email_address", - "fastrand", - "futures-io", - "futures-util", - "hostname", - "httpdate", - "idna", - "mime", - "nom", - "percent-encoding", - "quoted_printable", - "rustls 0.23.12", - "rustls-pemfile", - "socket2", - "tokio", - "tokio-rustls 0.26.0", - "url", - "webpki-roots", -] - [[package]] name = "libc" -version = "0.2.155" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[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" @@ -1365,18 +1601,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", @@ -1390,10 +1626,10 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ - "bytes 1.7.1", + "bytes 1.8.0", "encoding_rs", "futures-util", - "http", + "http 1.1.0", "httparse", "memchr", "mime", @@ -1488,19 +1724,39 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.3" +name = "oauth2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +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.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -1510,12 +1766,12 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "ordered-multimap" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", - "hashbrown 0.13.2", + "hashbrown 0.14.5", ] [[package]] @@ -1526,9 +1782,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -1548,7 +1804,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -1561,9 +1817,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" [[package]] name = "pem-rfc7468" @@ -1582,20 +1838,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -1603,22 +1859,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -1647,9 +1903,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1680,26 +1936,26 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "poem" -version = "3.0.4" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ba1c27f8f89e1bccdda0c680f72790545a11a8d8555819472f5839d7a8ca9d" +checksum = "20d9d595efb128dddda67e32c3d6d26c10a94a81c914d6c5cf0c9f92d19136ae" dependencies = [ "base64 0.22.1", - "bytes 1.7.1", + "bytes 1.8.0", "chrono", "cookie", "csrf", "futures-util", "headers", - "http", + "http 1.1.0", "http-body-util", - "hyper", + "hyper 1.5.1", "hyper-util", "mime", "multer", @@ -1708,19 +1964,21 @@ dependencies = [ "percent-encoding", "pin-project-lite", "poem-derive", - "quick-xml 0.36.1", + "priority-queue", + "quick-xml", + "rand", "regex", "rfc7239", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", "serde_yaml", "smallvec", "sse-codec", - "sync_wrapper", + "sync_wrapper 1.0.2", "tempfile", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "tokio-rustls 0.25.0", @@ -1732,24 +1990,24 @@ dependencies = [ [[package]] name = "poem-derive" -version = "3.0.4" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62fea1692d80a000126f9b28d865012a160b80000abb53ccf152b428222c155" +checksum = "7f2553c04acbd3887e2ad1959ff007fb9ec05d15d67931b6fdd6eb47de138649" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] name = "poem-openapi" -version = "5.0.3" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f4ac688d8b83fbbc8de929dee207d345600a6b3af315927e92dba26a023103" +checksum = "d92bcce356da677d56723f1f2017276457659d6b93abdcf3ebb218b5050f5bc6" dependencies = [ "base64 0.22.1", - "bytes 1.7.1", + "bytes 1.8.0", "chrono", "derive_more", "futures-util", @@ -1758,33 +2016,33 @@ dependencies = [ "num-traits", "poem", "poem-openapi-derive", - "quick-xml 0.32.0", + "quick-xml", "regex", "serde", "serde_json", "serde_urlencoded", "serde_yaml", - "thiserror", + "thiserror 1.0.69", "tokio", "uuid", ] [[package]] name = "poem-openapi-derive" -version = "5.0.3" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0812a8f13ac63020d1274d80ea2229e858ed0118a2d9537744465995e0913375" +checksum = "6c59f3a8822f162875cc5de29c8cd911e367a79078b06a9fedc081e1f259dd24" dependencies = [ "darling", - "http", + "http 1.1.0", "indexmap", "mime", "proc-macro-crate", "proc-macro2", "quote", "regex", - "syn 2.0.72", - "thiserror", + "syn 2.0.89", + "thiserror 1.0.69", ] [[package]] @@ -1793,7 +2051,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "opaque-debug", "universal-hash 0.4.0", ] @@ -1805,7 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "opaque-debug", "universal-hash 0.4.0", ] @@ -1817,7 +2075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "opaque-debug", "universal-hash 0.5.1", ] @@ -1838,67 +2096,105 @@ dependencies = [ ] [[package]] -name = "proc-macro-crate" -version = "3.1.0" +name = "priority-queue" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ - "toml_edit 0.21.1", + "autocfg", + "equivalent", + "indexmap", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", "serde", ] [[package]] -name = "quick-xml" -version = "0.36.1" +name = "quinn" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "memchr", - "serde", + "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.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] -[[package]] -name = "quoted_printable" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" - [[package]] name = "rand" version = "0.8.5" @@ -1931,32 +2227,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1970,13 +2257,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1987,9 +2274,94 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +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" @@ -2049,9 +2421,9 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" dependencies = [ "cfg-if", "ordered-multimap", @@ -2064,19 +2436,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustc-hash" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -2085,6 +2454,18 @@ dependencies = [ "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" @@ -2094,47 +2475,67 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ - "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +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 = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +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", @@ -2154,36 +2555,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "semver" -version = "1.0.23" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "serde" -version = "1.0.205" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2192,10 +2597,20 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.7" +name = "serde_path_to_error" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +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", ] @@ -2232,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "digest 0.10.7", ] @@ -2244,7 +2659,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "digest 0.9.0", "opaque-debug", ] @@ -2256,7 +2671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", - "cpufeatures 0.2.12", + "cpufeatures 0.2.16", "digest 0.10.7", ] @@ -2269,6 +2684,12 @@ 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" @@ -2328,9 +2749,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", @@ -2338,9 +2759,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2351,13 +2772,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "byteorder", - "bytes 1.7.1", + "bytes 1.8.0", "chrono", "crc", "crossbeam-queue", @@ -2369,7 +2790,7 @@ dependencies = [ "futures-io", "futures-util", "hashbrown 0.14.5", - "hashlink", + "hashlink 0.9.1", "hex", "indexmap", "log", @@ -2382,7 +2803,7 @@ dependencies = [ "sha2 0.10.8", "smallvec", "sqlformat", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -2392,22 +2813,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] name = "sqlx-macros-core" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", @@ -2423,7 +2844,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.72", + "syn 2.0.89", "tempfile", "tokio", "url", @@ -2431,15 +2852,15 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", "bitflags 2.6.0", "byteorder", - "bytes 1.7.1", + "bytes 1.8.0", "chrono", "crc", "digest 0.10.7", @@ -2467,7 +2888,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "tracing", "uuid", "whoami", @@ -2475,9 +2896,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", @@ -2507,7 +2928,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "tracing", "uuid", "whoami", @@ -2515,9 +2936,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "chrono", @@ -2551,17 +2972,10 @@ dependencies = [ ] [[package]] -name = "stacker" -version = "0.1.15" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "winapi", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stringprep" @@ -2599,9 +3013,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2610,18 +3024,56 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +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 = "tempfile" -version = "3.12.0" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[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", @@ -2632,22 +3084,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "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.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", +] + +[[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.89", ] [[package]] @@ -2700,6 +3172,16 @@ 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" @@ -2717,12 +3199,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "bytes 1.7.1", + "bytes 1.8.0", "libc", "mio", "pin-project-lite", @@ -2739,7 +3221,17 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", +] + +[[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]] @@ -2759,16 +3251,16 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.18", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -2777,11 +3269,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.7.1", + "bytes 1.8.0", "futures-core", "futures-io", "futures-sink", @@ -2798,7 +3290,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -2812,28 +3304,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] +[[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" @@ -2854,7 +3341,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", ] [[package]] @@ -2909,6 +3396,12 @@ dependencies = [ "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" @@ -2917,9 +3410,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uncased" @@ -2932,36 +3425,42 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -3003,20 +3502,33 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] -name = "uuid" -version = "1.10.0" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", "serde", @@ -3040,6 +3552,15 @@ 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" @@ -3054,34 +3575,47 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" +name = "wasm-bindgen-futures" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +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", @@ -3089,47 +3623,73 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +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.26.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +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.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", ] [[package]] name = "wildmatch" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" +checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" [[package]] name = "winapi" @@ -3153,16 +3713,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.52.0" @@ -3172,6 +3722,36 @@ 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" @@ -3322,29 +3902,68 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] -name = "winnow" -version = "0.6.18" +name = "winreg" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "memchr", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +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 = [ - "linked-hash-map", + "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.89", + "synstructure", ] [[package]] @@ -3365,7 +3984,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.89", +] + +[[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.89", + "synstructure", ] [[package]] @@ -3373,3 +4013,25 @@ 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.89", +] diff --git a/Cargo.toml b/Cargo.toml index 3cae004..72d56ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,61 +1,8 @@ -[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", ] - - -[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" \ No newline at end of file diff --git a/backend.just b/backend.just new file mode 100644 index 0000000..a4c741d --- /dev/null +++ b/backend.just @@ -0,0 +1,19 @@ +default: run + +prepare: + pushd gejdr-backend + cargo sqlx prepare + popd + +build: + cargo auditable build + +build-release: + cargo auditable build --release + +run: + cargo auditable run --bin gejdr-backend + +## Local Variables: +## mode: makefile +## End: 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 5dfb7c7..c96b1af 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1732014248, - "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=", + "lastModified": 1736344531, + "narHash": "sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc+c2c=", "owner": "nixos", "repo": "nixpkgs", - "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367", + "rev": "bffc22eb12172e6db3c5dde9e3e5628f8e3e7912", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1732242723, - "narHash": "sha256-NWI8csIK0ujFlFuEXKnoc+7hWoCiEtINK9r48LUUMeU=", + "lastModified": 1736476219, + "narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a229311fcb45b88a95fdfa5cecd8349c809a272a", + "rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 912b28b..1ae2bb0 100644 --- a/flake.nix +++ b/flake.nix @@ -18,7 +18,7 @@ rustc = rustVersion; }; - appName = "gege-jdr-backend"; + appName = "gejdr-backend"; appRustBuild = rustPlatform.buildRustPackage { pname = appName; @@ -49,7 +49,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/settings/base.yaml b/gejdr-backend/settings/base.yaml similarity index 100% rename from settings/base.yaml rename to gejdr-backend/settings/base.yaml 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 80% rename from src/settings.rs rename to gejdr-backend/src/settings.rs index c362473..3c4d1db 100644 --- a/src/settings.rs +++ b/gejdr-backend/src/settings.rs @@ -1,4 +1,4 @@ -use sqlx::ConnectOptions; +use gejdr_core::database::Database; #[derive(Debug, serde::Deserialize, Clone, Default)] pub struct Settings { @@ -13,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 @@ -66,7 +58,7 @@ impl Settings { )) .add_source( config::Environment::with_prefix("APP") - .prefix_separator("_") + .prefix_separator("__") .separator("__"), ) .build()?; @@ -84,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, @@ -167,8 +130,8 @@ pub struct EmailSettings { #[derive(Debug, serde::Deserialize, Clone, Default)] pub struct Discord { - client_id: String, - client_secret: String, + pub client_id: String, + pub client_secret: String, } #[cfg(test)] @@ -235,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(), @@ -244,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..0d645ce --- /dev/null +++ b/gejdr-core/Cargo.toml @@ -0,0 +1,16 @@ +[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"] } + +[dependencies.sqlx] +version = "0.8.2" +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..a4699b1 --- /dev/null +++ b/gejdr-core/src/models/accounts.rs @@ -0,0 +1,396 @@ +use sqlx::PgPool; + +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).save(pool).await, + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone)] +pub struct User { + 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 + } + } + } + + pub async fn find(pool: &PgPool, id: &String) -> Result, sqlx::Error> { + sqlx::query_as!(Self, r#"SELECT * FROM users WHERE id = $1"#, id) + .fetch_optional(pool) + .await + } + + pub async fn save(&self, pool: &PgPool) -> Result { + sqlx::query_as!( + Self, + r#" +INSERT INTO users (id, username, email, avatar, name, created_at, last_updated) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING * +"#, + self.id, + self.username, + self.email, + self.avatar, + self.name, + self.created_at, + self.last_updated + ) + .fetch_one(pool) + .await + } + + pub async fn update(&self, pool: &PgPool) -> Result { + sqlx::query_as!( + Self, + r#" +UPDATE users +SET username = $1, email = $2, avatar = $3, name = $4, last_updated = $5 +WHERE id = $6 +RETURNING * +"#, + self.username, + self.email, + self.avatar, + self.name, + self.last_updated, + self.id + ) + .fetch_one(pool) + .await + } + + pub async fn save_or_update(&self, pool: &PgPool) -> Result { + if Self::find(pool, &self.id).await?.is_some() { + self.update(pool).await + } else { + self.save(pool).await + } + } + + pub async fn delete(pool: &PgPool, id: &String) -> Result { + let rows_affected = sqlx::query!("DELETE FROM users WHERE id = $1", id) + .execute(pool) + .await? + .rows_affected(); + Ok(rows_affected) + } +} + +#[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.save(&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.save_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.save_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(&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(&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..9bb4894 --- /dev/null +++ b/gejdr-core/src/models/mod.rs @@ -0,0 +1 @@ +pub mod accounts; 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/justfile b/justfile index 427a37f..b570a93 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,7 @@ -default: run +mod backend +mod docker -prepare: - cargo sqlx prepare - -migrate: - sqlx migrate run +default: lint format: cargo fmt --all @@ -12,17 +9,12 @@ format: format-check: cargo fmt --check --all +migrate: + sqlx migrate run --source gejdr-core/migrations + build: - cargo auditable build - -build-release: - cargo auditable build --release - -run: docker-start - cargo auditable run - -run-no-docker: - cargo auditable run + cargo auditable build --bin gejdr-backend + cargo auditable build --bin gejdr-bot lint: cargo clippy --all-targets @@ -31,19 +23,22 @@ msrv: cargo msrv verify release-build: - cargo auditable build --release + cargo auditable build --release --bin gejdr-backend + cargo auditable build --release --bin gejdr-bot 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 +audit-release: + 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 @@ -55,17 +50,8 @@ coverage-ci: check-all: format-check lint msrv 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-build: +# nix build .#docker ## Local Variables: ## mode: makefile diff --git a/migrations/20240809173617_users.down.sql b/migrations/20240809173617_users.down.sql deleted file mode 100644 index 7f7549a..0000000 --- a/migrations/20240809173617_users.down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Add down migration script here -ALTER TABLE IF EXISTS public.sessions DROP CONSTRAINT IF EXISTS sessions_user_id_users_fk; -DROP TABLE IF EXISTS public.sessions; -DROP TABLE IF EXISTS public.users; -DROP EXTENSION IF EXISTS "uuid-ossp"; diff --git a/migrations/20240809173617_users.up.sql b/migrations/20240809173617_users.up.sql deleted file mode 100644 index 1a35bcc..0000000 --- a/migrations/20240809173617_users.up.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Add up migration script here -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -CREATE TABLE IF NOT EXISTS public.users -( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - email character varying(255) NOT NULL, - 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) -); - -CREATE TABLE IF NOT EXISTS public.sessions -( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - user_id uuid NOT NULL, - session_id character varying NOT NULL, - expires_at timestamp with time zone NOT NULL, - PRIMARY KEY (id), - CONSTRAINT sessions_user_id_unique UNIQUE (user_id) -); - -ALTER TABLE IF EXISTS public.sessions - ADD CONSTRAINT sessions_user_id_users_fk FOREIGN KEY (user_id) - REFERENCES public.users (id) MATCH SIMPLE - ON UPDATE CASCADE - ON DELETE CASCADE - NOT VALID;