From e0b7b2098272ab659d8f7fc1581d60317c97da56 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Tue, 11 Feb 2025 10:57:36 +0200 Subject: [PATCH 1/2] clean slate --- .dockerfile | 8 + .env.example | 1 + Cargo.lock | 4340 ----------------- Cargo.toml | 73 +- Dockerfile | 31 + README.md | 17 + backend/Cargo.toml | 42 - .../migrations/20250125123853_init.down.sql | 11 - backend/migrations/20250125123853_init.up.sql | 28 - backend/src/config.rs | 116 - backend/src/db/mod.rs | 1 - backend/src/db/users.rs | 36 - backend/src/domain/mod.rs | 1 - backend/src/domain/user/error.rs | 22 - backend/src/domain/user/mod.rs | 4 - backend/src/domain/user/new_user.rs | 7 - backend/src/domain/user/user_code.rs | 73 - backend/src/domain/user/username.rs | 73 - backend/src/error/app.rs | 88 - backend/src/error/mod.rs | 1 - backend/src/lib.rs | 8 - backend/src/main.rs | 18 - backend/src/routes/api/mod.rs | 9 - backend/src/routes/api/v1/auth.rs | 72 - backend/src/routes/api/v1/mod.rs | 9 - backend/src/server.rs | 38 - backend/src/startup.rs | 17 - {backend/config => config}/base.toml | 2 +- {backend/config => config}/local.toml | 0 {backend/config => config}/production.toml | 0 frontend/.gitignore | 14 - frontend/Cargo.toml | 105 - frontend/README.md | 91 - frontend/end2end/.gitignore | 3 - frontend/end2end/package-lock.json | 167 - frontend/end2end/package.json | 15 - frontend/end2end/playwright.config.ts | 105 - frontend/end2end/tests/example.spec.ts | 9 - frontend/end2end/tsconfig.json | 109 - frontend/public/favicon.ico | Bin 15406 -> 0 bytes frontend/src/components/app.rs | 33 - frontend/src/components/homepage.rs | 14 - frontend/src/components/mod.rs | 25 - frontend/src/components/register.rs | 115 - frontend/src/lib.rs | 9 - frontend/src/main.rs | 38 - frontend/style/main.scss | 5 - justfile | 153 +- migrations/.gitkeep | 0 rust-toolchain.toml | 4 - scripts/init_db | 4 +- src/config/application.rs | 9 + src/config/database.rs | 43 + src/config/environment.rs | 38 + src/config/mod.rs | 53 + src/domain/mod.rs | 24 + src/errors/mod.rs | 0 src/lib.rs | 9 + src/main.rs | 15 + src/middleware/mod.rs | 1 + {backend/src => src/middleware}/telemetry.rs | 16 + src/models/mod.rs | 20 + src/repositories/mod.rs | 25 + {backend/src => src}/routes/health_check.rs | 0 {backend/src => src}/routes/mod.rs | 40 +- src/services/mod.rs | 25 + src/startup.rs | 69 + tests/api/health_check.rs | 17 + tests/api/helpers.rs | 79 + tests/api/main.rs | 2 + 70 files changed, 651 insertions(+), 5998 deletions(-) create mode 100644 .dockerfile create mode 100644 .env.example delete mode 100644 Cargo.lock create mode 100644 Dockerfile create mode 100644 README.md delete mode 100644 backend/Cargo.toml delete mode 100644 backend/migrations/20250125123853_init.down.sql delete mode 100644 backend/migrations/20250125123853_init.up.sql delete mode 100644 backend/src/config.rs delete mode 100644 backend/src/db/mod.rs delete mode 100644 backend/src/db/users.rs delete mode 100644 backend/src/domain/mod.rs delete mode 100644 backend/src/domain/user/error.rs delete mode 100644 backend/src/domain/user/mod.rs delete mode 100644 backend/src/domain/user/new_user.rs delete mode 100644 backend/src/domain/user/user_code.rs delete mode 100644 backend/src/domain/user/username.rs delete mode 100644 backend/src/error/app.rs delete mode 100644 backend/src/error/mod.rs delete mode 100644 backend/src/lib.rs delete mode 100644 backend/src/main.rs delete mode 100644 backend/src/routes/api/mod.rs delete mode 100644 backend/src/routes/api/v1/auth.rs delete mode 100644 backend/src/routes/api/v1/mod.rs delete mode 100644 backend/src/server.rs delete mode 100644 backend/src/startup.rs rename {backend/config => config}/base.toml (74%) rename {backend/config => config}/local.toml (100%) rename {backend/config => config}/production.toml (100%) delete mode 100644 frontend/.gitignore delete mode 100644 frontend/Cargo.toml delete mode 100644 frontend/README.md delete mode 100644 frontend/end2end/.gitignore delete mode 100644 frontend/end2end/package-lock.json delete mode 100644 frontend/end2end/package.json delete mode 100644 frontend/end2end/playwright.config.ts delete mode 100644 frontend/end2end/tests/example.spec.ts delete mode 100644 frontend/end2end/tsconfig.json delete mode 100644 frontend/public/favicon.ico delete mode 100644 frontend/src/components/app.rs delete mode 100644 frontend/src/components/homepage.rs delete mode 100644 frontend/src/components/mod.rs delete mode 100644 frontend/src/components/register.rs delete mode 100644 frontend/src/lib.rs delete mode 100644 frontend/src/main.rs delete mode 100644 frontend/style/main.scss create mode 100644 migrations/.gitkeep delete mode 100644 rust-toolchain.toml mode change 100755 => 100644 scripts/init_db create mode 100644 src/config/application.rs create mode 100644 src/config/database.rs create mode 100644 src/config/environment.rs create mode 100644 src/config/mod.rs create mode 100644 src/domain/mod.rs create mode 100644 src/errors/mod.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/middleware/mod.rs rename {backend/src => src/middleware}/telemetry.rs (57%) create mode 100644 src/models/mod.rs create mode 100644 src/repositories/mod.rs rename {backend/src => src}/routes/health_check.rs (100%) rename {backend/src => src}/routes/mod.rs (55%) create mode 100644 src/services/mod.rs create mode 100644 src/startup.rs create mode 100644 tests/api/health_check.rs create mode 100644 tests/api/helpers.rs create mode 100644 tests/api/main.rs diff --git a/.dockerfile b/.dockerfile new file mode 100644 index 0000000..838ea02 --- /dev/null +++ b/.dockerfile @@ -0,0 +1,8 @@ +.env +target/ +tests/ +Dockerfile +README.md +LICENSE +scripts/ +migrations/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ef5a4ec --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgres://postgres:password@localhost:5432/echoes-of-ascension diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 7f724b3..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,4340 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "any_spawner" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41058deaa38c9d9dd933d6d238d825227cffa668e2839b52879f6619c63eee3b" -dependencies = [ - "futures", - "thiserror 2.0.11", - "tokio", - "wasm-bindgen-futures", -] - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - -[[package]] -name = "async-compression" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" -dependencies = [ - "brotli", - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "zstd", - "zstd-safe", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-trait" -version = "0.1.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "attribute-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" -dependencies = [ - "attribute-derive-macro", - "derive-where", - "manyhow", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "attribute-derive-macro" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" -dependencies = [ - "collection_literals", - "interpolator", - "manyhow", - "proc-macro-utils", - "proc-macro2", - "quote", - "quote-use", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "multer", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" -dependencies = [ - "axum-core 0.5.0", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit 0.8.4", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "backend" -version = "0.1.0" -dependencies = [ - "anyhow", - "argon2", - "axum 0.8.1", - "chrono", - "config 0.15.6", - "hex", - "password-hash", - "rand", - "secrecy", - "serde", - "serde-aux", - "serde_json", - "sqlx", - "thiserror 2.0.11", - "tokio", - "tower", - "tower-http", - "tracing", - "tracing-bunyan-formatter", - "tracing-log 0.2.0", - "tracing-subscriber", - "unicode-segmentation", - "uuid", - "validator", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bitflags" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" -dependencies = [ - "serde", -] - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" - -[[package]] -name = "cc" -version = "1.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "codee" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "collection_literals" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "config" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" -dependencies = [ - "convert_case", - "nom", - "pathdiff", - "serde", - "toml", -] - -[[package]] -name = "config" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e329294a796e9b22329669c1f433a746983f9e324e07f4ef135be81bb2262de4" -dependencies = [ - "pathdiff", - "serde", - "toml", - "winnow", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_format" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "const_str_slice_concat" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive-where" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "drain_filter_polyfill" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -dependencies = [ - "serde", -] - -[[package]] -name = "either_of" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2dc0006c5cf511f802ddcffc0a6df9dcc1912f5f0e448f6641b3b035f14f43d" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "flate2" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "frontend" -version = "0.1.0" -dependencies = [ - "axum 0.7.9", - "console_error_panic_hook", - "leptos", - "leptos_axum", - "leptos_meta", - "leptos_router", - "reqwest", - "serde", - "thiserror 2.0.11", - "tokio", - "wasm-bindgen", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "gloo-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils", - "http", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "guardian" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "493913a18c0d7bebb75127a26a432162c59edbe06f6cf712001e3e769345e8b5" - -[[package]] -name = "h2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.2", -] - -[[package]] -name = "hdrhistogram" -version = "7.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" -dependencies = [ - "byteorder", - "num-traits", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "html-escape" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" -dependencies = [ - "utf8-width", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - -[[package]] -name = "httparse" -version = "1.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hydration_context" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35485b3dcbf7e044b8f28c73f04f13e7b509c2466fd10cb2a8a447e38f8a93a" -dependencies = [ - "futures", - "js-sys", - "once_cell", - "or_poisoned", - "pin-project-lite", - "serde", - "throw_error", - "wasm-bindgen", -] - -[[package]] -name = "hyper" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", -] - -[[package]] -name = "interpolator" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" - -[[package]] -name = "inventory" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b31349d02fe60f80bbbab1a9402364cad7460626d6030494b08ac4a2075bf81" -dependencies = [ - "rustversion", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0f0a572e8ffe56e2ff4f769f32ffe919282c3916799f8b68688b6030063bea" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "leptos" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21c31c9d022c77702c53e02830d08b28320aca9c0899a19c443096c114623fa5" -dependencies = [ - "any_spawner", - "base64", - "cfg-if", - "either_of", - "futures", - "getrandom", - "hydration_context", - "leptos_config", - "leptos_dom", - "leptos_hot_reload", - "leptos_macro", - "leptos_server", - "oco_ref", - "or_poisoned", - "paste", - "rand", - "reactive_graph", - "rustc-hash", - "send_wrapper", - "serde", - "serde_qs", - "server_fn", - "slotmap", - "tachys", - "thiserror 2.0.11", - "throw_error", - "typed-builder", - "typed-builder-macro", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "leptos_axum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b613d5784037baee42a11d21bc263adfc1a55e416556a3d5bfe39c7b87fadf" -dependencies = [ - "any_spawner", - "axum 0.7.9", - "dashmap", - "futures", - "hydration_context", - "leptos", - "leptos_integration_utils", - "leptos_macro", - "leptos_meta", - "leptos_router", - "once_cell", - "parking_lot", - "server_fn", - "tokio", - "tower", - "tower-http", -] - -[[package]] -name = "leptos_config" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d874993c7664d757677d056c8f46b5cb5365fe622005e1bf26050f4996e7e52" -dependencies = [ - "config 0.14.1", - "regex", - "serde", - "thiserror 2.0.11", - "typed-builder", -] - -[[package]] -name = "leptos_dom" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a462aaeec85bc4ecfb26bf324437b92690bf3add1e30eb29b3acc08b20e8b4cb" -dependencies = [ - "js-sys", - "or_poisoned", - "reactive_graph", - "send_wrapper", - "tachys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "leptos_hot_reload" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07eb295ad2f3b2af190da62af339b84fd01ce3c71702f09eb69a57310fcf0c6d" -dependencies = [ - "anyhow", - "camino", - "indexmap", - "parking_lot", - "proc-macro2", - "quote", - "rstml", - "serde", - "syn", - "walkdir", -] - -[[package]] -name = "leptos_integration_utils" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8652fcd7a1744f85403b95c5520143f3b962d640c8450b8514f9530fb5c4b76" -dependencies = [ - "futures", - "hydration_context", - "leptos", - "leptos_config", - "leptos_meta", - "leptos_router", - "reactive_graph", -] - -[[package]] -name = "leptos_macro" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90291b25ee576bc9c299d3371cc8f09bf60ea939a8de61fa8b744650aff76e24" -dependencies = [ - "attribute-derive", - "cfg-if", - "convert_case", - "html-escape", - "itertools", - "leptos_hot_reload", - "prettyplease", - "proc-macro-error2", - "proc-macro2", - "quote", - "rstml", - "server_fn_macro", - "syn", - "uuid", -] - -[[package]] -name = "leptos_meta" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7250991b2077ef5869e999c74cf4990926a3c3919b50c9937e101c1c874102db" -dependencies = [ - "futures", - "indexmap", - "leptos", - "once_cell", - "or_poisoned", - "send_wrapper", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "leptos_router" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a193dbd62b9617a5d7d199ea70c570da01a1bbe798e617373b6351845be6778" -dependencies = [ - "any_spawner", - "either_of", - "futures", - "gloo-net", - "js-sys", - "leptos", - "leptos_router_macro", - "once_cell", - "or_poisoned", - "percent-encoding", - "reactive_graph", - "send_wrapper", - "tachys", - "thiserror 2.0.11", - "url", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "leptos_router_macro" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34bc3f80ad810b22058f12d278bb0bf929779cc0bc1289a06980d896f62743f0" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", -] - -[[package]] -name = "leptos_server" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18caffe32c245ddb35697edd898ccb3393efce67672a707a14eebd0db2e8249a" -dependencies = [ - "any_spawner", - "base64", - "codee", - "futures", - "hydration_context", - "or_poisoned", - "reactive_graph", - "send_wrapper", - "serde", - "serde_json", - "server_fn", - "tachys", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linear-map" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" - -[[package]] -name = "manyhow" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" -dependencies = [ - "manyhow-macros", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "manyhow-macros" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" -dependencies = [ - "proc-macro-utils", - "proc-macro2", - "quote", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "native-tls" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "next_tuple" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "oco_ref" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b94982fe39a861561cf67ff17a7849f2cedadbbad960a797634032b7abb998" -dependencies = [ - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "openssl" -version = "0.10.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "or_poisoned" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro-utils" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" -dependencies = [ - "proc-macro2", - "quote", - "smallvec", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "quote-use" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" -dependencies = [ - "quote", - "quote-use-macros", -] - -[[package]] -name = "quote-use-macros" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" -dependencies = [ - "proc-macro-utils", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "reactive_graph" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbf210c04505e128fb7f64acecc23c71f82f56c7d481b190e1010b7bada2cb9" -dependencies = [ - "any_spawner", - "async-lock", - "futures", - "guardian", - "hydration_context", - "or_poisoned", - "pin-project-lite", - "rustc-hash", - "send_wrapper", - "serde", - "slotmap", - "thiserror 2.0.11", - "web-sys", -] - -[[package]] -name = "reactive_stores" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bb1913eeb71f74028213455ee971550c2b3cb91b6acd5efa8a0f8dc59f5039" -dependencies = [ - "guardian", - "itertools", - "or_poisoned", - "paste", - "reactive_graph", - "reactive_stores_macro", - "rustc-hash", -] - -[[package]] -name = "reactive_stores_macro" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86e4f08f361b05d11422398cef4bc4cf356f2fdd2f06a96646b0e9cd902226" -dependencies = [ - "convert_case", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rstml" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" -dependencies = [ - "derive-where", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", - "syn_derive", - "thiserror 2.0.11", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secrecy" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" -dependencies = [ - "serde", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -dependencies = [ - "futures-core", -] - -[[package]] -name = "serde" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-aux" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" -dependencies = [ - "chrono", - "serde", - "serde_json", -] - -[[package]] -name = "serde_derive" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_qs" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "server_fn" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5dd7fcccd3ef2081da086c1f8595b506627abbbbc9f64be0141d2251219570e" -dependencies = [ - "axum 0.7.9", - "bytes", - "const_format", - "dashmap", - "futures", - "gloo-net", - "http", - "http-body-util", - "hyper", - "inventory", - "js-sys", - "once_cell", - "pin-project-lite", - "send_wrapper", - "serde", - "serde_json", - "serde_qs", - "server_fn_macro_default", - "thiserror 2.0.11", - "throw_error", - "tower", - "tower-layer", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "xxhash-rust", -] - -[[package]] -name = "server_fn_macro" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0bbac4f01a714b0490247ac625bdb7055548210556c39e8f56a2dbbe3abc70b" -dependencies = [ - "const_format", - "convert_case", - "proc-macro2", - "quote", - "syn", - "xxhash-rust", -] - -[[package]] -name = "server_fn_macro_default" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07dfd1744a5f5612f00f69fe035b0bfafdf12bb46d76e785673078a9e56b170" -dependencies = [ - "server_fn_macro", - "syn", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" -dependencies = [ - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.2", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.11", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "tempfile", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.11", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.11", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn_derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tachys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d777e4426a597296b020edcb5c3d8f25a3ccd8adfd22eb5154ac81da946aef9f" -dependencies = [ - "any_spawner", - "const_str_slice_concat", - "drain_filter_polyfill", - "either_of", - "futures", - "html-escape", - "indexmap", - "itertools", - "js-sys", - "linear-map", - "next_tuple", - "oco_ref", - "once_cell", - "or_poisoned", - "parking_lot", - "paste", - "reactive_graph", - "reactive_stores", - "rustc-hash", - "send_wrapper", - "slotmap", - "throw_error", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "tempfile" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "throw_error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ef8bf264c6ae02a065a4a16553283f0656bd6266fc1fcb09fd2e6b5e91427b" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "hdrhistogram", - "indexmap", - "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" -dependencies = [ - "async-compression", - "base64", - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "http-range-header", - "httpdate", - "iri-string", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", - "uuid", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[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.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-bunyan-formatter" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d637245a0d8774bd48df6482e086c59a8b5348a910c3b0579354045a9d82411" -dependencies = [ - "ahash", - "log", - "serde", - "serde_json", - "time", - "tracing", - "tracing-core", - "tracing-log 0.1.4", - "tracing-subscriber", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log 0.2.0", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typed-builder" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" -dependencies = [ - "typed-builder-macro", -] - -[[package]] -name = "typed-builder-macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "validator" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" -dependencies = [ - "idna", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "whoami" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" -dependencies = [ - "redox_syscall", - "wasite", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[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", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zstd" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 22bdd56..242c290 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,60 @@ -[workspace] -members = ["frontend", "backend"] -resolver = "2" +[package] +name = "echoes-of-ascension" +version = "0.1.0" +edition = "2021" +authors = ["Kristofers Solo "] -[workspace.dependencies] -cfg-if = "1" -http = "1" -thiserror = "2.0" -wasm-bindgen = "=0.2.100" -tokio = { version = "1.43", features = ["rt", "macros", "tracing"] } +[lib] +path = "src/lib.rs" + +[[bin]] +path = "src/main.rs" +name = "echoes-of-ascension" + +[dependencies] +axum = "0.8" +chrono = { version = "0.4", features = ["serde", "clock"] } +config = { version = "0.15", features = ["toml"], default-features = false } serde = { version = "1", features = ["derive"] } -tower = { version = "0.5", features = ["full"] } -tower-http = { version = "0.6", features = ["full"] } -uuid = { version = "1.12", features = ["v4", "serde"] } +sqlx = { version = "0.8", default-features = false, features = [ + "runtime-tokio", + "tls-rustls", + "macros", + "postgres", + "uuid", + "chrono", + "migrate", +] } +tokio = { version = "1.39", features = [ + "rt", + "macros", + "tracing", + "rt-multi-thread", +] } +uuid = { version = "1.8", features = ["v4", "serde"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } +tower-http = { version = "0.6", features = ["trace"] } +tracing-bunyan-formatter = "0.3" +tracing-log = "0.2" +secrecy = { version = "0.10", features = ["serde"] } +serde-aux = "4" +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", +] } +askama = { version = "0.12", features = ["with-axum"] } -[profile.release] -opt-level = "z" -lto = true -codegen-units = 1 -[workspace.lints.clippy] +[dev-dependencies] +once_cell = "1.19" +fake = "3.1" +quickcheck = "1.0" +quickcheck_macros = "1.0" +wiremock = "0.6" +serde_json = "1" + +[lints.clippy] +pedantic = "warn" nursery = "warn" unwrap_used = "warn" -style = "warn" -perf = "warn" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..42cf8b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1.77.0 AS chef +WORKDIR /app +COPY . . +RUN apt-get update && apt-get install lld clang -y + +FROM chef as planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json +COPY . . +ENV SQLX_OFFLINE true +RUN cargo build --release + +FROM debian:bookworm-slim AS runtime +WORKDIR /app +# openssl - it is dynamically linked by some dependencies +# ca-certificates - it is needed to verify TLS certificates when establishing HTTPS connections +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends openssl ca-certificates \ + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/target/release/echoes-of-ascension echoes-of-ascension +COPY config config +ENV APP_ENVIRONMENT production +ENTRYPOINT ["./echoes-of-ascension"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..384576c --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Axum Template + +This repository contains templates for bootstrapping a Rust Web application with Axum. + +## Getting Started + +1. Install [`cargo-generate`](https://github.com/cargo-generate/cargo-generate#installation) + + ```shell + cargo install cargo-generate + ``` + +2. Create a new app based on this repository: + + ```shell + cargo generate kristoferssolo/axum-template + ``` diff --git a/backend/Cargo.toml b/backend/Cargo.toml deleted file mode 100644 index daf3143..0000000 --- a/backend/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "backend" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -thiserror.workspace = true -axum = "0.8" -tokio = { workspace = true, features = ["rt-multi-thread"] } -tower.workspace = true -tower-http = { workspace = true, features = ["cors"] } -serde.workspace = true -serde_json = "1" -uuid.workspace = true -sqlx = { version = "0.8", features = [ - "runtime-tokio", - "macros", - "postgres", - "uuid", - "chrono", - "migrate", -] } -chrono = { version = "0.4", features = ["serde", "clock"] } -secrecy = { version = "0.10", features = ["serde"] } -validator = "0.20" -config = { version = "0.15", features = ["toml"], default-features = false } -serde-aux = "4" -unicode-segmentation = "1" -rand = "0.8" -argon2 = "0.5" -password-hash = "0.5" -hex = "0.4" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } -tracing-bunyan-formatter = { version = "0.3", default-features = false } -tracing-log = "0.2" -anyhow = "1" - -[lints] -workspace = true diff --git a/backend/migrations/20250125123853_init.down.sql b/backend/migrations/20250125123853_init.down.sql deleted file mode 100644 index 679391c..0000000 --- a/backend/migrations/20250125123853_init.down.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Add down migration script here --- Drop indexes first -DROP INDEX IF EXISTS idx_scores_user_score; - -DROP INDEX IF EXISTS idx_users_login; - --- Drop tables in reverse order of creation -DROP TABLE IF EXISTS scores; - -DROP TABLE IF EXISTS users; - diff --git a/backend/migrations/20250125123853_init.up.sql b/backend/migrations/20250125123853_init.up.sql deleted file mode 100644 index a82fe10..0000000 --- a/backend/migrations/20250125123853_init.up.sql +++ /dev/null @@ -1,28 +0,0 @@ --- Add up migration script here --- Enable UUID support -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- Users table with login codes -CREATE TABLE IF NOT EXISTS "user" ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid (), - username varchar(255) NOT NULL UNIQUE, - code varchar(255) NOT NULL UNIQUE, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- Scores table with detailed game stats -CREATE TABLE IF NOT EXISTS score ( - id bigserial PRIMARY KEY, - user_id uuid NOT NULL, - score integer NOT NULL, - floor_reached integer NOT NULL, - play_time_seconds integer NOT NULL, - created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES "user" (id) -); - --- Indexes for performance -CREATE INDEX idx_user_login ON "user" (code); - -CREATE INDEX idx_scores_user_score ON score (user_id, score DESC); - diff --git a/backend/src/config.rs b/backend/src/config.rs deleted file mode 100644 index 4287f05..0000000 --- a/backend/src/config.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use secrecy::{ExposeSecret, SecretString}; -use serde::Deserialize; -use serde_aux::field_attributes::deserialize_number_from_string; -use sqlx::{ - postgres::{PgConnectOptions, PgSslMode}, - ConnectOptions, -}; - -#[derive(Debug, Deserialize, Clone)] -pub struct Settings { - pub database: DatabaseSettings, - pub application: ApplicationSettings, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct DatabaseSettings { - pub username: String, - pub password: SecretString, - #[serde(deserialize_with = "deserialize_number_from_string")] - pub port: u16, - pub host: String, - pub database_name: String, - pub require_ssl: bool, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct ApplicationSettings { - #[serde(deserialize_with = "deserialize_number_from_string")] - pub port: u16, - pub host: String, -} - -#[derive(Debug, Clone)] -pub enum Environment { - Local, - Production, -} - -impl DatabaseSettings { - #[must_use] - pub fn without_db(&self) -> PgConnectOptions { - let ssl_mode = if self.require_ssl { - PgSslMode::Require - } else { - PgSslMode::Prefer - }; - - PgConnectOptions::new() - .host(&self.host) - .username(&self.username) - .password(self.password.expose_secret()) - .port(self.port) - .ssl_mode(ssl_mode) - } - - #[must_use] - pub fn with_db(&self) -> PgConnectOptions { - self.without_db() - .database(&self.database_name) - .log_statements(tracing_log::log::LevelFilter::Trace) - } -} - -pub fn get_config() -> Result { - let base_path = std::env::current_dir().expect("Failed to determine current directory"); - let config_directory = base_path.join("backend").join("config"); - let env = std::env::var("APP_ENVIRONMENT") - .unwrap_or_else(|_| "local".into()) - .parse() - .unwrap_or(Environment::Local); - - let env_filename = format!("{}.toml", &env); - - let settings = config::Config::builder() - .add_source(config::File::from(config_directory.join("base.toml"))) - .add_source(config::File::from(config_directory.join(env_filename))) - .add_source( - config::Environment::with_prefix("APP") - .prefix_separator("_") - .separator("__"), - ) - .build()?; - settings.try_deserialize::() -} - -impl Display for Environment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Local => write!(f, "local"), - Self::Production => write!(f, "production"), - } - } -} - -impl TryFrom for Environment { - type Error = String; - fn try_from(value: String) -> Result { - Self::from_str(&value) - } -} - -impl FromStr for Environment { - type Err = String; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "local" => Ok(Self::Local), - "production" => Ok(Self::Production), - other => Err(format!( - "{other} is not supported environment. \ - Use either `local` or `production`." - )), - } - } -} diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs deleted file mode 100644 index 913bd46..0000000 --- a/backend/src/db/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod users; diff --git a/backend/src/db/users.rs b/backend/src/db/users.rs deleted file mode 100644 index a2f9b1e..0000000 --- a/backend/src/db/users.rs +++ /dev/null @@ -1,36 +0,0 @@ -use sqlx::PgPool; -use thiserror::Error; - -use crate::domain::user::{error::UserError, new_user::NewUser}; - -#[derive(Debug, Error)] -pub enum ServerUserError { - #[error("Database error: {0}")] - Database(#[from] sqlx::Error), - #[error("Database error: {0}")] - User(#[from] UserError), -} - -#[tracing::instrument(name = "Saving new user details in the database", skip(pool, new_user))] -pub async fn insert_user(pool: &PgPool, new_user: &NewUser) -> Result<(), ServerUserError> { - sqlx::query!( - r#" - INSERT INTO "user" (username, code) - VALUES ($1, $2) - "#, - new_user.username.as_ref(), - new_user.code.hash()? - ) - .execute(pool) - .await - .map_err(|e| { - tracing::error!("Failed to execute query: {:?}", e); - match e { - sqlx::Error::Database(ref dbe) if dbe.constraint() == Some("user_username_key") => { - ServerUserError::User(UserError::UsernameTaken(new_user.username.to_string())) - } - _ => ServerUserError::Database(e), - } - })?; - Ok(()) -} diff --git a/backend/src/domain/mod.rs b/backend/src/domain/mod.rs deleted file mode 100644 index 22d12a3..0000000 --- a/backend/src/domain/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod user; diff --git a/backend/src/domain/user/error.rs b/backend/src/domain/user/error.rs deleted file mode 100644 index 90d9325..0000000 --- a/backend/src/domain/user/error.rs +++ /dev/null @@ -1,22 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum UserError { - #[error("Username validation failed: {0}")] - UsernameValidation(String), - - #[error("Code hashing failed: {0}")] - HashingError(String), - - #[error("Username already taken: {0}")] - UsernameTaken(String), - - #[error("Invalid code format")] - InvalidCode, - - #[error("Authentication failed")] - AuthenticationFailed, - - #[error("Internal server error: {0}")] - Internal(String), -} diff --git a/backend/src/domain/user/mod.rs b/backend/src/domain/user/mod.rs deleted file mode 100644 index 6268c80..0000000 --- a/backend/src/domain/user/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod error; -pub mod new_user; -mod user_code; -mod username; diff --git a/backend/src/domain/user/new_user.rs b/backend/src/domain/user/new_user.rs deleted file mode 100644 index fe70c35..0000000 --- a/backend/src/domain/user/new_user.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::{user_code::UserCode, username::Username}; - -#[derive(Debug, Clone, Default)] -pub struct NewUser { - pub username: Username, - pub code: UserCode, -} diff --git a/backend/src/domain/user/user_code.rs b/backend/src/domain/user/user_code.rs deleted file mode 100644 index ce35f46..0000000 --- a/backend/src/domain/user/user_code.rs +++ /dev/null @@ -1,73 +0,0 @@ -use argon2::Argon2; -use password_hash::SaltString; -use std::ops::Deref; - -use rand::{rngs::OsRng, thread_rng, Rng}; -use secrecy::{ExposeSecret, SecretString}; - -use super::error::UserError; - -#[derive(Debug, Clone)] -pub struct UserCode(SecretString); - -impl UserCode { - pub fn hash(&self) -> Result { - let salt = SaltString::generate(&mut OsRng); - let argon2 = Argon2::default(); - - let mut output_key_material = [0u8; 32]; - argon2 - .hash_password_into( - self.expose_secret().as_bytes(), - salt.as_str().as_bytes(), - &mut output_key_material, - ) - .map_err(|e| UserError::HashingError(e.to_string()))?; - Ok(format!( - "{}${}", - salt.as_str(), - hex::encode(output_key_material) - )) - } - - pub fn verify(stored: &str, code: &str) -> Result { - let argon2 = Argon2::default(); - - // Split stored value into salt and hash - let parts: Vec<&str> = stored.split('$').collect(); - if parts.len() != 2 { - return Err(UserError::HashingError("Invalid hash format".to_string())); - } - - let salt = parts[0]; - let stored_hash = - hex::decode(parts[1]).map_err(|e| UserError::HashingError(e.to_string()))?; - - let mut output = [0u8; 32]; - argon2 - .hash_password_into(code.as_bytes(), salt.as_bytes(), &mut output) - .map_err(|e| UserError::HashingError(e.to_string()))?; - - Ok(output.as_slice() == stored_hash.as_slice()) - } -} - -impl Default for UserCode { - fn default() -> Self { - let mut rng = thread_rng(); - - let code = (0..16) - .map(|_| rng.gen_range(0..10).to_string()) - .collect::(); - - Self(code.into()) - } -} - -impl Deref for UserCode { - type Target = SecretString; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/backend/src/domain/user/username.rs b/backend/src/domain/user/username.rs deleted file mode 100644 index c62cc07..0000000 --- a/backend/src/domain/user/username.rs +++ /dev/null @@ -1,73 +0,0 @@ -use rand::{seq::SliceRandom, thread_rng, Rng}; -use std::{fmt::Display, str::FromStr}; -use unicode_segmentation::UnicodeSegmentation; - -use super::error::UserError; - -#[derive(Debug, Clone)] -pub struct Username(String); - -impl TryFrom for Username { - type Error = UserError; - fn try_from(value: String) -> Result { - let is_empty_or_whitespace = value.trim().is_empty(); - let is_too_long = value.graphemes(true).count() > 256; - let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; - let contains_forbidden_characters = - value.chars().any(|c| forbidden_characters.contains(&c)); - if is_empty_or_whitespace || is_too_long || contains_forbidden_characters { - return Err(UserError::UsernameValidation(value)); - } - Ok(Self(value)) - } -} - -impl Default for Username { - fn default() -> Self { - let adjectives = [ - "swift", "bright", "clever", "brave", "mighty", "noble", "wise", "calm", "kind", - "bold", "quick", "sharp", "smart", "keen", "fair", - ]; - - let nouns = [ - "wolf", "eagle", "lion", "hawk", "bear", "tiger", "fox", "owl", "deer", "seal", - "raven", "crane", "dove", "swan", "falcon", - ]; - - let mut rng = thread_rng(); - - let adjective = adjectives.choose(&mut rng).unwrap_or(&"swift"); - let noun = nouns.choose(&mut rng).unwrap_or(&"wolf"); - - let number = rng.gen_range(100..1000); - - let username = format!("{adjective}_{noun}_{number}"); - - Self(username) - } -} - -impl FromStr for Username { - type Err = UserError; - fn from_str(s: &str) -> Result { - Self::try_from(s.to_owned()) - } -} - -impl AsRef for Username { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Display for Username { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for String { - fn from(value: Username) -> Self { - value.0 - } -} diff --git a/backend/src/error/app.rs b/backend/src/error/app.rs deleted file mode 100644 index 6a3eca2..0000000 --- a/backend/src/error/app.rs +++ /dev/null @@ -1,88 +0,0 @@ -use axum::{http::StatusCode, response::IntoResponse, Json}; -use serde::Serialize; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum AppError { - // Authentication/Authorization errors - #[error("Unauthorized")] - Unauthorized, - - #[error("Forbidden")] - Forbidden, - - // Validation errors - #[error("Validation error: {0}")] - Validation(String), - - // Resource errors - #[error("Resource not found: {0}")] - NotFound(String), - - #[error("{resource} already exists: {id}")] - AlreadyExists { resource: &'static str, id: String }, - - // Database errors - #[error("Database error")] - Database(#[from] sqlx::Error), - - // Internal errors - #[error("Internal server error")] - Internal(#[from] anyhow::Error), -} -#[derive(Debug, Serialize)] -pub struct ErrorResponse { - pub error: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub details: Option, -} - -impl IntoResponse for AppError { - fn into_response(self) -> axum::response::Response { - let (status, error_message, details) = match self { - // Auth errors - Self::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_string(), None), - Self::Forbidden => (StatusCode::FORBIDDEN, "Forbidden".to_string(), None), - // Validation errors - Self::Validation(msg) => ( - StatusCode::BAD_REQUEST, - "Validation error".to_string(), - Some(msg), - ), - // Resource errors - Self::NotFound(resource) => ( - StatusCode::NOT_FOUND, - "Resource not found".to_string(), - Some(resource), - ), - Self::AlreadyExists { resource, id } => ( - StatusCode::CONFLICT, - format!("{resource} already exists"), - Some(id), - ), - // Database/Internal errors - Self::Database(e) => { - tracing::error!("Database error: {:?}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Internal server error".to_string(), - None, - ) - } - Self::Internal(e) => { - tracing::error!("Internal error: {:?}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Internal server error".to_string(), - None, - ) - } - }; - - let body = Json(ErrorResponse { - error: error_message, - details, - }); - (status, body).into_response() - } -} diff --git a/backend/src/error/mod.rs b/backend/src/error/mod.rs deleted file mode 100644 index 309be62..0000000 --- a/backend/src/error/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod app; diff --git a/backend/src/lib.rs b/backend/src/lib.rs deleted file mode 100644 index 6749d30..0000000 --- a/backend/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod config; -pub mod db; -pub mod domain; -pub mod error; -pub mod routes; -pub mod server; -pub mod startup; -pub mod telemetry; diff --git a/backend/src/main.rs b/backend/src/main.rs deleted file mode 100644 index acf5e7f..0000000 --- a/backend/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -use backend::{ - config::get_config, - server::Server, - telemetry::{get_subscriber, init_subscriber}, -}; - -#[tokio::main] -async fn main() -> Result<(), std::io::Error> { - // Generate the list of routes in your Leptos App - let subscriber = get_subscriber("echoes-of-ascension-server", "info", std::io::stdout); - init_subscriber(subscriber); - - let config = get_config().expect("Failed to read configuation."); - // - let application = Server::build(config).await?; - application.run_until_stopped().await?; - Ok(()) -} diff --git a/backend/src/routes/api/mod.rs b/backend/src/routes/api/mod.rs deleted file mode 100644 index 9b18325..0000000 --- a/backend/src/routes/api/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod v1; - -use axum::Router; - -use crate::startup::AppState; - -pub fn routes() -> Router { - Router::new().nest("/v1", v1::routes()) -} diff --git a/backend/src/routes/api/v1/auth.rs b/backend/src/routes/api/v1/auth.rs deleted file mode 100644 index 43c492f..0000000 --- a/backend/src/routes/api/v1/auth.rs +++ /dev/null @@ -1,72 +0,0 @@ -use anyhow::anyhow; -use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; -use secrecy::ExposeSecret; -use serde::{Deserialize, Serialize}; - -use crate::{ - db::users::{insert_user, ServerUserError}, - domain::user::{error::UserError, new_user::NewUser}, - error::app::AppError, - startup::AppState, -}; - -#[derive(Debug, Deserialize)] -pub struct FormData { - pub username: String, -} - -#[derive(Debug, Serialize)] -pub struct Response { - pub username: String, - pub code: String, -} - -#[tracing::instrument( - name = "Creating new user", - skip(state, payload), - fields( - username= %payload.username, - ) -)] -pub async fn register( - State(state): State, - Json(payload): Json, -) -> Result { - let new_user = payload - .try_into() - .map_err(|e: UserError| AppError::Validation(e.to_string()))?; - - match insert_user(&state.pool, &new_user).await { - Ok(()) => Ok((StatusCode::CREATED, Json(Response::from(new_user)))), - Err(ServerUserError::User(UserError::UsernameTaken(username))) => { - Err(AppError::AlreadyExists { - resource: "User", - id: username, - }) - } - Err(e) => { - tracing::error!("Failed to register user: {}", e); - Err(AppError::Internal(anyhow!(e))) - } - } -} - -impl TryFrom for NewUser { - type Error = UserError; - fn try_from(value: FormData) -> Result { - let username = value.username.try_into()?; - Ok(Self { - username, - ..Default::default() - }) - } -} - -impl From for Response { - fn from(value: NewUser) -> Self { - Self { - username: value.username.into(), - code: value.code.expose_secret().into(), - } - } -} diff --git a/backend/src/routes/api/v1/mod.rs b/backend/src/routes/api/v1/mod.rs deleted file mode 100644 index b4d8e9c..0000000 --- a/backend/src/routes/api/v1/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod auth; - -use axum::{routing::post, Router}; - -use crate::startup::AppState; - -pub fn routes() -> Router { - Router::new().route("/register", post(auth::register)) -} diff --git a/backend/src/server.rs b/backend/src/server.rs deleted file mode 100644 index 00068ca..0000000 --- a/backend/src/server.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tokio::{net::TcpListener, task::JoinHandle}; - -use crate::{ - config::Settings, - routes::route, - startup::{get_connection_pool, App}, -}; - -#[derive(Debug)] -pub struct Server { - port: u16, - server: JoinHandle>, -} - -impl Server { - pub async fn build(config: Settings) -> Result { - let pool = get_connection_pool(&config.database); - - // Use application's address configuration - let addr = format!("{}:{}", config.application.host, config.application.port); - let listener = TcpListener::bind(addr).await?; - let port = listener.local_addr()?.port(); - let app_state = App { pool }.into(); - let server = tokio::spawn(async move { axum::serve(listener, route(app_state)).await }); - - Ok(Self { port, server }) - } - - #[must_use] - #[inline] - pub const fn port(&self) -> u16 { - self.port - } - - pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { - self.server.await? - } -} diff --git a/backend/src/startup.rs b/backend/src/startup.rs deleted file mode 100644 index a11cc9f..0000000 --- a/backend/src/startup.rs +++ /dev/null @@ -1,17 +0,0 @@ -use sqlx::{postgres::PgPoolOptions, PgPool}; -use std::sync::Arc; - -use crate::config::DatabaseSettings; - -pub type AppState = Arc; - -#[derive(Debug)] -pub struct App { - pub pool: PgPool, -} - -#[must_use] -#[inline] -pub fn get_connection_pool(config: &DatabaseSettings) -> PgPool { - PgPoolOptions::new().connect_lazy_with(config.with_db()) -} diff --git a/backend/config/base.toml b/config/base.toml similarity index 74% rename from backend/config/base.toml rename to config/base.toml index b7d2e68..5632cdd 100644 --- a/backend/config/base.toml +++ b/config/base.toml @@ -6,4 +6,4 @@ host = "127.0.0.1" port = 5432 username = "postgres" password = "password" -database_name = "maze_ascension" +database_name = "echoes-of-ascension" diff --git a/backend/config/local.toml b/config/local.toml similarity index 100% rename from backend/config/local.toml rename to config/local.toml diff --git a/backend/config/production.toml b/config/production.toml similarity index 100% rename from backend/config/production.toml rename to config/production.toml diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 6985cf1..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml deleted file mode 100644 index efac00b..0000000 --- a/frontend/Cargo.toml +++ /dev/null @@ -1,105 +0,0 @@ -[package] -name = "frontend" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -leptos = { version = "0.7", features = ["nightly"] } -leptos_router = { version = "0.7", features = ["nightly"] } -axum = { version = "0.7", optional = true } -console_error_panic_hook = { version = "0.1", optional = true } -leptos_axum = { version = "0.7", optional = true } -leptos_meta = { version = "0.7" } -tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } -wasm-bindgen = { version = "=0.2.100", optional = true } -reqwest = { version = "0.12", features = ["json"] } -serde.workspace = true -thiserror.workspace = true - - -[features] -hydrate = ["leptos/hydrate", "dep:console_error_panic_hook", "dep:wasm-bindgen"] -ssr = [ - "dep:axum", - "dep:tokio", - "dep:leptos_axum", - "leptos/ssr", - "leptos_meta/ssr", - "leptos_router/ssr", -] - -# Defines a size-optimized profile for the WASM bundle in release mode -[profile.wasm-release] -inherits = "release" -opt-level = "z" -lto = true -codegen-units = 1 -panic = "abort" - -[package.metadata.leptos] -# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name -output-name = "frontend" - -# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. -site-root = "target/site" - -# The site-root relative folder where all compiled output (JS, WASM and CSS) is written -# Defaults to pkg -site-pkg-dir = "pkg" - -# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css -style-file = "style/main.scss" -# Assets source dir. All files found here will be copied and synchronized to site-root. -# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir. -# -# Optional. Env: LEPTOS_ASSETS_DIR. -assets-dir = "public" - -# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. -site-addr = "127.0.0.1:3000" - -# The port to use for automatic reload monitoring -reload-port = 3001 - -# [Optional] Command to use when running end2end tests. It will run in the end2end dir. -# [Windows] for non-WSL use "npx.cmd playwright test" -# This binary name can be checked in Powershell with Get-Command npx -end2end-cmd = "npx playwright test" -end2end-dir = "end2end" - -# The browserlist query used for optimizing the CSS. -browserquery = "defaults" - -# The environment Leptos will run in, usually either "DEV" or "PROD" -env = "DEV" - -# The features to use when compiling the bin target -# -# Optional. Can be over-ridden with the command line parameter --bin-features -bin-features = ["ssr"] - -# If the --no-default-features flag should be used when compiling the bin target -# -# Optional. Defaults to false. -bin-default-features = false - -# The features to use when compiling the lib target -# -# Optional. Can be over-ridden with the command line parameter --lib-features -lib-features = ["hydrate"] - -# If the --no-default-features flag should be used when compiling the lib target -# -# Optional. Defaults to false. -lib-default-features = false - -# The profile to use for the lib target when compiling for release -# -# Optional. Defaults to "release". -lib-profile-release = "wasm-release" - -[lints] -workspace = true diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 15ad4f4..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,91 +0,0 @@ - - - Leptos Logo - - -# Leptos Axum Starter Template - -This is a template for use with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool using [Axum](https://github.com/tokio-rs/axum). - -## Creating your template repo - -If you don't have `cargo-leptos` installed you can install it with - -```bash -cargo install cargo-leptos --locked -``` - -Then run -```bash -cargo leptos new --git https://github.com/leptos-rs/start-axum-0.7 -``` - -to generate a new project template. - -```bash -cd frontend -``` - -to go to your newly created project. -Feel free to explore the project structure, but the best place to start with your application code is in `src/app.rs`. -Addtionally, Cargo.toml may need updating as new versions of the dependencies are released, especially if things are not working after a `cargo update`. - -## Running your project - -```bash -cargo leptos watch -``` - -## Installing Additional Tools - -By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools. - -1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly -2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly -3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future) -4. `npm install -g sass` - install `dart-sass` (should be optional in future -5. Run `npm install` in end2end subdirectory before test - -## Compiling for Release -```bash -cargo leptos build --release -``` - -Will generate your server binary in target/server/release and your site package in target/site - -## Testing Your Project -```bash -cargo leptos end-to-end -``` - -```bash -cargo leptos end-to-end --release -``` - -Cargo-leptos uses Playwright as the end-to-end test tool. -Tests are located in end2end/tests directory. - -## Executing a Server on a Remote Machine Without the Toolchain -After running a `cargo leptos build --release` the minimum files needed are: - -1. The server binary located in `target/server/release` -2. The `site` directory and all files within located in `target/site` - -Copy these files to your remote server. The directory structure should be: -```text -frontend -site/ -``` -Set the following environment variables (updating for your project as needed): -```sh -export LEPTOS_OUTPUT_NAME="frontend" -export LEPTOS_SITE_ROOT="site" -export LEPTOS_SITE_PKG_DIR="pkg" -export LEPTOS_SITE_ADDR="127.0.0.1:3000" -export LEPTOS_RELOAD_PORT="3001" -``` -Finally, run the server binary. - -## Licensing - -This template itself is released under the Unlicense. You should replace the LICENSE for your own application with an appropriate license if you plan to release it publicly. diff --git a/frontend/end2end/.gitignore b/frontend/end2end/.gitignore deleted file mode 100644 index 169a2af..0000000 --- a/frontend/end2end/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -playwright-report -test-results diff --git a/frontend/end2end/package-lock.json b/frontend/end2end/package-lock.json deleted file mode 100644 index 260e8eb..0000000 --- a/frontend/end2end/package-lock.json +++ /dev/null @@ -1,167 +0,0 @@ -{ - "name": "end2end", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "end2end", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "@playwright/test": "^1.44.1", - "@types/node": "^20.12.12", - "typescript": "^5.4.5" - } - }, - "node_modules/@playwright/test": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", - "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.44.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/playwright": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", - "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.44.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", - "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - } - }, - "dependencies": { - "@playwright/test": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", - "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", - "dev": true, - "requires": { - "playwright": "1.44.1" - } - }, - "@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "playwright": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", - "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", - "dev": true, - "requires": { - "fsevents": "2.3.2", - "playwright-core": "1.44.1" - } - }, - "playwright-core": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", - "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", - "dev": true - }, - "typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true - }, - "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - } - } -} diff --git a/frontend/end2end/package.json b/frontend/end2end/package.json deleted file mode 100644 index a80ac59..0000000 --- a/frontend/end2end/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "end2end", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": {}, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@playwright/test": "^1.44.1", - "@types/node": "^20.12.12", - "typescript": "^5.4.5" - } -} diff --git a/frontend/end2end/playwright.config.ts b/frontend/end2end/playwright.config.ts deleted file mode 100644 index aee2d46..0000000 --- a/frontend/end2end/playwright.config.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { PlaywrightTestConfig } from "@playwright/test"; -import { devices, defineConfig } from "@playwright/test"; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: "./tests", - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: { - ...devices["Desktop Chrome"], - }, - }, - - { - name: "firefox", - use: { - ...devices["Desktop Firefox"], - }, - }, - - { - name: "webkit", - use: { - ...devices["Desktop Safari"], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // port: 3000, - // }, -}); diff --git a/frontend/end2end/tests/example.spec.ts b/frontend/end2end/tests/example.spec.ts deleted file mode 100644 index 0139fc3..0000000 --- a/frontend/end2end/tests/example.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { test, expect } from "@playwright/test"; - -test("homepage has title and heading text", async ({ page }) => { - await page.goto("http://localhost:3000/"); - - await expect(page).toHaveTitle("Welcome to Leptos"); - - await expect(page.locator("h1")).toHaveText("Welcome to Leptos!"); -}); diff --git a/frontend/end2end/tsconfig.json b/frontend/end2end/tsconfig.json deleted file mode 100644 index e075f97..0000000 --- a/frontend/end2end/tsconfig.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index 2ba8527cb12f5f28f331b8d361eef560492d4c77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHOd3aPs5`TblWD*3D%tXPJ#q(n!z$P=3gCjvf#a)E}a;Uf>h{pmVih!a-5LVO` zB?JrzEFicD0wRLo0iPfO372xnkvkzFlRHB)lcTnNZ}KK@US{UKN#b8?e_zkLy1RZ= zT~*y(-6IICgf>E_P6A)M3(wvl2qr-gx_5Ux-_uzT*6_Q&ee1v9B?vzS3&K5IhO2N5 z$9ukLN<`G>$$|GLnga~y%>f}*j%+w@(ixVUb^1_Gjoc;(?TrD3m2)RduFblVN)uy; zQAEd^T{5>-YYH%|Kv{V^cxHMBr1Ik7Frht$imC`rqx@5*| z+OqN!xAjqmaU=qR$uGDMa7p!W9oZ+64($4xDk^FyFQ<_9Z`(;DLnB<;LLJD1<&vnZ zo0(>zIkQTse}qNMb6+i`th54(3pKm8;UAJ<_BULR*Z=m5FU7jiW(&#l+}WkHZ|e@1 z`pm;Q^pCuLUQUrnQ(hPM10pSSHQS=Bf8DqG1&!-B!oQQ|FuzLruL1w(+g<8&znyI? zzX-}?SwUvNjEuT?7uUOy{Fb@xKklpj+jdYM^IK9}NxvLRZd{l9FHEQJ4IO~q%4I0O zAN|*8x^nIU4Giw?f*tmNx=7H)2-Zn?J^B6SgpcW3ZXV_57Sn%Mtfr_=w|sYpAhdJT zcKo6Z*oIOU(az~3$LOEWm9Q)dYWMA}T7L23MVGqrcA%4H)+^`+=j+Hh8CTCnnG2Rh zgcXVW%F8$R9)6}f=NQiLPt8qt3xNUQI>Q*)H1lzk<&n?XR-f}tc&9V0H0lhGqHJ^N zN%h(9-Of2_)!Xk{qdIkU>1%mk%I_Id1!MU*yq&&>)Q+!L^t&-2mW9Xq7g9C@* zl&PKJ&su2L+iku?Te?Pf?k3tUK){Bj_gb&aPo8Ago^XI~mRTd(5{&^tf1)!-lSMha z@$~ae!r(~`=p&|mMxy2EiZQ6FvXb(1avS*`Pj%$)*?vwceGKHmHnl`v&fEQ_Wh+G) zEPQ^3&oV%}%;zF`AM|S%d>pM@1}33PN5*4SewROk_K$n^i8QjaYiRzwG8#OvVIF|{x85wH+?*P*%)woI zR538k@=(E`V;p1UwA|fqSh`$n_t;Sz4T)`_s~pRR4lbmWWSdxa-FqLZ%fLT)Bh?iye?COx~mO1wkn5)HNMg7`8~ z25VJhz&3Z7`M>6luJrEw$Jikft+6SxyIh?)PU1?DfrKMGC z=3T;;omE4H`PWqF8?0*dOA3o9y@~WK`S}{?tIHquEw?v`M^D%Lobpdrp%3}1=-&qk zqAtb1px-1Fy6}E8IUg4s%8B0~P<P5C;de%@n~XnDKF@fr$a+^@$^P|>vlw($aSK2lRtLt~8tRb`I0 znfI!G?K|<5ry*gk>y56rZy0NkK6)))6Mg1=K?7yS9p+#1Ij=W*%5Rt-mlc;#MOnE9 zoi`-+6oj@)`gq2Af!B+9%J#K9V=ji2dj2<_qaLSXOCeqQ&<0zMSb$5mAi;HU=v`v<>NYk}MbD!ewYVB+N-ctzn=l&bTwv)*7 zmY<+Y@SBbtl9PPk$HTR?ln@(T92XjTRj0Mx|Mzl;lW>Su_y^~fh?8(L?oz8h!cCpb zZG-OY=NJ3{>r*`U<(J%#zjFT-a9>u6+23H{=d(utkgqt7@^)C;pkb)fQ|Q=*8*SyT z;otKe+f8fEp)ZacKZDn3TNzs>_Kx+g*c_mr8LBhr8GnoEmAQk#%sR52`bdbW8Ms$!0u2bdt=T-lK3JbDW`F(Urt%Ob2seiN>7U`YN}aOdIiCC;eeufJC#m3S z9#|l2c?G@t*hH5y^76jkv)rs4H+;oiTuY5FQwRMN_7NUqeiD|b&RyxPXQz|3qC(_> zZJMwjC4F!1m2INXqzisQ4X^w=>&(+Ecdu&~IWEMn7f*YcYI&eWI(6hI#f114%aymM zyhlG6{q>XN7(LyGiMAS&qijR%d2rV|>AUT_sE&EKUSTCM26>aKzNxk0?K|utOcxl# zxIOwM#O!!H+QzbX*&p=QuKe4y;bS>&StQOE5AEGg_ubk8{;1yOVAJfE_Js-lL7rr9 z)CEuFIlkApj~uV^zJK7KocjT=4B zJP(}0x}|A7C$$5gIp>KBPZ|A#2Ew;$#g9Fk)r;Q~?G$>x<+JM)J3u>j zi68K=I;ld`JJ?Nq+^_B?C+Q%+x#m{9JF$tbaDeNIep%=^#>KHGtg=L)>m z_J&vaZTs2{qP!4Gdw5u5Kcf}5R4(q}Lebx%(J$7l*Q`Il#pCTM%!`y5y*-~zIVs}D z9;t+(xmV~R65^ZQXe+<5{$QW0O8MT~a{kdFLR)nfRMA9L(YU>x*DTltN#m-2km zC;T`cfb{c`mcx(z7o_a8bYJn8_^dz4Cq!DZ37{P6uF{@#519UWK1{>(9sZB1I^6MmNc39MJ-_|)!S8vO+O3&$MulU3Gc z_W{N*B(yneyl-oN_MKaJ{CZ6dv-~^8uPbLSh&0jfV@EfA{2Dc!_rOyfx`R0T@LonA z<*%O?-aa_Wm-z$s@K(ex7UhM0-?9C=PkYdk&d2n((E4>&(f4D`fOQY%CURMMyJyU` zVeJBAId&StHjw76tnwSqZs3e0683`L{a3k9JYdg#(ZVw4J`&CkV-2LFaDE1Z?CehVy%vZx$tM3tTax8E@2;N^QTrPcI?Ob8uK!DM0_sfE6ks2M?iw zPS4{(k-PF*-oY>S!d9;L+|xdTtLen9B2LvpL4k;#ScB< z$NP_7j~7)5eXuoYEk*dK_rSz9yT_C4B{r~^#^o}-VQI=Y?01|$aa!a7=UEm$|DsQQ zfLK1qmho2@)nwA?$1%T6jwO2HZ({6&;`s|OQOxI4S8*Hw=Qp!b(gNJR%SAj&wGa>^&2@x)Vj zhd^WfzJ^b0O{E^q82Pw({uT`E`MT2WnZ02{E%t*yRPN>?W>0vU^4@Vyh4;mLj918c z*s*papo?<}cQM{5lcgZScx}?usg{mS!KkH9U%@|^_33?{FI{1ss+8kXyFY&5M-e~f zM$){FF;_+z3sNJ)Er~{Beux$fEl{R4|7WKcpEsGtK57f+H0DJ$hI;U;JtF>+lG@sV zQI_;bQ^7XIJ>Bs?C32b1v;am;P4GUqAJ#zOHv}4SmV|xXX6~O9&e_~YCCpbT>s$`! k<4FtN!5 impl IntoView { - // Provides context that manages stylesheets, titles, meta tags, etc. - provide_meta_context(); - - view! { - // injects a stylesheet into the document - // id=leptos means cargo-leptos will hot-reload this stylesheet - - - // sets the document title - - - // content for this welcome page - <Router> - <main> - <Routes fallback=|| "Page not found.".into_view()> - <Route path=StaticSegment("") view=HomePage /> - <Route path=StaticSegment("/register") view=RegisterForm /> - </Routes> - </main> - </Router> - } -} diff --git a/frontend/src/components/homepage.rs b/frontend/src/components/homepage.rs deleted file mode 100644 index 140458b..0000000 --- a/frontend/src/components/homepage.rs +++ /dev/null @@ -1,14 +0,0 @@ -use leptos::prelude::*; - -/// Renders the home page of your application. -#[component] -pub fn HomePage() -> impl IntoView { - // Creates a reactive value to update the button - let count = RwSignal::new(0); - let on_click = move |_| *count.write() += 1; - - view! { - <h1>"Welcome to Leptos!"</h1> - <button on:click=on_click>"Click Me: " {count}</button> - } -} diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs deleted file mode 100644 index cde9b45..0000000 --- a/frontend/src/components/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod app; -mod homepage; -mod register; -pub use app::App; - -use leptos::prelude::*; -use leptos_meta::MetaTags; - -pub fn shell(options: LeptosOptions) -> impl IntoView { - view! { - <!DOCTYPE html> - <html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <AutoReload options=options.clone() /> - <HydrationScripts options /> - <MetaTags /> - </head> - <body> - <App /> - </body> - </html> - } -} diff --git a/frontend/src/components/register.rs b/frontend/src/components/register.rs deleted file mode 100644 index e6c380b..0000000 --- a/frontend/src/components/register.rs +++ /dev/null @@ -1,115 +0,0 @@ -use leptos::{ev::SubmitEvent, prelude::*}; -use reqwest::{Client, StatusCode}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FormData { - username: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ResponseData { - username: String, - code: String, -} - -#[server] -async fn register(payload: FormData) -> Result<ResponseData, ServerFnError> { - let client = Client::new(); - let response = client - .post("http://localhost:8000/api/v1/register") - .json(&payload) - .send() - .await?; - - match response.status() { - StatusCode::CREATED => { - let response_data = response.json::<ResponseData>().await?; - Ok(response_data) - } - status => { - let error_msg = response.text().await?; - Err(ServerFnError::ServerError(format!( - "Registration failed: {} - {}", - status, error_msg - ))) - } - } -} - -#[component] -pub fn RegisterForm() -> impl IntoView { - let username = RwSignal::new(String::new()); - let register_action = Action::new(|input: &FormData| { - let input = input.clone(); - async move { register(input).await } - }); - let on_submit = move |ev: SubmitEvent| { - ev.prevent_default(); - let form_data = FormData { - username: username.get(), - }; - register_action.dispatch(form_data); - }; - let is_submitting = move || register_action.pending().get(); - - view! { - <form on:submit=on_submit> - <div> - <label>"Username"</label> - <input - type="text" - on:input=move |ev| username.set(event_target_value(&ev)) - prop:value=username - /> - </div> - <button type="submit" disabled=is_submitting> - {move || { if is_submitting() { "Registering..." } else { "Register" } }} - </button> - <ErrorBoundary fallback=move |errors| { - view! { - <div class="error-container"> - <p class="error-title">"Registration Error:"</p> - <ul class="error-list"> - {move || { - errors - .get() - .into_iter() - .map(|(_, e)| { - view! { <li class="error-item">{e.to_string()}</li> } - }) - .collect_view() - }} - </ul> - </div> - } - }> - {move || { - register_action - .value() - .get() - .map(|result| { - match result { - Ok(response) => { - view! { - <div class="success-container"> - <p class="success-message">"Registration successful!"</p> - <div class="registration-details"> - <p>"Username: " {response.username}</p> - <p>"Your Code: " {response.code}</p> - <p class="code-notice"> - "Please save this code. You will need it to login." - </p> - </div> - </div> - } - .into_any() - } - Err(e) => view! { <span>{e.to_string()}</span> }.into_any(), - } - }) - }} - </ErrorBoundary> - </form> - } -} diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs deleted file mode 100644 index ab638fe..0000000 --- a/frontend/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod components; - -#[cfg(feature = "hydrate")] -#[wasm_bindgen::prelude::wasm_bindgen] -pub fn hydrate() { - use crate::components::*; - console_error_panic_hook::set_once(); - leptos::mount::hydrate_body(App); -} diff --git a/frontend/src/main.rs b/frontend/src/main.rs deleted file mode 100644 index 7ca865d..0000000 --- a/frontend/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[cfg(feature = "ssr")] -#[tokio::main] -async fn main() { - use axum::Router; - use frontend::components::*; - use leptos::logging::log; - use leptos::prelude::*; - use leptos_axum::{generate_route_list, LeptosRoutes}; - - let conf = get_configuration(None).unwrap(); - let addr = conf.leptos_options.site_addr; - let leptos_options = conf.leptos_options; - // Generate the list of routes in your Leptos App - let routes = generate_route_list(App); - - let app = Router::new() - .leptos_routes(&leptos_options, routes, { - let leptos_options = leptos_options.clone(); - move || shell(leptos_options.clone()) - }) - .fallback(leptos_axum::file_and_error_handler(shell)) - .with_state(leptos_options); - - // run our app with hyper - // `axum::Server` is a re-export of `hyper::Server` - log!("listening on http://{}", &addr); - let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - axum::serve(listener, app.into_make_service()) - .await - .unwrap(); -} - -#[cfg(not(feature = "ssr"))] -pub fn main() { - // no client-side main function - // unless we want this to work with e.g., Trunk for pure client-side testing - // see lib.rs for hydration function instead -} diff --git a/frontend/style/main.scss b/frontend/style/main.scss deleted file mode 100644 index a448b36..0000000 --- a/frontend/style/main.scss +++ /dev/null @@ -1,5 +0,0 @@ -body { - font-family: sans-serif; - text-align: center; -} - diff --git a/justfile b/justfile index b81e594..834c5e1 100644 --- a/justfile +++ b/justfile @@ -1,106 +1,83 @@ set dotenv-load +PROJECT_NAME := "echoes-of-ascension" + # List all available commands default: @just --list -# Install required tools and dependencies -setup: - just db-setup - rustup toolchain install nightly - rustup default nightly - rustup target add wasm32-unknown-unknown - cargo install cargo-leptos - cargo install cargo-watch - cargo install just +# Format code +fmt: + cargo fmt -# Development Commands - -# Start development server with hot reload -dev: kill-server db-migrate - #!/usr/bin/env bash - (RUSTC_WRAPPER=sccache cargo watch -x "run -p backend" | bunyan) & \ - (RUSTC_WRAPPER=sccache cargo leptos watch | bunyan) & \ - wait - -# Run cargo check on both native and wasm targets -check: - cargo check --all-targets - cargo check --all-targets --target wasm32-unknown-unknown +# Lint code +lint: + cargo clippy -- -D warnings # Run tests test: - cargo test --all-targets - cargo test --all-targets --target wasm32-unknown-unknown + cargo test -# Format code -fmt: - cargo fmt --all +# Build the application (debug) +build: + cargo build -# Run clippy lints -lint: - cargo clippy --all-targets -- -D warnings - cargo clippy --all-targets --target wasm32-unknown-unknown -- -D warnings +# Build the application (release) +build-release: + cargo build --release -# Clean build artifacts +# Run the application (debug) +run: + cargo run + +# Run the application (release) +run-release: + cargo run --release + +# Run migrations +migrate: + cargo sqlx migrate run + +# Revert migrations +migrate-revert: + cargo sqlx migrate revert + +# Create a new migration +migrate-create name: + cargo sqlx migrate add $(name) + +# Check migrations +migrate-status: + cargo sqlx migrate status + +# Watch for changes and run tests/linting/run (for development) +dev: + cargo watch -x clippy -x test -x run | bunyan + +# Build, migrate, and run (release) +deploy: + just build-release + just migrate + just run-release + +# Generate documentation +doc: + cargo doc --open + +# Clean the project clean: cargo clean - rm -rf dist - rm -rf target -# Build Commands +# Analyze binary size +analyze-size: + cargo build --release + cargo install cargo-bloat + cargo bloat --release --all-features --crates -# Build for development -build-dev: - cargo build - cargo leptos build - -# Build for production -build-prod: - cargo leptos build --release - -# Build WASM only -build-wasm: - cargo leptos build-only-wasm - -# Build server only -build-server: - cargo leptos build-only-server - -# Deployment Commands -deploy: - echo "Add deployment commands here" - -# Combined commands -check-all: fmt lint check test - -# Start production server -serve-prod: - cargo leptos serve --release - -kill-server: - #!/usr/bin/env sh - pkill -f "target/debug/server" || true - pkill -f "cargo-leptos" || true - -# Database Commands - -# Setup the database -db-setup: - ./scripts/init_db - -alias migrate:=db-migrate -alias m:=db-migrate -# Migrate -db-migrate: - sqlx migrate run --source backend/migrations - -# Generate sqlx prepare check files -db-prepare: - sqlx prepare - -alias migrations:=db-new-migration -# Create new migration -db-new-migration name: - sqlx migrate add -r {{name}} +# Check dependencies for security vulnerabilities +audit: + cargo audit +# Check for outdated dependencies +outdated: + cargo outdated diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 776484a..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "nightly" -components = ["rustfmt", "clippy", "rust-analyzer"] -targets = ["wasm32-unknown-unknown"] diff --git a/scripts/init_db b/scripts/init_db old mode 100755 new mode 100644 index 8b47958..234a274 --- a/scripts/init_db +++ b/scripts/init_db @@ -16,7 +16,7 @@ fi DB_USER="${POSTGRES_USER:=postgres}" DB_PASSWORD="${POSTGRES_PASSWORD:=password}" -DB_NAME="${POSTGRES_DB:=maze_ascension}" +DB_NAME="${POSTGRES_DB:=newsletter}" DB_PORT="${POSTGRES_PORT:=5432}" DB_HOST="${POSTGRES_HOST:=localhost}" @@ -43,4 +43,4 @@ done DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}" export DATABASE_URL sqlx database create -sqlx migrate run --source backend/migrations +sqlx migrate run diff --git a/src/config/application.rs b/src/config/application.rs new file mode 100644 index 0000000..11e2f7e --- /dev/null +++ b/src/config/application.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; +use serde_aux::field_attributes::deserialize_number_from_string; + +#[derive(Debug, Clone, Deserialize)] +pub struct ApplicationSettings { + #[serde(deserialize_with = "deserialize_number_from_string")] + pub port: u16, + pub host: String, +} diff --git a/src/config/database.rs b/src/config/database.rs new file mode 100644 index 0000000..efa7b88 --- /dev/null +++ b/src/config/database.rs @@ -0,0 +1,43 @@ +use secrecy::{ExposeSecret, SecretString}; +use serde::Deserialize; +use serde_aux::field_attributes::deserialize_number_from_string; +use sqlx::{ + postgres::{PgConnectOptions, PgSslMode}, + ConnectOptions, +}; + +#[derive(Debug, Clone, Deserialize)] +pub struct DatabaseSettings { + pub username: String, + pub password: SecretString, + #[serde(deserialize_with = "deserialize_number_from_string")] + pub port: u16, + pub host: String, + pub database_name: String, + pub require_ssl: bool, +} + +impl DatabaseSettings { + #[must_use] + pub fn without_db(&self) -> PgConnectOptions { + let ssl_mode = if self.require_ssl { + PgSslMode::Require + } else { + PgSslMode::Prefer + }; + + PgConnectOptions::new() + .host(&self.host) + .username(&self.username) + .password(self.password.expose_secret()) + .port(self.port) + .ssl_mode(ssl_mode) + } + + #[must_use] + pub fn with_db(&self) -> PgConnectOptions { + self.without_db() + .database(&self.database_name) + .log_statements(tracing_log::log::LevelFilter::Trace) + } +} diff --git a/src/config/environment.rs b/src/config/environment.rs new file mode 100644 index 0000000..8050e95 --- /dev/null +++ b/src/config/environment.rs @@ -0,0 +1,38 @@ +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug)] +pub enum Environment { + Local, + Production, +} + +impl Display for Environment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Self::Local => "local", + Self::Production => "production", + }; + write!(f, "{s}") + } +} + +impl TryFrom<String> for Environment { + type Error = String; + fn try_from(value: String) -> Result<Self, Self::Error> { + Self::from_str(&value) + } +} + +impl FromStr for Environment { + type Err = String; + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.to_lowercase().as_str() { + "local" => Ok(Self::Local), + "production" => Ok(Self::Production), + other => Err(format!( + "{other} is not supported environment. \ + Use either `local` or `production`.", + )), + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..08f0954 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,53 @@ +pub mod application; +pub mod database; +pub mod environment; + +use application::ApplicationSettings; +pub use database::DatabaseSettings; +use environment::Environment; +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct Settings { + pub database: DatabaseSettings, + pub application: ApplicationSettings, +} + +/// Get the configuration settings for the application. +/// +/// # Panics +/// +/// This function may panic in the following cases: +/// +/// - If the current directory cannot be determined. This is highly unusual. +/// - If the `APP_ENVIRONMENT` environment variable is set to an invalid value +/// that cannot be converted to an `Environment` enum variant. +/// +/// # Errors +/// +/// This function returns an error if: +/// +/// - Any of the configuration files (`base.toml`, `{environment}.toml`) cannot be read or parsed. +/// - Environment variables prefixed with `APP_` cannot be read or parsed. +/// - The resulting configuration cannot be deserialized into the `Settings` struct. +pub fn get_config() -> Result<Settings, config::ConfigError> { + let base_path = std::env::current_dir().expect("Failed to determine current directory"); + let config_directory = base_path.join("config"); + let env: Environment = std::env::var("APP_ENVIRONMENT") + .unwrap_or_else(|_| "local".into()) + .try_into() + .expect("Failed to parse APP_ENVIRONMENT"); + + let env_filename = format!("{}.toml", &env); + + let settings = config::Config::builder() + .add_source(config::File::from(config_directory.join("base.toml"))) + .add_source(config::File::from(config_directory.join(env_filename))) + .add_source( + config::Environment::with_prefix("APP") + .prefix_separator("_") + .separator("__"), + ) + .build()?; + settings.try_deserialize::<Settings>() +} diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..81613e2 --- /dev/null +++ b/src/domain/mod.rs @@ -0,0 +1,24 @@ +//! # Domain +//! +//! This module defines the core business logic and data structures of the application, +//! independent of any specific implementation details such as databases or external APIs. +//! +//! It contains value objects, entities, and domain services that represent the fundamental +//! concepts and rules of the application's problem domain. +//! +//! Example: +//! +//! ``` +//! // A value object representing an email address +//! #[derive(Debug, Clone)] +//! pub struct EmailAddress(pub String); +//! +//! // An entity representing a User +//! pub struct User { +//! pub id: UserId, +//! pub email: EmailAddress, +//! pub name: String, +//! } +//! +//! pub struct UserId(pub String); +//! ``` diff --git a/src/errors/mod.rs b/src/errors/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f892d48 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub mod config; +pub mod domain; +pub mod errors; +pub mod middleware; +pub mod models; +pub mod repositories; +pub mod routes; +pub mod services; +pub mod startup; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3d0270c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,15 @@ +use echoes_of_ascension::{ + config::get_config, + middleware::telemetry::{get_subscriber, init_subscriber}, + startup::Application, +}; + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + let subscriber = get_subscriber("echoes_of_ascension", "info", std::io::stdout); + init_subscriber(subscriber); + let config = get_config().expect("Failed to read configuation."); + let application = Application::build(config).await?; + application.run_until_stopped().await?; + Ok(()) +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..304af1e --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1 @@ +pub mod telemetry; diff --git a/backend/src/telemetry.rs b/src/middleware/telemetry.rs similarity index 57% rename from backend/src/telemetry.rs rename to src/middleware/telemetry.rs index 4ce1b7d..711bf34 100644 --- a/backend/src/telemetry.rs +++ b/src/middleware/telemetry.rs @@ -3,6 +3,12 @@ use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; use tracing_log::LogTracer; use tracing_subscriber::{fmt::MakeWriter, layer::SubscriberExt, EnvFilter, Registry}; +/// Create a new tracing subscriber. +/// +/// # Panics +/// +/// This function may panic if there is a bug in the `EnvFilter::from` implementation, +/// causing the `env_filter.into()` conversion to fail. This is highly unlikely. pub fn get_subscriber<Sink>( name: &str, env_filter: &str, @@ -19,6 +25,16 @@ where .with(formatting_layer) } +/// Initialize a global subscriber for tracing and logging. +/// +/// # Panics +/// +/// This function may panic in the following cases: +/// +/// - If `LogTracer::init()` fails because the global logger has already been initialized. +/// This typically happens if `init_subscriber` is called more than once. +/// - If `set_global_default(subscriber)` fails because another subscriber has already been set, +/// or if there's an issue with the provided subscriber. pub fn init_subscriber(subscriber: impl Subscriber + Sync + Send) { LogTracer::init().expect("Failed to set logger"); set_global_default(subscriber).expect("Failed to set subscriber."); diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..d57353b --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,20 @@ +//! # Models +//! +//! This module defines the data structures (models or entities) that represent the core concepts +//! of the application's domain. These models are database-agnostic and primarily focus on +//! data representation. +//! +//! They are used to define the shape of the data as it exists within the application, +//! independent of how it's stored or retrieved. +//! +//! Example: +//! +//! ``` +//! // A simple model for a Subscriber +//! #[derive(Debug, Clone)] +//! pub struct Subscriber { +//! pub id: i32, +//! pub email: String, +//! pub name: String, +//! } +//! ``` diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs new file mode 100644 index 0000000..3f8706b --- /dev/null +++ b/src/repositories/mod.rs @@ -0,0 +1,25 @@ +//! # Repositories +//! +//! This module provides an abstraction layer for data access. Repositories encapsulate the +//! logic for interacting with the database, providing a consistent interface for creating, +//! reading, updating, and deleting data. +//! +//! They are responsible for translating between the application's domain models and the +//! database's data structures. The database connection pool is passed as a parameter to +//! the repository methods. +//! +//! Example: +//! +//! ```rust +//! use sqlx::PgPool; +//! use sqlx::Error; +//! +//! // A repository for managing Subscribers +//! #[derive(Debug, Clone, Copy)] +//! pub struct SubscriberRepository; +//! +//! pub async fn create_subscriber(pool: &PgPool, email: &str, name: &str) -> Result<(), Error> { +//! // ... database interaction logic using the pool +//! Ok(()) +//! } +//! ``` diff --git a/backend/src/routes/health_check.rs b/src/routes/health_check.rs similarity index 100% rename from backend/src/routes/health_check.rs rename to src/routes/health_check.rs diff --git a/backend/src/routes/mod.rs b/src/routes/mod.rs similarity index 55% rename from backend/src/routes/mod.rs rename to src/routes/mod.rs index c3e1bf7..f45395d 100644 --- a/backend/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,28 +1,25 @@ -mod api; mod health_check; +use axum::{routing::get, Router}; + +use crate::startup::AppState; use axum::{ body::Bytes, extract::MatchedPath, http::{HeaderMap, Request}, response::Response, - routing::get, - Router, }; -use health_check::health_check; +pub use health_check::*; use std::time::Duration; -use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer}; -use tracing::{info_span, Span}; +use tower_http::classify::ServerErrorsFailureClass; +use tower_http::trace::TraceLayer; +use tracing::{info, info_span, Span}; use uuid::Uuid; -use crate::startup::AppState; - pub fn route(state: AppState) -> Router { Router::new() .route("/health_check", get(health_check)) - .nest("/api", api::routes()) .with_state(state) - // Tracing layer .layer( TraceLayer::new_for_http() .make_span_with(|request: &Request<_>| { @@ -33,13 +30,29 @@ pub fn route(state: AppState) -> Router { info_span!( "http-request", method = ?request.method(), + uri = %request.uri(), matched_path, - some_other_field = tracing::field::Empty, request_id=%Uuid::new_v4(), ) }) - .on_request(|_request: &Request<_>, _span: &Span| {}) - .on_response(|_response: &Response<_>, _latency: Duration, _span: &Span| {}) + .on_request(|request: &Request<_>, span: &Span| { + info!( + target: "http_requests", + parent: span, + method = ?request.method(), + uri = %request.uri(), + "Incoming request" + ); + }) + .on_response(|response: &Response<_>, latency: Duration, span: &Span| { + info!( + target: "http_responses", + parent: span, + status = response.status().as_u16(), + latency = ?latency, + "Outgoing response" + ); + }) .on_body_chunk(|_chunk: &Bytes, _latency: Duration, _span: &Span| {}) .on_eos( |_trailers: Option<&HeaderMap>, _stream_duration: Duration, _span: &Span| {}, @@ -48,4 +61,5 @@ pub fn route(state: AppState) -> Router { |_error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {}, ), ) + // .layer(create_telemetry_layer()) } diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..31d4a62 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,25 @@ +//! # Services +//! +//! This module contains the business logic of the application. Services orchestrate the +//! interaction between models, repositories, and other components to fulfill specific use +//! cases. +//! +//! They define the core operations that the application performs and are responsible for +//! enforcing business rules and ensuring data consistency. +//! +//! Example: +//! +//! ``` +//! // A service for managing newsletter subscriptions +//! pub struct NewsletterService { +//! // ... dependencies (e.g., SubscriberRepository, EmailClient) +//! } +//! +//! impl NewsletterService { +//! // Method to subscribe a user to the newsletter +//! pub async fn subscribe_user(&self, email: &str, name: &str) -> Result<(), String> { +//! // ... business logic (e.g., validate email, create subscriber, send confirmation email) +//! Ok(()) +//! } +//! } +//! ``` diff --git a/src/startup.rs b/src/startup.rs new file mode 100644 index 0000000..6222785 --- /dev/null +++ b/src/startup.rs @@ -0,0 +1,69 @@ +use sqlx::{postgres::PgPoolOptions, PgPool}; +use std::sync::Arc; +use tokio::{net::TcpListener, task::JoinHandle}; + +use crate::{ + config::{DatabaseSettings, Settings}, + routes::route, +}; + +pub struct App { + pub pool: PgPool, +} + +pub type AppState = Arc<App>; + +pub struct Application { + port: u16, + server: JoinHandle<Result<(), std::io::Error>>, +} + +impl Application { + /// Builds and starts the application server. + /// + /// # Errors + /// + /// - Returns `std::io::Error` if: + /// - It fails to bind to the specified address. + /// + /// # Panics + /// + /// - Panics if `listener.local_addr()` returns `None`. This should only occur if the + /// listener is not properly bound to an address, which is considered a critical + /// failure during application startup. + pub async fn build(config: Settings) -> Result<Self, std::io::Error> { + let pool = get_connection_pool(&config.database); + + let addr = format!("{}:{}", config.application.host, config.application.port); + let listener = TcpListener::bind(addr).await?; + let port = listener + .local_addr() + .expect("Listener should have a local address") + .port(); + let app_state = App { pool }.into(); + let server = tokio::spawn(async move { axum::serve(listener, route(app_state)).await }); + + Ok(Self { port, server }) + } + + #[must_use] + #[inline] + pub const fn port(&self) -> u16 { + self.port + } + + /// Runs the application until it is stopped. + /// + /// # Errors + /// + /// - Returns `std::io::Error` if the server task encounters an error. + #[inline] + pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { + self.server.await? + } +} + +#[must_use] +pub fn get_connection_pool(config: &DatabaseSettings) -> PgPool { + PgPoolOptions::new().connect_lazy_with(config.with_db()) +} diff --git a/tests/api/health_check.rs b/tests/api/health_check.rs new file mode 100644 index 0000000..3578d94 --- /dev/null +++ b/tests/api/health_check.rs @@ -0,0 +1,17 @@ +use crate::helpers::spawn_app; +use reqwest::Client; + +#[tokio::test] +async fn health_check() { + let app = spawn_app().await; + let url = format!("{}/health_check", &app.address); + let client = Client::new(); + let response = client + .get(&url) + .send() + .await + .expect("Failed to execute request"); + + assert!(response.status().is_success()); + assert_eq!(Some(0), response.content_length()); +} diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs new file mode 100644 index 0000000..97e3eb5 --- /dev/null +++ b/tests/api/helpers.rs @@ -0,0 +1,79 @@ +use echoes_of_ascension::{ + config::{get_config, DatabaseSettings}, + middleware::telemetry::{get_subscriber, init_subscriber}, + startup::{get_connection_pool, Application}, +}; +use once_cell::sync::Lazy; +use sqlx::{Connection, Executor, PgConnection, PgPool}; +use uuid::Uuid; + +static TRACING: Lazy<()> = Lazy::new(|| { + let default_filter_level = "trace"; + let subscriber_name = "test"; + if std::env::var("TEST_LOG").is_ok() { + let subscriber = get_subscriber(subscriber_name, default_filter_level, std::io::stdout); + init_subscriber(subscriber); + } else { + let subscriber = get_subscriber(default_filter_level, subscriber_name, std::io::sink); + init_subscriber(subscriber); + } +}); + +pub struct TestApp { + pub address: String, + pub pool: PgPool, +} + +pub async fn spawn_app() -> TestApp { + Lazy::force(&TRACING); + + let config = { + let mut c = get_config().expect("Failed to read configuration."); + c.database.database_name = Uuid::new_v4().to_string(); + c.application.port = 0; + c + }; + configure_database(&config.database).await; + + let application = Application::build(config.clone()) + .await + .expect("Failed to build application."); + + let address = format!("http://127.0.0.1:{}", application.port()); + let _ = tokio::spawn(application.run_until_stopped()); + + TestApp { + address, + pool: get_connection_pool(&config.database), + } +} + +async fn configure_database(config: &DatabaseSettings) -> PgPool { + let mut connection = PgConnection::connect_with(&config.without_db()) + .await + .expect("Failed to connect to Postgres."); + + connection + .execute( + format!( + r#" + CREATE DATABASE "{}" + "#, + config.database_name + ) + .as_str(), + ) + .await + .expect("Failed to create database."); + + let pool = PgPool::connect_with(config.with_db()) + .await + .expect("Failed to connect to Postgres."); + + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("Failed to migrate database"); + + pool +} diff --git a/tests/api/main.rs b/tests/api/main.rs new file mode 100644 index 0000000..c76d49f --- /dev/null +++ b/tests/api/main.rs @@ -0,0 +1,2 @@ +mod health_check; +mod helpers; From ff62ce1761f8bac8f9a65ccee59bf754beb1f950 Mon Sep 17 00:00:00 2001 From: Kristofers Solo <dev@kristofers.xyz> Date: Tue, 11 Feb 2025 11:14:28 +0200 Subject: [PATCH 2/2] feat(migrations): migrate old code --- ...628f4ff2e8623c00e0675f2b5866cda7c49bf.json | 15 ++++ Cargo.toml | 11 ++- migrations/.gitkeep | 0 migrations/20250125123853_init.down.sql | 11 +++ migrations/20250125123853_init.up.sql | 28 ++++++ scripts/init_db | 2 +- src/domain/mod.rs | 2 + src/domain/user/mod.rs | 3 + src/domain/user/new_user.rs | 7 ++ src/domain/user/user_code.rs | 73 +++++++++++++++ src/domain/user/username.rs | 73 +++++++++++++++ src/errors/app.rs | 88 +++++++++++++++++++ src/errors/mod.rs | 2 + src/errors/user.rs | 22 +++++ src/repositories/mod.rs | 2 + src/repositories/user.rs | 36 ++++++++ src/routes/api/mod.rs | 9 ++ src/routes/api/v1/auth.rs | 71 +++++++++++++++ src/routes/api/v1/mod.rs | 9 ++ src/routes/mod.rs | 2 + 20 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 .sqlx/query-76811378181edbd741685f253a2628f4ff2e8623c00e0675f2b5866cda7c49bf.json delete mode 100644 migrations/.gitkeep create mode 100644 migrations/20250125123853_init.down.sql create mode 100644 migrations/20250125123853_init.up.sql mode change 100644 => 100755 scripts/init_db create mode 100644 src/domain/user/mod.rs create mode 100644 src/domain/user/new_user.rs create mode 100644 src/domain/user/user_code.rs create mode 100644 src/domain/user/username.rs create mode 100644 src/errors/app.rs create mode 100644 src/errors/user.rs create mode 100644 src/repositories/user.rs create mode 100644 src/routes/api/mod.rs create mode 100644 src/routes/api/v1/auth.rs create mode 100644 src/routes/api/v1/mod.rs diff --git a/.sqlx/query-76811378181edbd741685f253a2628f4ff2e8623c00e0675f2b5866cda7c49bf.json b/.sqlx/query-76811378181edbd741685f253a2628f4ff2e8623c00e0675f2b5866cda7c49bf.json new file mode 100644 index 0000000..89d9f8e --- /dev/null +++ b/.sqlx/query-76811378181edbd741685f253a2628f4ff2e8623c00e0675f2b5866cda7c49bf.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO \"user\" (username, code)\n VALUES ($1, $2)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "76811378181edbd741685f253a2628f4ff2e8623c00e0675f2b5866cda7c49bf" +} diff --git a/Cargo.toml b/Cargo.toml index 242c290..4822a65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ axum = "0.8" chrono = { version = "0.4", features = ["serde", "clock"] } config = { version = "0.15", features = ["toml"], default-features = false } serde = { version = "1", features = ["derive"] } +serde_json = "1" sqlx = { version = "0.8", default-features = false, features = [ "runtime-tokio", "tls-rustls", @@ -31,7 +32,7 @@ tokio = { version = "1.39", features = [ "tracing", "rt-multi-thread", ] } -uuid = { version = "1.8", features = ["v4", "serde"] } +uuid = { version = "1.13", features = ["v4", "serde"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } tower-http = { version = "0.6", features = ["trace"] } @@ -44,6 +45,14 @@ reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", ] } askama = { version = "0.12", features = ["with-axum"] } +validator = "0.20" +unicode-segmentation = "1" +rand = "0.8" +argon2 = "0.5" +password-hash = "0.5" +hex = "0.4" +anyhow = "1" +thiserror = "2" [dev-dependencies] diff --git a/migrations/.gitkeep b/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/migrations/20250125123853_init.down.sql b/migrations/20250125123853_init.down.sql new file mode 100644 index 0000000..679391c --- /dev/null +++ b/migrations/20250125123853_init.down.sql @@ -0,0 +1,11 @@ +-- Add down migration script here +-- Drop indexes first +DROP INDEX IF EXISTS idx_scores_user_score; + +DROP INDEX IF EXISTS idx_users_login; + +-- Drop tables in reverse order of creation +DROP TABLE IF EXISTS scores; + +DROP TABLE IF EXISTS users; + diff --git a/migrations/20250125123853_init.up.sql b/migrations/20250125123853_init.up.sql new file mode 100644 index 0000000..a82fe10 --- /dev/null +++ b/migrations/20250125123853_init.up.sql @@ -0,0 +1,28 @@ +-- Add up migration script here +-- Enable UUID support +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- Users table with login codes +CREATE TABLE IF NOT EXISTS "user" ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid (), + username varchar(255) NOT NULL UNIQUE, + code varchar(255) NOT NULL UNIQUE, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Scores table with detailed game stats +CREATE TABLE IF NOT EXISTS score ( + id bigserial PRIMARY KEY, + user_id uuid NOT NULL, + score integer NOT NULL, + floor_reached integer NOT NULL, + play_time_seconds integer NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES "user" (id) +); + +-- Indexes for performance +CREATE INDEX idx_user_login ON "user" (code); + +CREATE INDEX idx_scores_user_score ON score (user_id, score DESC); + diff --git a/scripts/init_db b/scripts/init_db old mode 100644 new mode 100755 index 234a274..68163c4 --- a/scripts/init_db +++ b/scripts/init_db @@ -16,7 +16,7 @@ fi DB_USER="${POSTGRES_USER:=postgres}" DB_PASSWORD="${POSTGRES_PASSWORD:=password}" -DB_NAME="${POSTGRES_DB:=newsletter}" +DB_NAME="${POSTGRES_DB:=echoes-of-ascension}" DB_PORT="${POSTGRES_PORT:=5432}" DB_HOST="${POSTGRES_HOST:=localhost}" diff --git a/src/domain/mod.rs b/src/domain/mod.rs index 81613e2..1bbb0a2 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -22,3 +22,5 @@ //! //! pub struct UserId(pub String); //! ``` + +pub mod user; diff --git a/src/domain/user/mod.rs b/src/domain/user/mod.rs new file mode 100644 index 0000000..566a020 --- /dev/null +++ b/src/domain/user/mod.rs @@ -0,0 +1,3 @@ +pub mod new_user; +mod user_code; +mod username; diff --git a/src/domain/user/new_user.rs b/src/domain/user/new_user.rs new file mode 100644 index 0000000..fe70c35 --- /dev/null +++ b/src/domain/user/new_user.rs @@ -0,0 +1,7 @@ +use super::{user_code::UserCode, username::Username}; + +#[derive(Debug, Clone, Default)] +pub struct NewUser { + pub username: Username, + pub code: UserCode, +} diff --git a/src/domain/user/user_code.rs b/src/domain/user/user_code.rs new file mode 100644 index 0000000..51bcbce --- /dev/null +++ b/src/domain/user/user_code.rs @@ -0,0 +1,73 @@ +use argon2::Argon2; +use password_hash::SaltString; +use std::ops::Deref; + +use rand::{rngs::OsRng, thread_rng, Rng}; +use secrecy::{ExposeSecret, SecretString}; + +use crate::errors::user::UserError; + +#[derive(Debug, Clone)] +pub struct UserCode(SecretString); + +impl UserCode { + pub fn hash(&self) -> Result<String, UserError> { + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + + let mut output_key_material = [0u8; 32]; + argon2 + .hash_password_into( + self.expose_secret().as_bytes(), + salt.as_str().as_bytes(), + &mut output_key_material, + ) + .map_err(|e| UserError::HashingError(e.to_string()))?; + Ok(format!( + "{}${}", + salt.as_str(), + hex::encode(output_key_material) + )) + } + + pub fn verify(stored: &str, code: &str) -> Result<bool, UserError> { + let argon2 = Argon2::default(); + + // Split stored value into salt and hash + let parts: Vec<&str> = stored.split('$').collect(); + if parts.len() != 2 { + return Err(UserError::HashingError("Invalid hash format".to_string())); + } + + let salt = parts[0]; + let stored_hash = + hex::decode(parts[1]).map_err(|e| UserError::HashingError(e.to_string()))?; + + let mut output = [0u8; 32]; + argon2 + .hash_password_into(code.as_bytes(), salt.as_bytes(), &mut output) + .map_err(|e| UserError::HashingError(e.to_string()))?; + + Ok(output.as_slice() == stored_hash.as_slice()) + } +} + +impl Default for UserCode { + fn default() -> Self { + let mut rng = thread_rng(); + + let code = (0..16) + .map(|_| rng.gen_range(0..10).to_string()) + .collect::<String>(); + + Self(code.into()) + } +} + +impl Deref for UserCode { + type Target = SecretString; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/domain/user/username.rs b/src/domain/user/username.rs new file mode 100644 index 0000000..1b6794a --- /dev/null +++ b/src/domain/user/username.rs @@ -0,0 +1,73 @@ +use rand::{seq::SliceRandom, thread_rng, Rng}; +use std::{fmt::Display, str::FromStr}; +use unicode_segmentation::UnicodeSegmentation; + +use crate::errors::user::UserError; + +#[derive(Debug, Clone)] +pub struct Username(String); + +impl TryFrom<String> for Username { + type Error = UserError; + fn try_from(value: String) -> Result<Self, Self::Error> { + let is_empty_or_whitespace = value.trim().is_empty(); + let is_too_long = value.graphemes(true).count() > 256; + let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; + let contains_forbidden_characters = + value.chars().any(|c| forbidden_characters.contains(&c)); + if is_empty_or_whitespace || is_too_long || contains_forbidden_characters { + return Err(UserError::UsernameValidation(value)); + } + Ok(Self(value)) + } +} + +impl Default for Username { + fn default() -> Self { + let adjectives = [ + "swift", "bright", "clever", "brave", "mighty", "noble", "wise", "calm", "kind", + "bold", "quick", "sharp", "smart", "keen", "fair", + ]; + + let nouns = [ + "wolf", "eagle", "lion", "hawk", "bear", "tiger", "fox", "owl", "deer", "seal", + "raven", "crane", "dove", "swan", "falcon", + ]; + + let mut rng = thread_rng(); + + let adjective = adjectives.choose(&mut rng).unwrap_or(&"swift"); + let noun = nouns.choose(&mut rng).unwrap_or(&"wolf"); + + let number = rng.gen_range(100..1000); + + let username = format!("{adjective}_{noun}_{number}"); + + Self(username) + } +} + +impl FromStr for Username { + type Err = UserError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Self::try_from(s.to_owned()) + } +} + +impl AsRef<str> for Username { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Display for Username { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From<Username> for String { + fn from(value: Username) -> Self { + value.0 + } +} diff --git a/src/errors/app.rs b/src/errors/app.rs new file mode 100644 index 0000000..6a3eca2 --- /dev/null +++ b/src/errors/app.rs @@ -0,0 +1,88 @@ +use axum::{http::StatusCode, response::IntoResponse, Json}; +use serde::Serialize; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum AppError { + // Authentication/Authorization errors + #[error("Unauthorized")] + Unauthorized, + + #[error("Forbidden")] + Forbidden, + + // Validation errors + #[error("Validation error: {0}")] + Validation(String), + + // Resource errors + #[error("Resource not found: {0}")] + NotFound(String), + + #[error("{resource} already exists: {id}")] + AlreadyExists { resource: &'static str, id: String }, + + // Database errors + #[error("Database error")] + Database(#[from] sqlx::Error), + + // Internal errors + #[error("Internal server error")] + Internal(#[from] anyhow::Error), +} +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub error: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option<String>, +} + +impl IntoResponse for AppError { + fn into_response(self) -> axum::response::Response { + let (status, error_message, details) = match self { + // Auth errors + Self::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_string(), None), + Self::Forbidden => (StatusCode::FORBIDDEN, "Forbidden".to_string(), None), + // Validation errors + Self::Validation(msg) => ( + StatusCode::BAD_REQUEST, + "Validation error".to_string(), + Some(msg), + ), + // Resource errors + Self::NotFound(resource) => ( + StatusCode::NOT_FOUND, + "Resource not found".to_string(), + Some(resource), + ), + Self::AlreadyExists { resource, id } => ( + StatusCode::CONFLICT, + format!("{resource} already exists"), + Some(id), + ), + // Database/Internal errors + Self::Database(e) => { + tracing::error!("Database error: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + None, + ) + } + Self::Internal(e) => { + tracing::error!("Internal error: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + None, + ) + } + }; + + let body = Json(ErrorResponse { + error: error_message, + details, + }); + (status, body).into_response() + } +} diff --git a/src/errors/mod.rs b/src/errors/mod.rs index e69de29..836e47e 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -0,0 +1,2 @@ +pub mod app; +pub mod user; diff --git a/src/errors/user.rs b/src/errors/user.rs new file mode 100644 index 0000000..90d9325 --- /dev/null +++ b/src/errors/user.rs @@ -0,0 +1,22 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum UserError { + #[error("Username validation failed: {0}")] + UsernameValidation(String), + + #[error("Code hashing failed: {0}")] + HashingError(String), + + #[error("Username already taken: {0}")] + UsernameTaken(String), + + #[error("Invalid code format")] + InvalidCode, + + #[error("Authentication failed")] + AuthenticationFailed, + + #[error("Internal server error: {0}")] + Internal(String), +} diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs index 3f8706b..8458b58 100644 --- a/src/repositories/mod.rs +++ b/src/repositories/mod.rs @@ -23,3 +23,5 @@ //! Ok(()) //! } //! ``` + +pub mod user; diff --git a/src/repositories/user.rs b/src/repositories/user.rs new file mode 100644 index 0000000..726ac2e --- /dev/null +++ b/src/repositories/user.rs @@ -0,0 +1,36 @@ +use sqlx::PgPool; +use thiserror::Error; + +use crate::{domain::user::new_user::NewUser, errors::user::UserError}; + +#[derive(Debug, Error)] +pub enum ServerUserError { + #[error("Database error: {0}")] + Database(#[from] sqlx::Error), + #[error("Database error: {0}")] + User(#[from] UserError), +} + +#[tracing::instrument(name = "Saving new user details in the database", skip(pool, new_user))] +pub async fn insert_user(pool: &PgPool, new_user: &NewUser) -> Result<(), ServerUserError> { + sqlx::query!( + r#" + INSERT INTO "user" (username, code) + VALUES ($1, $2) + "#, + new_user.username.as_ref(), + new_user.code.hash()? + ) + .execute(pool) + .await + .map_err(|e| { + tracing::error!("Failed to execute query: {:?}", e); + match e { + sqlx::Error::Database(ref dbe) if dbe.constraint() == Some("user_username_key") => { + ServerUserError::User(UserError::UsernameTaken(new_user.username.to_string())) + } + _ => ServerUserError::Database(e), + } + })?; + Ok(()) +} diff --git a/src/routes/api/mod.rs b/src/routes/api/mod.rs new file mode 100644 index 0000000..9b18325 --- /dev/null +++ b/src/routes/api/mod.rs @@ -0,0 +1,9 @@ +mod v1; + +use axum::Router; + +use crate::startup::AppState; + +pub fn routes() -> Router<AppState> { + Router::new().nest("/v1", v1::routes()) +} diff --git a/src/routes/api/v1/auth.rs b/src/routes/api/v1/auth.rs new file mode 100644 index 0000000..750eb37 --- /dev/null +++ b/src/routes/api/v1/auth.rs @@ -0,0 +1,71 @@ +use crate::{ + domain::user::new_user::NewUser, + errors::{app::AppError, user::UserError}, + repositories::user::{insert_user, ServerUserError}, + startup::AppState, +}; +use anyhow::anyhow; +use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; +use secrecy::ExposeSecret; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +pub struct FormData { + pub username: String, +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub username: String, + pub code: String, +} + +#[tracing::instrument( + name = "Creating new user", + skip(state, payload), + fields( + username= %payload.username, + ) +)] +pub async fn register( + State(state): State<AppState>, + Json(payload): Json<FormData>, +) -> Result<impl IntoResponse, impl IntoResponse> { + let new_user = payload + .try_into() + .map_err(|e: UserError| AppError::Validation(e.to_string()))?; + + match insert_user(&state.pool, &new_user).await { + Ok(()) => Ok((StatusCode::CREATED, Json(Response::from(new_user)))), + Err(ServerUserError::User(UserError::UsernameTaken(username))) => { + Err(AppError::AlreadyExists { + resource: "User", + id: username, + }) + } + Err(e) => { + tracing::error!("Failed to register user: {}", e); + Err(AppError::Internal(anyhow!(e))) + } + } +} + +impl TryFrom<FormData> for NewUser { + type Error = UserError; + fn try_from(value: FormData) -> Result<Self, Self::Error> { + let username = value.username.try_into()?; + Ok(Self { + username, + ..Default::default() + }) + } +} + +impl From<NewUser> for Response { + fn from(value: NewUser) -> Self { + Self { + username: value.username.into(), + code: value.code.expose_secret().into(), + } + } +} diff --git a/src/routes/api/v1/mod.rs b/src/routes/api/v1/mod.rs new file mode 100644 index 0000000..b4d8e9c --- /dev/null +++ b/src/routes/api/v1/mod.rs @@ -0,0 +1,9 @@ +mod auth; + +use axum::{routing::post, Router}; + +use crate::startup::AppState; + +pub fn routes() -> Router<AppState> { + Router::new().route("/register", post(auth::register)) +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f45395d..57e8058 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ +mod api; mod health_check; use axum::{routing::get, Router}; @@ -19,6 +20,7 @@ use uuid::Uuid; pub fn route(state: AppState) -> Router { Router::new() .route("/health_check", get(health_check)) + .nest("/api", api::routes()) .with_state(state) .layer( TraceLayer::new_for_http()