From d9e29b62b7d3aa490395bbfe364c5cb802bdfbf1 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Sat, 23 Nov 2024 10:00:53 +0100 Subject: [PATCH] feat: OAuth implementation with Discord MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit separates the core features of géjdr from the backend as these will also be used by the bot in the future. This commit also updates the dependencies of the project. It also removes the dependency lettre as well as the mailpit docker service for developers as it appears clearer this project won’t send emails anytime soon. The publication of a docker image is also postponed until later. --- .dir-locals.el | 23 +- .gitea/workflows/ci.yaml | 2 - .../{publish.yaml => publish.yaml.bak} | 0 ...9621cc21b493b4c9ae4c14e30c99a6d8072bd.json | 64 + ...71cef087a159c6ee7182d8ca929ecb748f3b7.json | 14 + ...dc8fdfc05f489328e8376513124dfb42996e3.json | 58 + ...5f8efda9b9cf9eaab140c10246218f0240600.json | 63 + .tarpaulin.ci.toml | 2 + .tarpaulin.local.toml | 2 + Cargo.lock | 1616 ++++++++++++----- Cargo.toml | 65 +- backend.just | 19 + docker/compose.dev.yml | 19 - docker/mod.just | 14 + flake.lock | 12 +- flake.nix | 3 +- gejdr-backend/.tarpaulin.ci.toml | 6 + gejdr-backend/.tarpaulin.local.toml | 7 + gejdr-backend/Cargo.toml | 43 + .../settings}/base.yaml | 0 .../settings}/development.yaml | 4 +- .../settings}/production.yaml | 0 gejdr-backend/src/api_wrapper/discord.rs | 55 + gejdr-backend/src/api_wrapper/mod.rs | 41 + gejdr-backend/src/errors.rs | 14 + {src => gejdr-backend/src}/lib.rs | 20 +- {src => gejdr-backend/src}/main.rs | 2 +- gejdr-backend/src/oauth/discord.rs | 62 + gejdr-backend/src/oauth/mod.rs | 17 + gejdr-backend/src/route/auth.rs | 220 +++ gejdr-backend/src/route/errors.rs | 204 +++ {src => gejdr-backend/src}/route/health.rs | 4 +- {src => gejdr-backend/src}/route/mod.rs | 8 +- {src => gejdr-backend/src}/route/version.rs | 4 +- {src => gejdr-backend/src}/settings.rs | 56 +- {src => gejdr-backend/src}/startup.rs | 49 +- gejdr-bot/.gitignore | 1 + gejdr-bot/Cargo.toml | 6 + gejdr-bot/src/main.rs | 3 + gejdr-core/.gitignore | 1 + gejdr-core/Cargo.toml | 16 + .../migrations/20240809173617_users.down.sql | 3 + .../migrations/20240809173617_users.up.sql | 15 + gejdr-core/src/database.rs | 50 + gejdr-core/src/lib.rs | 4 + gejdr-core/src/models/accounts.rs | 396 ++++ gejdr-core/src/models/fixtures/accounts.sql | 2 + gejdr-core/src/models/mod.rs | 1 + {src => gejdr-core/src}/telemetry.rs | 2 +- justfile | 50 +- migrations/20240809173617_users.down.sql | 5 - migrations/20240809173617_users.up.sql | 29 - 52 files changed, 2642 insertions(+), 734 deletions(-) rename .gitea/workflows/{publish.yaml => publish.yaml.bak} (100%) create mode 100644 .sqlx/query-24bbd63a324e36f4a0c559c44909621cc21b493b4c9ae4c14e30c99a6d8072bd.json create mode 100644 .sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json create mode 100644 .sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json create mode 100644 .sqlx/query-c36467a81bad236a0c1a8d3fc1b5f8efda9b9cf9eaab140c10246218f0240600.json create mode 100644 backend.just create mode 100644 docker/mod.just create mode 100644 gejdr-backend/.tarpaulin.ci.toml create mode 100644 gejdr-backend/.tarpaulin.local.toml create mode 100644 gejdr-backend/Cargo.toml rename {settings => gejdr-backend/settings}/base.yaml (100%) rename {settings => gejdr-backend/settings}/development.yaml (60%) rename {settings => gejdr-backend/settings}/production.yaml (100%) create mode 100644 gejdr-backend/src/api_wrapper/discord.rs create mode 100644 gejdr-backend/src/api_wrapper/mod.rs create mode 100644 gejdr-backend/src/errors.rs rename {src => gejdr-backend/src}/lib.rs (84%) rename {src => gejdr-backend/src}/main.rs (71%) create mode 100644 gejdr-backend/src/oauth/discord.rs create mode 100644 gejdr-backend/src/oauth/mod.rs create mode 100644 gejdr-backend/src/route/auth.rs create mode 100644 gejdr-backend/src/route/errors.rs rename {src => gejdr-backend/src}/route/health.rs (82%) rename {src => gejdr-backend/src}/route/mod.rs (73%) rename {src => gejdr-backend/src}/route/version.rs (90%) rename {src => gejdr-backend/src}/settings.rs (80%) rename {src => gejdr-backend/src}/startup.rs (76%) create mode 100644 gejdr-bot/.gitignore create mode 100644 gejdr-bot/Cargo.toml create mode 100644 gejdr-bot/src/main.rs create mode 100644 gejdr-core/.gitignore create mode 100644 gejdr-core/Cargo.toml create mode 100644 gejdr-core/migrations/20240809173617_users.down.sql create mode 100644 gejdr-core/migrations/20240809173617_users.up.sql create mode 100644 gejdr-core/src/database.rs create mode 100644 gejdr-core/src/lib.rs create mode 100644 gejdr-core/src/models/accounts.rs create mode 100644 gejdr-core/src/models/fixtures/accounts.sql create mode 100644 gejdr-core/src/models/mod.rs rename {src => gejdr-core/src}/telemetry.rs (92%) delete mode 100644 migrations/20240809173617_users.down.sql delete mode 100644 migrations/20240809173617_users.up.sql 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;