diff --git a/nixin_farm_ssr/.cargo/config.toml b/nixin_farm_ssr/.cargo/config.toml new file mode 100644 index 0000000..fb921ea --- /dev/null +++ b/nixin_farm_ssr/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +loco = "run --" +loco-tool = "run --bin tool --" +playground = "run --example playground" diff --git a/nixin_farm_ssr/.devcontainer/.env b/nixin_farm_ssr/.devcontainer/.env new file mode 100644 index 0000000..1a3ca5b --- /dev/null +++ b/nixin_farm_ssr/.devcontainer/.env @@ -0,0 +1,6 @@ +POSTGRES_DB=loco_app +POSTGRES_USER=loco +POSTGRES_PASSWORD=loco +DATABASE_URL=postgres://loco:loco@db:5432/loco_app +REDIS_URL=redis://redis:6379 +MAILER_HOST=mailer \ No newline at end of file diff --git a/nixin_farm_ssr/.devcontainer/Dockerfile b/nixin_farm_ssr/.devcontainer/Dockerfile new file mode 100644 index 0000000..49616a5 --- /dev/null +++ b/nixin_farm_ssr/.devcontainer/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/rust:1 + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends postgresql-client \ + && cargo install sea-orm-cli cargo-insta \ + && chown -R vscode /usr/local/cargo + +COPY .env /.env diff --git a/nixin_farm_ssr/.devcontainer/compose.yaml b/nixin_farm_ssr/.devcontainer/compose.yaml new file mode 100644 index 0000000..e461417 --- /dev/null +++ b/nixin_farm_ssr/.devcontainer/compose.yaml @@ -0,0 +1,49 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + command: sleep infinity + networks: + - db + - redis + - mailer + volumes: + - ../..:/workspaces:cached + env_file: + - .env + db: + image: postgres:15.3-alpine + restart: unless-stopped + ports: + - 5432:5432 + networks: + - db + volumes: + - postgres-data:/var/lib/postgresql/data + env_file: + - .env + redis: + image: redis:latest + restart: unless-stopped + ports: + - 6379:6379 + networks: + - redis + mailer: + image: mailtutan/mailtutan:latest + restart: unless-stopped + ports: + - 1080:1080 + - 1025:1025 + networks: + - mailer + +volumes: + postgres-data: + + +networks: + db: + redis: + mailer: diff --git a/nixin_farm_ssr/.devcontainer/devcontainer.json b/nixin_farm_ssr/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b2d83c2 --- /dev/null +++ b/nixin_farm_ssr/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "Loco", + "dockerComposeFile": "compose.yaml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [ + 5150 + ] +} \ No newline at end of file diff --git a/nixin_farm_ssr/.github/workflows/ci.yaml b/nixin_farm_ssr/.github/workflows/ci.yaml new file mode 100644 index 0000000..75ba8a5 --- /dev/null +++ b/nixin_farm_ssr/.github/workflows/ci.yaml @@ -0,0 +1,102 @@ +name: CI +on: + push: + branches: + - master + - main + pull_request: + +env: + RUST_TOOLCHAIN: stable + TOOLCHAIN_PROFILE: minimal + +jobs: + rustfmt: + name: Check Style + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: rustfmt + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Run Clippy + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms + + test: + name: Run Tests + runs-on: ubuntu-latest + + permissions: + contents: read + + services: + redis: + image: redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - "6379:6379" + postgres: + image: postgres + env: + POSTGRES_DB: postgres_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + # Set health checks to wait until postgres has started + options: --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --all + env: + REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} + DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test + diff --git a/nixin_farm_ssr/.gitignore b/nixin_farm_ssr/.gitignore new file mode 100644 index 0000000..d83d21a --- /dev/null +++ b/nixin_farm_ssr/.gitignore @@ -0,0 +1,19 @@ +**/config/local.yaml +**/config/*.local.yaml +**/config/production.yaml + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# include cargo lock +!Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +*.sqlite \ No newline at end of file diff --git a/nixin_farm_ssr/.rustfmt.toml b/nixin_farm_ssr/.rustfmt.toml new file mode 100644 index 0000000..d862e08 --- /dev/null +++ b/nixin_farm_ssr/.rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 100 +use_small_heuristics = "Default" diff --git a/nixin_farm_ssr/Cargo.lock b/nixin_farm_ssr/Cargo.lock new file mode 100644 index 0000000..5568950 --- /dev/null +++ b/nixin_farm_ssr/Cargo.lock @@ -0,0 +1,5552 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[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 = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[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 = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[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 = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.3", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", + "tokio", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.2", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[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 = "auto-future" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http 1.1.0", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower 0.5.1", + "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 1.1.0", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "http 1.1.0", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "serde", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "axum-test" +version = "16.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3254184de359bbae2a8ca10b050870a7a4f1c8332a2c27d53f360b9835bb3911" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum", + "bytes", + "cookie", + "http 1.1.0", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower 0.5.1", + "url", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace_printer" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d28de81c708c843640982b66573df0f0168d87e42854b563971f326745aab7" +dependencies = [ + "btparse-stable", + "colored", + "regex", + "thiserror", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bb8" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10cf871f3ff2ce56432fddc2615ac7acc3aa22ca321f8fea800846fbb32f188" +dependencies = [ + "async-trait", + "futures-util", + "parking_lot", + "tokio", +] + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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 = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.72", + "syn_derive", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "btparse-stable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d75b8252ed252f881d1dc4482ae3c3854df6ee8183c1906bac50ff358f4f89f" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-unit" +version = "4.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" +dependencies = [ + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.5", + "stacker", +] + +[[package]] +name = "clap" +version = "4.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[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 = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +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 = "cron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" +dependencies = [ + "chrono", + "nom", + "once_cell", +] + +[[package]] +name = "cron_clock" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8699d8ed16e3db689f8ae04d8dc3c6666a4ba7e724e5a157884b7cc385d16b" +dependencies = [ + "chrono", + "nom", + "once_cell", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "cruet" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113a9e83d8f614be76de8df1f25bf9d0ea6e85ea573710a3d3f3abe1438ae49c" +dependencies = [ + "once_cell", + "regex", +] + +[[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 = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.72", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.72", +] + +[[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", + "serde", +] + +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[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 = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "duct" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + +[[package]] +name = "duct_sh" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6633cadba557545fbbe0299a2f9adc4bb2fc5fb238773f5e841e0c23d62146" +dependencies = [ + "duct", +] + +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "email-encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" +dependencies = [ + "base64 0.22.1", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46b7a0ac6570e31bfe2c6cf575a576a55af9893d1a6b30b4444e6e90b216bb84" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "english-to-cron" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16423ac933fee80f05a52b435a912d5b08edbbbfe936e0042ebb3accdf303da" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror", +] + +[[package]] +name = "fluent-template-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec7592cd1f45c1afe9084ce59c62a3a7c266c125c4c2ec97e95b0563c4aa914" +dependencies = [ + "flume 0.10.14", + "ignore", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "unic-langid", +] + +[[package]] +name = "fluent-templates" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3ef2c2152757885365abce32ddf682746062f1b6b3c0824a29fbed6ee4d080" +dependencies = [ + "arc-swap", + "fluent", + "fluent-bundle", + "fluent-langneg", + "fluent-syntax", + "fluent-template-macros", + "flume 0.10.14", + "heck 0.4.1", + "ignore", + "intl-memoizer", + "lazy_static", + "log", + "once_cell", + "serde_json", + "snafu 0.7.5", + "tera", + "unic-langid", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "spin", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +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 = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +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 = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[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.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.6.0", + "ignore", + "walkdir", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +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 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower 0.4.13", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.7", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "inherent" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "insta" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "pest", + "pest_derive", + "regex", + "serde", + "similar", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[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.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lettre" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a62049a808f1c4e2356a2a380bd5f2aca3b011b0b482cf3b914ba1731426969" +dependencies = [ + "async-trait", + "base64 0.22.1", + "chumsky", + "email-encoding", + "email_address", + "fastrand 2.1.0", + "futures-io", + "futures-util", + "hostname", + "httpdate", + "idna", + "mime", + "nom", + "percent-encoding", + "quoted_printable", + "rustls 0.23.12", + "rustls-pemfile 2.1.2", + "socket2 0.5.7", + "tokio", + "tokio-rustls", + "url", + "webpki-roots 0.26.3", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[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 = "loco-rs" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d90fd97a367bd7e46192e69877c22c02ed73675a6b793124b2f4327e203647" +dependencies = [ + "argon2", + "async-trait", + "axum", + "axum-extra", + "axum-test", + "backtrace_printer", + "bb8", + "byte-unit", + "bytes", + "cargo_metadata", + "cfg-if", + "chrono", + "clap", + "colored", + "dialoguer", + "duct", + "duct_sh", + "english-to-cron", + "fs-err", + "futures-util", + "heck 0.4.1", + "hyper", + "include_dir", + "ipnetwork", + "jsonwebtoken", + "lazy_static", + "lettre", + "mime", + "moka", + "object_store", + "rand", + "regex", + "rrgen", + "rusty-sidekiq", + "sea-orm", + "sea-orm-migration", + "serde", + "serde_json", + "serde_variant", + "serde_yaml", + "sqlx", + "tera", + "thiserror", + "tokio", + "tokio-cron-scheduler", + "tower 0.4.13", + "tower-http", + "tracing", + "tracing-appender", + "tracing-subscriber", + "ulid", + "uuid", + "validator", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] + +[[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 = "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 = "migration" +version = "0.1.0" +dependencies = [ + "async-std", + "loco-rs", + "sea-orm-migration", +] + +[[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.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "nixin_farm_ssr" +version = "0.1.0" +dependencies = [ + "async-trait", + "axum", + "chrono", + "fluent-templates", + "include_dir", + "insta", + "loco-rs", + "migration", + "rstest", + "sea-orm", + "serde", + "serde_json", + "serial_test", + "tokio", + "tracing", + "tracing-subscriber", + "unic-langid", + "uuid", + "validator", +] + +[[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" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[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 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a0c4b3a0e31f8b66f71ad8064521efa773910196e2cde791436f13409f3b45" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "humantime", + "itertools", + "parking_lot", + "percent-encoding", + "snafu 0.8.5", + "tokio", + "tracing", + "url", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_pipe" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[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 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[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 = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "pest_meta" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quoted_printable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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 = "raw-cpuid" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redis" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8455fa3621f6b41c514946de66ea0531f57ca017b2e6c7cc368035ea5b46df" +dependencies = [ + "async-trait", + "bytes", + "combine", + "futures-util", + "itoa", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reserve-port" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9838134a2bfaa8e1f40738fcc972ac799de6e0e06b5157acb95fc2b05a0ea283" +dependencies = [ + "lazy_static", + "thiserror", +] + +[[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 = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rrgen" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40013551787f9f535e7dbc8dafc164591d941aeae48881a385d8b0393dd45f6" +dependencies = [ + "cruet", + "fs-err", + "glob", + "heck 0.4.1", + "regex", + "serde", + "serde_json", + "serde_regex", + "serde_yaml", + "tera", + "thiserror", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.72", + "unicode-ident", +] + +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b748410c0afdef2ebbe3685a6a862e2ee937127cdaae623336a459451c8d57" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "mime", + "mime_guess", + "rand", + "thiserror", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rusty-sidekiq" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a00db3916faeea070039864f98d4fd759d96fc07722571a4918d996fea5621" +dependencies = [ + "async-trait", + "bb8", + "chrono", + "cron_clock", + "gethostname", + "heck 0.4.1", + "hex", + "num_cpus", + "rand", + "redis", + "serde", + "serde_json", + "sha2", + "slog-term", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[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 = "scc" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fadf67e3cf23f8b11a6c8c48a16cb2437381503615acd91094ec7b4686a5a53" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sdd" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85f05a494052771fc5bd0619742363b5e24e5ad72ab3111ec2e27925b8edc5f3" + +[[package]] +name = "sea-bae" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "sea-orm" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d4ec1cdd8bdd3553d3c946079f58efa33fedc477f32603652652abcef96fe6" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-cli" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d525eee597817631f800857b0c2fe8645ec9c65b4304176a44bf14b93366009e" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "glob", + "regex", + "sea-schema", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f363ead48b625a6f8f905322a820464f728fa4fe4f1c222bed5234ccf8fb8555" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.72", + "unicode-ident", +] + +[[package]] +name = "sea-orm-migration" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df62369752f91b9295f8d71c146d84f818301a9ae7295106ebe8bbaa989a072" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "futures", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sea-query" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5073b2cfed767511a57d18115f3b3d8bcb5690bf8c89518caec6cb22c0cd74" +dependencies = [ + "bigdecimal", + "chrono", + "educe", + "inherent", + "ordered-float", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754965d4aee6145bec25d0898e5c931e6c22859789ce62fd85a42a15ed5a8ce3" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.72", + "thiserror", +] + +[[package]] +name = "sea-schema" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad52149fc81836ea7424c3425d8f6ed8ad448dd16d2e4f6a3907ba46f3f2fd78" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.0.4", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "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_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_variant" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[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 = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[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 = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[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 = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive 0.7.5", +] + +[[package]] +name = "snafu" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +dependencies = [ + "snafu-derive 0.8.5", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "snafu-derive" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.11", + "atoi", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener 2.5.3", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rust_decimal", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots 0.25.4", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bigdecimal", + "bitflags 2.6.0", + "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", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bigdecimal", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume 0.11.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.1.0", + "rustix 0.38.34", + "windows-sys 0.52.0", +] + +[[package]] +name = "tera" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.7", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-cron-scheduler" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b9480125554f0ace1c3c3797a24b5cc56c6a7cd82c739db35fb54c4dc046f3" +dependencies = [ + "chrono", + "cron", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "async-compression", + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "ulid" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f903f293d11f31c0c29e4148f6dc0d033a7f80cebc0282bea147611667d289" +dependencies = [ + "getrandom", + "rand", + "web-time", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da1cd2c042d3c7569a1008806b02039e7a4a2bdf8f8e96bd3c792434a0e275e" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed7f4237ba393424195053097c1516bd4590dc82b84f2f97c5c69e12704555b" +dependencies = [ + "proc-macro-hack", + "quote", + "syn 2.0.72", + "unic-langid-impl", +] + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "rand", + "serde", +] + +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[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.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.72", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.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" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "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-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.12+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/nixin_farm_ssr/Cargo.toml b/nixin_farm_ssr/Cargo.toml new file mode 100644 index 0000000..b57fa0c --- /dev/null +++ b/nixin_farm_ssr/Cargo.toml @@ -0,0 +1,55 @@ +[workspace] + +[package] +name = "nixin_farm_ssr" +version = "0.1.0" +edition = "2021" +publish = false +default-run = "nixin_farm_ssr-cli" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +loco-rs = { version = "0.10.1" } +migration = { path = "migration" } + +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1.33.0", default-features = false } +async-trait = "0.1.74" +tracing = "0.1.40" +chrono = "0.4" +validator = { version = "0.18" } +sea-orm = { version = "1.0.0", features = [ + "sqlx-sqlite", + "sqlx-postgres", + "runtime-tokio-rustls", + "macros", +] } + +axum = "0.7.5" +include_dir = "0.7" +uuid = { version = "1.6.0", features = ["v4"] } +tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] } + +# view engine i18n +fluent-templates = { version = "0.8.0", features = ["tera"] } +unic-langid = "0.9.4" +# /view engine + +[[bin]] +name = "nixin_farm_ssr-cli" +path = "src/bin/main.rs" +required-features = [] + +[[bin]] +name = "tool" +path = "src/bin/tool.rs" +required-features = [] + +[dev-dependencies] +serial_test = "3.1.1" +rstest = "0.21.0" +loco-rs = { version = "0.10.1", features = ["testing"] } +insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } diff --git a/nixin_farm_ssr/README.md b/nixin_farm_ssr/README.md new file mode 100644 index 0000000..43b9bdd --- /dev/null +++ b/nixin_farm_ssr/README.md @@ -0,0 +1,58 @@ +# Welcome to Loco :train: + +[Loco](https://loco.rs) is a web and API framework running on Rust. + +This is the **SaaS starter** which includes a `User` model and authentication based on JWT. +It also include configuration sections that help you pick either a frontend or a server-side template set up for your fullstack server. + + +## Quick Start + +```sh +cargo loco start +``` + +```sh +$ cargo loco start +Finished dev [unoptimized + debuginfo] target(s) in 21.63s + Running `target/debug/myapp start` + + : + : + : + +controller/app_routes.rs:203: [Middleware] Adding log trace id + + ▄ ▀ + ▀ ▄ + ▄ ▀ ▄ ▄ ▄▀ + ▄ ▀▄▄ + ▄ ▀ ▀ ▀▄▀█▄ + ▀█▄ +▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ + ██████ █████ ███ █████ ███ █████ ███ ▀█ + ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ + ██████ █████ ███ █████ █████ ███ ████▄ + ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ + ██████ █████ ███ ████ ███ █████ ███ ████▀ + ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ + ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ + https://loco.rs + +environment: development + database: automigrate + logger: debug +compilation: debug + modes: server + +listening on http://localhost:5150 +``` + +## Full Stack Serving + +You can check your [configuration](config/development.yaml) to pick either frontend setup or server-side rendered template, and activate the relevant configuration sections. + + +## Getting help + +Check out [a quick tour](https://loco.rs/docs/getting-started/tour/) or [the complete guide](https://loco.rs/docs/getting-started/guide/). diff --git a/nixin_farm_ssr/assets/i18n/de-DE/main.ftl b/nixin_farm_ssr/assets/i18n/de-DE/main.ftl new file mode 100644 index 0000000..ced609f --- /dev/null +++ b/nixin_farm_ssr/assets/i18n/de-DE/main.ftl @@ -0,0 +1,4 @@ +hello-world = Hallo Welt! +greeting = Hallochen { $name }! + .placeholder = Hallo Freund! +about = Uber diff --git a/nixin_farm_ssr/assets/i18n/en-US/main.ftl b/nixin_farm_ssr/assets/i18n/en-US/main.ftl new file mode 100644 index 0000000..9d4d5e7 --- /dev/null +++ b/nixin_farm_ssr/assets/i18n/en-US/main.ftl @@ -0,0 +1,10 @@ +hello-world = Hello World! +greeting = Hello { $name }! + .placeholder = Hello Friend! +about = About +simple = simple text +reference = simple text with a reference: { -something } +parameter = text with a { $param } +parameter2 = text one { $param } second { $multi-word-param } +email = text with an EMAIL("example@example.org") +fallback = this should fall back diff --git a/nixin_farm_ssr/assets/i18n/shared.ftl b/nixin_farm_ssr/assets/i18n/shared.ftl new file mode 100644 index 0000000..f169eca --- /dev/null +++ b/nixin_farm_ssr/assets/i18n/shared.ftl @@ -0,0 +1 @@ +-something = foo diff --git a/nixin_farm_ssr/assets/static/404.html b/nixin_farm_ssr/assets/static/404.html new file mode 100644 index 0000000..66e78fb --- /dev/null +++ b/nixin_farm_ssr/assets/static/404.html @@ -0,0 +1,3 @@ + +not found :-( + diff --git a/nixin_farm_ssr/assets/static/image.png b/nixin_farm_ssr/assets/static/image.png new file mode 100644 index 0000000..fa5a095 Binary files /dev/null and b/nixin_farm_ssr/assets/static/image.png differ diff --git a/nixin_farm_ssr/assets/views/base.html b/nixin_farm_ssr/assets/views/base.html new file mode 100644 index 0000000..d0d8c43 --- /dev/null +++ b/nixin_farm_ssr/assets/views/base.html @@ -0,0 +1,26 @@ + + + + + + {% block title %}{% endblock title %} + + + + {% block head %} + + {% endblock head %} + + + +
+ {% block content %} + {% endblock content %} +
+ + {% block js %} + + {% endblock js %} + + + \ No newline at end of file diff --git a/nixin_farm_ssr/assets/views/home/hello.html b/nixin_farm_ssr/assets/views/home/hello.html new file mode 100644 index 0000000..6b97c39 --- /dev/null +++ b/nixin_farm_ssr/assets/views/home/hello.html @@ -0,0 +1,12 @@ + + +
+ find this tera template at assets/views/home/hello.html: +
+
+ {{ t(key="hello-world", lang="en-US") }}, +
+ {{ t(key="hello-world", lang="de-DE") }} + + + \ No newline at end of file diff --git a/nixin_farm_ssr/assets/views/server/create.html b/nixin_farm_ssr/assets/views/server/create.html new file mode 100644 index 0000000..9dc318c --- /dev/null +++ b/nixin_farm_ssr/assets/views/server/create.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block title %} +Create server +{% endblock title %} + +{% block content %} +
+
+

Create new server

+
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+
+
+{% endblock content %} + +{% block js %} + +{% endblock js %} \ No newline at end of file diff --git a/nixin_farm_ssr/assets/views/server/edit.html b/nixin_farm_ssr/assets/views/server/edit.html new file mode 100644 index 0000000..b2b8c7f --- /dev/null +++ b/nixin_farm_ssr/assets/views/server/edit.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} + +{% block title %} +Edit server: {{ item.id }} +{% endblock title %} + +{% block content %} +

Edit server: {{ item.id }}

+
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ + +
+
+ +
+
+Back to server +
+{% endblock content %} + +{% block js %} + +{% endblock js %} \ No newline at end of file diff --git a/nixin_farm_ssr/assets/views/server/list.html b/nixin_farm_ssr/assets/views/server/list.html new file mode 100644 index 0000000..5e732dd --- /dev/null +++ b/nixin_farm_ssr/assets/views/server/list.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %} +List of server +{% endblock title %} + +{% block content %} +

server

+
+ {% for item in items %} +
+
+ +
+
+ +
+ Edit + View +
+ {% endfor %} + +
+
+ New server +
+{% endblock content %} diff --git a/nixin_farm_ssr/assets/views/server/show.html b/nixin_farm_ssr/assets/views/server/show.html new file mode 100644 index 0000000..aff640a --- /dev/null +++ b/nixin_farm_ssr/assets/views/server/show.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %} +View server: {{ item.id }} +{% endblock title %} + +{% block content %} +

View server: {{ item.id }}

+
+
+ +
+
+ +
+
+Back to servers +
+{% endblock content %} diff --git a/nixin_farm_ssr/config/development.yaml b/nixin_farm_ssr/config/development.yaml new file mode 100644 index 0000000..020e45c --- /dev/null +++ b/nixin_farm_ssr/config/development.yaml @@ -0,0 +1,138 @@ +# Loco configuration file documentation + +# Application logging configuration +logger: + # Enable or disable logging. + enable: true + # Enable pretty backtrace (sets RUST_BACKTRACE=1) + pretty_backtrace: true + # Log level, options: trace, debug, info, warn or error. + level: debug + # Define the logging format. options: compact, pretty or json + format: compact + # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries + # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. + # override_filter: trace + +# Web server configuration +server: + # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} + port: 5150 + # The UI hostname or IP address that mailers will point to. + host: http://localhost + # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block + middlewares: + # ############################################# + # Full stack SaaS asset serving + # ############################################# + # + # You can choose from: + # + # (1) Server-side rendered templates (like erb in Ruby, ejs in nodejs) based on the Tera templating engine. + # (2) Client-side frontend set up + # + # You can also choose both. + # + # Uncomment the relevant sections below: + + # + # (1) Server-side static assets config + # ==================================== + # + # for use with the view_engine in initializers/view_engine.rs + static: + enable: true + must_exist: true + precompressed: false + folder: + uri: "/static" + path: "assets/static" + fallback: "assets/static/404.html" + + # + # (2) Client side app static config + # ================================= + # + # Note that you need to go in `frontend` and run your frontend build first, + # e.g.: $ npm install & npm build + # + # (client-block-start) + # static: + # enable: true + # must_exist: true + # precompressed: false + # folder: + # uri: "/" + # path: "frontend/dist" + # fallback: "frontend/dist/index.html" + # (client-block-end) + # + +# Worker Configuration +workers: + # specifies the worker mode. Options: + # - BackgroundQueue - Workers operate asynchronously in the background, processing queued. + # - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed. + # - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities. + mode: BackgroundAsync + +# Mailer Configuration. +mailer: + # SMTP mailer configuration. + smtp: + # Enable/Disable smtp mailer. + enable: true + # SMTP server host. e.x localhost, smtp.gmail.com + host: {{ get_env(name="MAILER_HOST", default="localhost") }} + # SMTP server port + port: 1025 + # Use secure connection (SSL/TLS). + secure: false + # auth: + # user: + # password: + +# Initializers Configuration +# initializers: +# oauth2: +# authorization_code: # Authorization code grant type +# - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. +# ... other fields + +# Database Configuration +database: + # Database connection URI + uri: {{ get_env(name="DATABASE_URL", default="sqlite://nixin_farm_ssr_development.sqlite?mode=rwc") }} + # When enabled, the sql query will be logged. + enable_logging: false + # Set the timeout duration when acquiring a connection. + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} + # Set the idle duration before closing a connection. + idle_timeout: 500 + # Minimum number of connections for a pool. + min_connections: 1 + # Maximum number of connections for a pool. + max_connections: 1 + # Run migration up when application loaded + auto_migrate: true + # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_truncate: false + # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_recreate: false + +# Queue Configuration +queue: + kind: Redis + # Redis connection URI + uri: {{ get_env(name="REDIS_URL", default="redis://127.0.0.1") }} + # Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_flush: false + +# Authentication Configuration +auth: + # JWT authentication + jwt: + # Secret key for token generation and verification + secret: jECGaGSsMtQmKyYyGuk7 + # Token expiration time in seconds + expiration: 604800 # 7 days diff --git a/nixin_farm_ssr/config/test.yaml b/nixin_farm_ssr/config/test.yaml new file mode 100644 index 0000000..f9a8796 --- /dev/null +++ b/nixin_farm_ssr/config/test.yaml @@ -0,0 +1,101 @@ +# Loco configuration file documentation + +# Application logging configuration +logger: + # Enable or disable logging. + enable: false + # Log level, options: trace, debug, info, warn or error. + level: debug + # Define the logging format. options: compact, pretty or json + format: compact + # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries + # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. + # override_filter: trace + +# Web server configuration +server: + # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} + port: 5150 + # The UI hostname or IP address that mailers will point to. + host: http://localhost + # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block + middlewares: + static: + enable: true + precompressed: false + must_exist: false + folder: + uri: "/" + path: "frontend/dist" + fallback: "frontend/dist/index.html" + +# Worker Configuration +workers: + # specifies the worker mode. Options: + # - BackgroundQueue - Workers operate asynchronously in the background, processing queued. + # - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed. + # - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities. + mode: ForegroundBlocking + +# Mailer Configuration. +mailer: + # SMTP mailer configuration. + smtp: + # Enable/Disable smtp mailer. + enable: true + # SMTP server host. e.x localhost, smtp.gmail.com + host: localhost + # SMTP server port + port: 1025 + # Use secure connection (SSL/TLS). + secure: false + # auth: + # user: + # password: + stub: true + +# Initializers Configuration +# initializers: +# oauth2: +# authorization_code: # Authorization code grant type +# - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. +# ... other fields + +# Database Configuration +database: + # Database connection URI + uri: {{get_env(name="DATABASE_URL", default="sqlite://nixin_farm_ssr_test.sqlite?mode=rwc")}} + # When enabled, the sql query will be logged. + enable_logging: false + # Set the timeout duration when acquiring a connection. + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} + # Set the idle duration before closing a connection. + idle_timeout: 500 + # Minimum number of connections for a pool. + min_connections: 1 + # Maximum number of connections for a pool. + max_connections: 1 + # Run migration up when application loaded + auto_migrate: true + # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_truncate: true + # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_recreate: false + +# Queue Configuration +queue: + kind: Redis + # Redis connection URI + uri: {{get_env(name="REDIS_URL", default="redis://127.0.0.1")}} + # Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode + dangerously_flush: false + +# Authentication Configuration +auth: + # JWT authentication + jwt: + # Secret key for token generation and verification + secret: bmGU9QRlQu5HAI6Kbbur + # Token expiration time in seconds + expiration: 604800 # 7 days + diff --git a/nixin_farm_ssr/examples/playground.rs b/nixin_farm_ssr/examples/playground.rs new file mode 100644 index 0000000..d17226c --- /dev/null +++ b/nixin_farm_ssr/examples/playground.rs @@ -0,0 +1,21 @@ +#[allow(unused_imports)] +use loco_rs::{cli::playground, prelude::*}; +use nixin_farm_ssr::app::App; + +#[tokio::main] +async fn main() -> loco_rs::Result<()> { + let _ctx = playground::().await?; + + // let active_model: articles::ActiveModel = ActiveModel { + // title: Set(Some("how to build apps in 3 steps".to_string())), + // content: Set(Some("use Loco: https://loco.rs".to_string())), + // ..Default::default() + // }; + // active_model.insert(&ctx.db).await.unwrap(); + + // let res = articles::Entity::find().all(&ctx.db).await.unwrap(); + // println!("{:?}", res); + println!("welcome to playground. edit me at `examples/playground.rs`"); + + Ok(()) +} diff --git a/nixin_farm_ssr/frontend/.gitignore b/nixin_farm_ssr/frontend/.gitignore new file mode 100644 index 0000000..a1dccae --- /dev/null +++ b/nixin_farm_ssr/frontend/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist-ssr +dist/ +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Common local dotenv files popularised by Create React App & Next.js +# https://rsbuild.dev/guide/advanced/env-vars#env-file +.env.local +.env.development.local +.env.production.local +.env.test.local diff --git a/nixin_farm_ssr/frontend/README.md b/nixin_farm_ssr/frontend/README.md new file mode 100644 index 0000000..9fd9aed --- /dev/null +++ b/nixin_farm_ssr/frontend/README.md @@ -0,0 +1,42 @@ +# SaaS Frontend + +## Batteries included + +- [TypeScript](https://www.typescriptlang.org/): A typed superset of JavaScript +- [Rsbuild](https://rsbuild.dev/): A Rust-based web build tool +- [Biome](https://biomejs.dev/): A Rust-based formatter and sensible linter for the web +- [React](https://reactjs.org/): A JavaScript library for building user interfaces + +If you don't like React for some reason, Rsbuild makes it easy to replace it with something else! + +# Development + +To get started with the development of the SaaS frontend, follow these steps: + +### 1. Install Packages + +Use the following command to install the required packages using pnpm: + +```sh +pnpm install +``` + +### 2. Run in Development Mode + +Once the packages are installed, run your frontend application in development mode with the following command: + +```sh +pnpm dev +``` + +This will start the development frontend server serving via vit + +### 3. Build The application + +To build your application run the following command: + +```sh +pnpm build +``` + +After the build `dist` folder is ready to served by loco. run loco `cargo loco start` and the frontend application will served via Loco \ No newline at end of file diff --git a/nixin_farm_ssr/frontend/biome.json b/nixin_farm_ssr/frontend/biome.json new file mode 100644 index 0000000..0dd3251 --- /dev/null +++ b/nixin_farm_ssr/frontend/biome.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "enabled": true, + "indentStyle": "space" + } + }, + "json": { + "formatter": { + "enabled": true, + "indentStyle": "space" + } + } +} diff --git a/nixin_farm_ssr/frontend/package.json b/nixin_farm_ssr/frontend/package.json new file mode 100644 index 0000000..dd7a791 --- /dev/null +++ b/nixin_farm_ssr/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "rsbuild dev --open", + "build": "rsbuild build", + "lint": "biome check src/", + "preview": "rsbuild preview" + }, + "dependencies": { + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@biomejs/biome": "^1", + "@rsbuild/core": "^1", + "@rsbuild/plugin-react": "^1", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} diff --git a/nixin_farm_ssr/frontend/rsbuild.config.ts b/nixin_farm_ssr/frontend/rsbuild.config.ts new file mode 100644 index 0000000..d86582c --- /dev/null +++ b/nixin_farm_ssr/frontend/rsbuild.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; + +// https://rsbuild.dev/guide/basic/configure-rsbuild +export default defineConfig({ + plugins: [pluginReact()], + html: { + favicon: "src/assets/favicon.ico", + title: "Loco SaaS Starter", + }, + server: { + proxy: { + "/api": { + target: "http://127.0.0.1:5150", + changeOrigin: true, + secure: false, + }, + }, + }, +}); diff --git a/nixin_farm_ssr/frontend/src/LocoSplash.tsx b/nixin_farm_ssr/frontend/src/LocoSplash.tsx new file mode 100644 index 0000000..cb96ace --- /dev/null +++ b/nixin_farm_ssr/frontend/src/LocoSplash.tsx @@ -0,0 +1,105 @@ +export const LocoSplash = () => { + return ( +
+
+
+ Loco + +
+
+
+

Loco: SaaS application

+ Loco logo +
+ +
+ ); +}; diff --git a/nixin_farm_ssr/frontend/src/assets/favicon.ico b/nixin_farm_ssr/frontend/src/assets/favicon.ico new file mode 100644 index 0000000..07f5b73 Binary files /dev/null and b/nixin_farm_ssr/frontend/src/assets/favicon.ico differ diff --git a/nixin_farm_ssr/frontend/src/env.d.ts b/nixin_farm_ssr/frontend/src/env.d.ts new file mode 100644 index 0000000..b0ac762 --- /dev/null +++ b/nixin_farm_ssr/frontend/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/nixin_farm_ssr/frontend/src/index.css b/nixin_farm_ssr/frontend/src/index.css new file mode 100644 index 0000000..854aa19 --- /dev/null +++ b/nixin_farm_ssr/frontend/src/index.css @@ -0,0 +1,100 @@ +body { + margin: 0; + font-family: "Arimo", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + background: #212529; + color: #dee2e6; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(29, 45, 53, 0) +} + +ul { + margin-top: 0; + margin-bottom: 1rem; + list-style: none; +} + +a { + color: #dee2e6; + text-decoration: none +} + +.container { + max-width: 1320px; + padding-right: var(--bs-gutter-x, 24px); + padding-left: var(--bs-gutter-x, 24px); + margin-right: auto; + margin-left: auto +} + + +.navbar { + padding-top: .5rem; + padding-bottom: .5rem +} + +.navbar .container { + display: flex; + justify-content: space-between +} + +.navbar-nav { + margin-bottom: 0; +} +.navbar-nav li { + display: inline-flex; + margin-right: 10px; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; +} + +body { + font-size: 1rem; + padding-top: 6rem !important +} + +.navbar { + border-bottom: 1px solid #2a2f34; +} + + +.logo { + max-width: 1280px; + margin: 0 auto; + text-align: center; +} + +.logo img { + width: 250px; +} +footer { + position: absolute; + bottom: 0; + width: 100%; + text-align: center; +} + +footer ul { + display: inline-block; + padding: 0; +} + +footer ul li { + display: inline-flex; + align-items: center; + margin: 0 5px; + list-style: none; +} + +footer ul li:not(:last-child) { + border-right: 1px solid #ccc; + padding-right: 5px; + height: 15px; +} \ No newline at end of file diff --git a/nixin_farm_ssr/frontend/src/index.tsx b/nixin_farm_ssr/frontend/src/index.tsx new file mode 100644 index 0000000..8d48a1d --- /dev/null +++ b/nixin_farm_ssr/frontend/src/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { LocoSplash } from "./LocoSplash"; + +import "./index.css"; + +const root = document.getElementById("root"); + +if (!root) { + throw new Error("No root element found"); +} + +ReactDOM.createRoot(root).render( + + + , +); diff --git a/nixin_farm_ssr/frontend/tsconfig.json b/nixin_farm_ssr/frontend/tsconfig.json new file mode 100644 index 0000000..e6b9bdf --- /dev/null +++ b/nixin_farm_ssr/frontend/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["DOM", "ES2020"], + "module": "ESNext", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "isolatedModules": true, + "resolveJsonModule": true, + "moduleResolution": "bundler", + "useDefineForClassFields": true + }, + "include": ["src"] +} diff --git a/nixin_farm_ssr/migration/Cargo.toml b/nixin_farm_ssr/migration/Cargo.toml new file mode 100644 index 0000000..bf79cfe --- /dev/null +++ b/nixin_farm_ssr/migration/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } +loco-rs = { version = "0.10.1" } + +[dependencies.sea-orm-migration] +version = "1.0.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature +] diff --git a/nixin_farm_ssr/migration/src/lib.rs b/nixin_farm_ssr/migration/src/lib.rs new file mode 100644 index 0000000..056ac3e --- /dev/null +++ b/nixin_farm_ssr/migration/src/lib.rs @@ -0,0 +1,20 @@ +#![allow(elided_lifetimes_in_paths)] +#![allow(clippy::wildcard_imports)] +pub use sea_orm_migration::prelude::*; + +mod m20220101_000001_users; +mod m20231103_114510_notes; + +mod m20241016_181828_servers; +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20220101_000001_users::Migration), + Box::new(m20231103_114510_notes::Migration), + Box::new(m20241016_181828_servers::Migration), + ] + } +} \ No newline at end of file diff --git a/nixin_farm_ssr/migration/src/m20220101_000001_users.rs b/nixin_farm_ssr/migration/src/m20220101_000001_users.rs new file mode 100644 index 0000000..936ad3d --- /dev/null +++ b/nixin_farm_ssr/migration/src/m20220101_000001_users.rs @@ -0,0 +1,50 @@ +use loco_rs::schema::table_auto_tz; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let table = table_auto_tz(Users::Table) + .col(pk_auto(Users::Id)) + .col(uuid(Users::Pid)) + .col(string_uniq(Users::Email)) + .col(string(Users::Password)) + .col(string(Users::ApiKey).unique_key()) + .col(string(Users::Name)) + .col(string_null(Users::ResetToken)) + .col(timestamp_with_time_zone_null(Users::ResetSentAt)) + .col(string_null(Users::EmailVerificationToken)) + .col(timestamp_with_time_zone_null( + Users::EmailVerificationSentAt, + )) + .col(timestamp_with_time_zone_null(Users::EmailVerifiedAt)) + .to_owned(); + manager.create_table(table).await?; + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Users::Table).to_owned()) + .await + } +} + +#[derive(Iden)] +pub enum Users { + Table, + Id, + Pid, + Email, + Name, + Password, + ApiKey, + ResetToken, + ResetSentAt, + EmailVerificationToken, + EmailVerificationSentAt, + EmailVerifiedAt, +} diff --git a/nixin_farm_ssr/migration/src/m20231103_114510_notes.rs b/nixin_farm_ssr/migration/src/m20231103_114510_notes.rs new file mode 100644 index 0000000..d0d8a5a --- /dev/null +++ b/nixin_farm_ssr/migration/src/m20231103_114510_notes.rs @@ -0,0 +1,34 @@ +use loco_rs::schema::table_auto_tz; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + table_auto_tz(Notes::Table) + .col(pk_auto(Notes::Id)) + .col(string_null(Notes::Title)) + .col(string_null(Notes::Content)) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Notes::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Notes { + Table, + Id, + Title, + Content, +} diff --git a/nixin_farm_ssr/migration/src/m20241016_181828_servers.rs b/nixin_farm_ssr/migration/src/m20241016_181828_servers.rs new file mode 100644 index 0000000..30a9518 --- /dev/null +++ b/nixin_farm_ssr/migration/src/m20241016_181828_servers.rs @@ -0,0 +1,37 @@ +use loco_rs::schema::table_auto_tz; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + table_auto_tz(Servers::Table) + .col(pk_auto(Servers::Id)) + .col(string_null(Servers::Name)) + .col(string_null(Servers::Domain)) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Servers::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Servers { + Table, + Id, + Name, + Domain, + +} + + diff --git a/nixin_farm_ssr/src/app.rs b/nixin_farm_ssr/src/app.rs new file mode 100644 index 0000000..1c9a468 --- /dev/null +++ b/nixin_farm_ssr/src/app.rs @@ -0,0 +1,79 @@ +use std::path::Path; + +use async_trait::async_trait; +use loco_rs::{ + app::{AppContext, Hooks, Initializer}, + bgworker::{BackgroundWorker, Queue}, + boot::{create_app, BootResult, StartMode}, + controller::AppRoutes, + db::{self, truncate_table}, + environment::Environment, + task::Tasks, + Result, +}; +use migration::Migrator; +use sea_orm::DatabaseConnection; + +use crate::{ + controllers, initializers, + models::_entities::{notes, users}, + tasks, + workers::downloader::DownloadWorker, +}; + +pub struct App; +#[async_trait] +impl Hooks for App { + fn app_name() -> &'static str { + env!("CARGO_CRATE_NAME") + } + + fn app_version() -> String { + format!( + "{} ({})", + env!("CARGO_PKG_VERSION"), + option_env!("BUILD_SHA") + .or(option_env!("GITHUB_SHA")) + .unwrap_or("dev") + ) + } + + async fn boot(mode: StartMode, environment: &Environment) -> Result { + create_app::(mode, environment).await + } + + async fn initializers(_ctx: &AppContext) -> Result>> { + Ok(vec![Box::new( + initializers::view_engine::ViewEngineInitializer, + )]) + } + + fn routes(_ctx: &AppContext) -> AppRoutes { + AppRoutes::with_default_routes() + .add_route(controllers::server::routes()) + .add_route(controllers::notes::routes()) + .add_route(controllers::auth::routes()) + .add_route(controllers::user::routes()) + } + + async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> { + queue.register(DownloadWorker::build(ctx)).await?; + Ok(()) + } + + fn register_tasks(tasks: &mut Tasks) { + tasks.register(tasks::seed::SeedData); + } + + async fn truncate(db: &DatabaseConnection) -> Result<()> { + truncate_table(db, users::Entity).await?; + truncate_table(db, notes::Entity).await?; + Ok(()) + } + + async fn seed(db: &DatabaseConnection, base: &Path) -> Result<()> { + db::seed::(db, &base.join("users.yaml").display().to_string()).await?; + db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; + Ok(()) + } +} \ No newline at end of file diff --git a/nixin_farm_ssr/src/bin/main.rs b/nixin_farm_ssr/src/bin/main.rs new file mode 100644 index 0000000..be3974c --- /dev/null +++ b/nixin_farm_ssr/src/bin/main.rs @@ -0,0 +1,8 @@ +use loco_rs::cli; +use nixin_farm_ssr::app::App; +use migration::Migrator; + +#[tokio::main] +async fn main() -> loco_rs::Result<()> { + cli::main::().await +} diff --git a/nixin_farm_ssr/src/bin/tool.rs b/nixin_farm_ssr/src/bin/tool.rs new file mode 100644 index 0000000..be3974c --- /dev/null +++ b/nixin_farm_ssr/src/bin/tool.rs @@ -0,0 +1,8 @@ +use loco_rs::cli; +use nixin_farm_ssr::app::App; +use migration::Migrator; + +#[tokio::main] +async fn main() -> loco_rs::Result<()> { + cli::main::().await +} diff --git a/nixin_farm_ssr/src/controllers/auth.rs b/nixin_farm_ssr/src/controllers/auth.rs new file mode 100644 index 0000000..9978d9d --- /dev/null +++ b/nixin_farm_ssr/src/controllers/auth.rs @@ -0,0 +1,150 @@ +use axum::debug_handler; +use loco_rs::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::{ + mailers::auth::AuthMailer, + models::{ + _entities::users, + users::{LoginParams, RegisterParams}, + }, + views::auth::LoginResponse, +}; +#[derive(Debug, Deserialize, Serialize)] +pub struct VerifyParams { + pub token: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ForgotParams { + pub email: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ResetParams { + pub token: String, + pub password: String, +} + +/// Register function creates a new user with the given parameters and sends a +/// welcome email to the user +#[debug_handler] +async fn register( + State(ctx): State, + Json(params): Json, +) -> Result { + let res = users::Model::create_with_password(&ctx.db, ¶ms).await; + + let user = match res { + Ok(user) => user, + Err(err) => { + tracing::info!( + message = err.to_string(), + user_email = ¶ms.email, + "could not register user", + ); + return format::json(()); + } + }; + + let user = user + .into_active_model() + .set_email_verification_sent(&ctx.db) + .await?; + + AuthMailer::send_welcome(&ctx, &user).await?; + + format::json(()) +} + +/// Verify register user. if the user not verified his email, he can't login to +/// the system. +#[debug_handler] +async fn verify( + State(ctx): State, + Json(params): Json, +) -> Result { + let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; + + if user.email_verified_at.is_some() { + tracing::info!(pid = user.pid.to_string(), "user already verified"); + } else { + let active_model = user.into_active_model(); + let user = active_model.verified(&ctx.db).await?; + tracing::info!(pid = user.pid.to_string(), "user verified"); + } + + format::json(()) +} + +/// In case the user forgot his password this endpoints generate a forgot token +/// and send email to the user. In case the email not found in our DB, we are +/// returning a valid request for for security reasons (not exposing users DB +/// list). +#[debug_handler] +async fn forgot( + State(ctx): State, + Json(params): Json, +) -> Result { + let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { + // we don't want to expose our users email. if the email is invalid we still + // returning success to the caller + return format::json(()); + }; + + let user = user + .into_active_model() + .set_forgot_password_sent(&ctx.db) + .await?; + + AuthMailer::forgot_password(&ctx, &user).await?; + + format::json(()) +} + +/// reset user password by the given parameters +#[debug_handler] +async fn reset(State(ctx): State, Json(params): Json) -> Result { + let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { + // we don't want to expose our users email. if the email is invalid we still + // returning success to the caller + tracing::info!("reset token not found"); + + return format::json(()); + }; + user.into_active_model() + .reset_password(&ctx.db, ¶ms.password) + .await?; + + format::json(()) +} + +/// Creates a user login and returns a token +#[debug_handler] +async fn login(State(ctx): State, Json(params): Json) -> Result { + let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; + + let valid = user.verify_password(¶ms.password); + + if !valid { + return unauthorized("unauthorized!"); + } + + let jwt_secret = ctx.config.get_jwt_config()?; + + let token = user + .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) + .or_else(|_| unauthorized("unauthorized!"))?; + + format::json(LoginResponse::new(&user, &token)) +} + +pub fn routes() -> Routes { + Routes::new() + .prefix("api/auth") + .add("/register", post(register)) + .add("/verify", post(verify)) + .add("/login", post(login)) + .add("/forgot", post(forgot)) + .add("/reset", post(reset)) +} diff --git a/nixin_farm_ssr/src/controllers/mod.rs b/nixin_farm_ssr/src/controllers/mod.rs new file mode 100644 index 0000000..baf6605 --- /dev/null +++ b/nixin_farm_ssr/src/controllers/mod.rs @@ -0,0 +1,5 @@ +pub mod auth; +pub mod notes; +pub mod user; + +pub mod server; \ No newline at end of file diff --git a/nixin_farm_ssr/src/controllers/notes.rs b/nixin_farm_ssr/src/controllers/notes.rs new file mode 100644 index 0000000..c95aa3e --- /dev/null +++ b/nixin_farm_ssr/src/controllers/notes.rs @@ -0,0 +1,75 @@ +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::unnecessary_struct_initialization)] +#![allow(clippy::unused_async)] +use axum::debug_handler; +use loco_rs::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::models::_entities::notes::{ActiveModel, Entity, Model}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Params { + pub title: Option, + pub content: Option, +} + +impl Params { + fn update(&self, item: &mut ActiveModel) { + item.title = Set(self.title.clone()); + item.content = Set(self.content.clone()); + } +} + +async fn load_item(ctx: &AppContext, id: i32) -> Result { + let item = Entity::find_by_id(id).one(&ctx.db).await?; + item.ok_or_else(|| Error::NotFound) +} + +#[debug_handler] +pub async fn list(State(ctx): State) -> Result { + format::json(Entity::find().all(&ctx.db).await?) +} + +#[debug_handler] +pub async fn add(State(ctx): State, Json(params): Json) -> Result { + let mut item = ActiveModel { + ..Default::default() + }; + params.update(&mut item); + let item = item.insert(&ctx.db).await?; + format::json(item) +} + +#[debug_handler] +pub async fn update( + Path(id): Path, + State(ctx): State, + Json(params): Json, +) -> Result { + let item = load_item(&ctx, id).await?; + let mut item = item.into_active_model(); + params.update(&mut item); + let item = item.update(&ctx.db).await?; + format::json(item) +} + +#[debug_handler] +pub async fn remove(Path(id): Path, State(ctx): State) -> Result { + load_item(&ctx, id).await?.delete(&ctx.db).await?; + format::empty() +} + +#[debug_handler] +pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { + format::json(load_item(&ctx, id).await?) +} + +pub fn routes() -> Routes { + Routes::new() + .prefix("api/notes") + .add("/", get(list)) + .add("/", post(add)) + .add("/:id", get(get_one)) + .add("/:id", delete(remove)) + .add("/:id", post(update)) +} diff --git a/nixin_farm_ssr/src/controllers/server.rs b/nixin_farm_ssr/src/controllers/server.rs new file mode 100644 index 0000000..1de9dda --- /dev/null +++ b/nixin_farm_ssr/src/controllers/server.rs @@ -0,0 +1,115 @@ +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::unnecessary_struct_initialization)] +#![allow(clippy::unused_async)] +use loco_rs::prelude::*; +use serde::{Deserialize, Serialize}; +use sea_orm::{sea_query::Order, QueryOrder}; +use axum::debug_handler; + +use crate::{ + models::_entities::servers::{ActiveModel, Column, Entity, Model}, + views, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Params { + pub name: Option, + pub domain: Option, + } + +impl Params { + fn update(&self, item: &mut ActiveModel) { + item.name = Set(self.name.clone()); + item.domain = Set(self.domain.clone()); + } +} + +async fn load_item(ctx: &AppContext, id: i32) -> Result { + let item = Entity::find_by_id(id).one(&ctx.db).await?; + item.ok_or_else(|| Error::NotFound) +} + +#[debug_handler] +pub async fn list( + ViewEngine(v): ViewEngine, + State(ctx): State, +) -> Result { + let item = Entity::find() + .order_by(Column::Id, Order::Desc) + .all(&ctx.db) + .await?; + views::server::list(&v, &item) +} + +#[debug_handler] +pub async fn new( + ViewEngine(v): ViewEngine, + State(_ctx): State, +) -> Result { + views::server::create(&v) +} + +#[debug_handler] +pub async fn update( + Path(id): Path, + State(ctx): State, + Json(params): Json, +) -> Result { + let item = load_item(&ctx, id).await?; + let mut item = item.into_active_model(); + params.update(&mut item); + let item = item.update(&ctx.db).await?; + format::json(item) +} + +#[debug_handler] +pub async fn edit( + Path(id): Path, + ViewEngine(v): ViewEngine, + State(ctx): State, +) -> Result { + let item = load_item(&ctx, id).await?; + views::server::edit(&v, &item) +} + +#[debug_handler] +pub async fn show( + Path(id): Path, + ViewEngine(v): ViewEngine, + State(ctx): State, +) -> Result { + let item = load_item(&ctx, id).await?; + views::server::show(&v, &item) +} + +#[debug_handler] +pub async fn add( + ViewEngine(v): ViewEngine, + State(ctx): State, + Json(params): Json, +) -> Result { + let mut item = ActiveModel { + ..Default::default() + }; + params.update(&mut item); + let item = item.insert(&ctx.db).await?; + views::server::show(&v, &item) +} + +#[debug_handler] +pub async fn remove(Path(id): Path, State(ctx): State) -> Result { + load_item(&ctx, id).await?.delete(&ctx.db).await?; + format::empty() +} + +pub fn routes() -> Routes { + Routes::new() + .prefix("servers/") + .add("/", get(list)) + .add("/", post(add)) + .add("new", get(new)) + .add(":id", get(show)) + .add(":id/edit", get(edit)) + .add(":id", post(update)) + .add(":id", delete(remove)) +} diff --git a/nixin_farm_ssr/src/controllers/user.rs b/nixin_farm_ssr/src/controllers/user.rs new file mode 100644 index 0000000..a7c0af3 --- /dev/null +++ b/nixin_farm_ssr/src/controllers/user.rs @@ -0,0 +1,16 @@ +use axum::debug_handler; +use loco_rs::prelude::*; + +use crate::{models::_entities::users, views::user::CurrentResponse}; + +#[debug_handler] +async fn current(auth: auth::JWT, State(ctx): State) -> Result { + let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; + format::json(CurrentResponse::new(&user)) +} + +pub fn routes() -> Routes { + Routes::new() + .prefix("api/user") + .add("/current", get(current)) +} diff --git a/nixin_farm_ssr/src/fixtures/notes.yaml b/nixin_farm_ssr/src/fixtures/notes.yaml new file mode 100644 index 0000000..0e66c95 --- /dev/null +++ b/nixin_farm_ssr/src/fixtures/notes.yaml @@ -0,0 +1,11 @@ +--- +- id: 1 + title: Loco note 1 + content: Loco note 1 content + created_at: "2023-11-12T12:34:56.789Z" + updated_at: "2023-11-12T12:34:56.789Z" +- id: 2 + title: Loco note 2 + content: Loco note 2 content + created_at: "2023-11-12T12:34:56.789Z" + updated_at: "2023-11-12T12:34:56.789Z" diff --git a/nixin_farm_ssr/src/fixtures/users.yaml b/nixin_farm_ssr/src/fixtures/users.yaml new file mode 100644 index 0000000..8f5b5ed --- /dev/null +++ b/nixin_farm_ssr/src/fixtures/users.yaml @@ -0,0 +1,17 @@ +--- +- id: 1 + pid: 11111111-1111-1111-1111-111111111111 + email: user1@example.com + password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" + api_key: lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758 + name: user1 + created_at: "2023-11-12T12:34:56.789Z" + updated_at: "2023-11-12T12:34:56.789Z" +- id: 2 + pid: 22222222-2222-2222-2222-222222222222 + email: user2@example.com + password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" + api_key: lo-153561ca-fa84-4e1b-813a-c62526d0a77e + name: user2 + created_at: "2023-11-12T12:34:56.789Z" + updated_at: "2023-11-12T12:34:56.789Z" diff --git a/nixin_farm_ssr/src/initializers/mod.rs b/nixin_farm_ssr/src/initializers/mod.rs new file mode 100644 index 0000000..8b3bec4 --- /dev/null +++ b/nixin_farm_ssr/src/initializers/mod.rs @@ -0,0 +1,2 @@ +#![allow(clippy::module_name_repetitions)] +pub mod view_engine; diff --git a/nixin_farm_ssr/src/initializers/view_engine.rs b/nixin_farm_ssr/src/initializers/view_engine.rs new file mode 100644 index 0000000..f0a5ffb --- /dev/null +++ b/nixin_farm_ssr/src/initializers/view_engine.rs @@ -0,0 +1,36 @@ +use axum::{async_trait, Extension, Router as AxumRouter}; +use fluent_templates::{ArcLoader, FluentLoader}; +use loco_rs::{ + app::{AppContext, Initializer}, + controller::views::{engines, ViewEngine}, + Error, Result, +}; +use tracing::info; + +const I18N_DIR: &str = "assets/i18n"; +const I18N_SHARED: &str = "assets/i18n/shared.ftl"; + +pub struct ViewEngineInitializer; +#[async_trait] +impl Initializer for ViewEngineInitializer { + fn name(&self) -> String { + "view-engine".to_string() + } + + async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { + let mut tera_engine = engines::TeraView::build()?; + if std::path::Path::new(I18N_DIR).exists() { + let arc = ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US")) + .shared_resources(Some(&[I18N_SHARED.into()])) + .customize(|bundle| bundle.set_use_isolating(false)) + .build() + .map_err(|e| Error::string(&e.to_string()))?; + tera_engine + .tera + .register_function("t", FluentLoader::new(arc)); + info!("locales loaded"); + } + + Ok(router.layer(Extension(ViewEngine::from(tera_engine)))) + } +} diff --git a/nixin_farm_ssr/src/lib.rs b/nixin_farm_ssr/src/lib.rs new file mode 100644 index 0000000..dc3ea76 --- /dev/null +++ b/nixin_farm_ssr/src/lib.rs @@ -0,0 +1,8 @@ +pub mod app; +pub mod controllers; +pub mod initializers; +pub mod mailers; +pub mod models; +pub mod tasks; +pub mod views; +pub mod workers; diff --git a/nixin_farm_ssr/src/mailers/auth.rs b/nixin_farm_ssr/src/mailers/auth.rs new file mode 100644 index 0000000..30bb1bf --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth.rs @@ -0,0 +1,65 @@ +// auth mailer +#![allow(non_upper_case_globals)] + +use loco_rs::prelude::*; +use serde_json::json; + +use crate::models::users; + +static welcome: Dir<'_> = include_dir!("src/mailers/auth/welcome"); +static forgot: Dir<'_> = include_dir!("src/mailers/auth/forgot"); +// #[derive(Mailer)] // -- disabled for faster build speed. it works. but lets +// move on for now. + +#[allow(clippy::module_name_repetitions)] +pub struct AuthMailer {} +impl Mailer for AuthMailer {} +impl AuthMailer { + /// Sending welcome email the the given user + /// + /// # Errors + /// + /// When email sending is failed + pub async fn send_welcome(ctx: &AppContext, user: &users::Model) -> Result<()> { + Self::mail_template( + ctx, + &welcome, + mailer::Args { + to: user.email.to_string(), + locals: json!({ + "name": user.name, + "verifyToken": user.email_verification_token, + "domain": ctx.config.server.full_url() + }), + ..Default::default() + }, + ) + .await?; + + Ok(()) + } + + /// Sending forgot password email + /// + /// # Errors + /// + /// When email sending is failed + pub async fn forgot_password(ctx: &AppContext, user: &users::Model) -> Result<()> { + Self::mail_template( + ctx, + &forgot, + mailer::Args { + to: user.email.to_string(), + locals: json!({ + "name": user.name, + "resetToken": user.reset_token, + "domain": ctx.config.server.full_url() + }), + ..Default::default() + }, + ) + .await?; + + Ok(()) + } +} diff --git a/nixin_farm_ssr/src/mailers/auth/forgot/html.t b/nixin_farm_ssr/src/mailers/auth/forgot/html.t new file mode 100644 index 0000000..221dd60 --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/forgot/html.t @@ -0,0 +1,11 @@ +; + + + Hey {{name}}, + Forgot your password? No worries! You can reset it by clicking the link below: + Reset Your Password + If you didn't request a password reset, please ignore this email. + Best regards,
The Loco Team
+ + + diff --git a/nixin_farm_ssr/src/mailers/auth/forgot/subject.t b/nixin_farm_ssr/src/mailers/auth/forgot/subject.t new file mode 100644 index 0000000..4938df1 --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/forgot/subject.t @@ -0,0 +1 @@ +Your reset password link diff --git a/nixin_farm_ssr/src/mailers/auth/forgot/text.t b/nixin_farm_ssr/src/mailers/auth/forgot/text.t new file mode 100644 index 0000000..58c30fd --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/forgot/text.t @@ -0,0 +1,3 @@ +Reset your password with this link: + +http://localhost/reset#{{resetToken}} diff --git a/nixin_farm_ssr/src/mailers/auth/welcome/html.t b/nixin_farm_ssr/src/mailers/auth/welcome/html.t new file mode 100644 index 0000000..ae4c41c --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/welcome/html.t @@ -0,0 +1,13 @@ +; + + + Dear {{name}}, + Welcome to Loco! You can now log in to your account. + Before you get started, please verify your account by clicking the link below: + + Verify Your Account + +

Best regards,
The Loco Team

+ + + diff --git a/nixin_farm_ssr/src/mailers/auth/welcome/subject.t b/nixin_farm_ssr/src/mailers/auth/welcome/subject.t new file mode 100644 index 0000000..82cc6fb --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/welcome/subject.t @@ -0,0 +1 @@ +Welcome {{name}} diff --git a/nixin_farm_ssr/src/mailers/auth/welcome/text.t b/nixin_farm_ssr/src/mailers/auth/welcome/text.t new file mode 100644 index 0000000..63beefd --- /dev/null +++ b/nixin_farm_ssr/src/mailers/auth/welcome/text.t @@ -0,0 +1,4 @@ +Welcome {{name}}, you can now log in. + Verify your account with the link below: + + http://localhost/verify#{{verifyToken}} diff --git a/nixin_farm_ssr/src/mailers/mod.rs b/nixin_farm_ssr/src/mailers/mod.rs new file mode 100644 index 0000000..0e4a05d --- /dev/null +++ b/nixin_farm_ssr/src/mailers/mod.rs @@ -0,0 +1 @@ +pub mod auth; diff --git a/nixin_farm_ssr/src/models/_entities/mod.rs b/nixin_farm_ssr/src/models/_entities/mod.rs new file mode 100644 index 0000000..fa23d39 --- /dev/null +++ b/nixin_farm_ssr/src/models/_entities/mod.rs @@ -0,0 +1,7 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 + +pub mod prelude; + +pub mod notes; +pub mod servers; +pub mod users; diff --git a/nixin_farm_ssr/src/models/_entities/notes.rs b/nixin_farm_ssr/src/models/_entities/notes.rs new file mode 100644 index 0000000..0bf2b7c --- /dev/null +++ b/nixin_farm_ssr/src/models/_entities/notes.rs @@ -0,0 +1,18 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "notes")] +pub struct Model { + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(primary_key)] + pub id: i32, + pub title: Option, + pub content: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} diff --git a/nixin_farm_ssr/src/models/_entities/prelude.rs b/nixin_farm_ssr/src/models/_entities/prelude.rs new file mode 100644 index 0000000..2a9300a --- /dev/null +++ b/nixin_farm_ssr/src/models/_entities/prelude.rs @@ -0,0 +1,5 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 + +pub use super::notes::Entity as Notes; +pub use super::servers::Entity as Servers; +pub use super::users::Entity as Users; diff --git a/nixin_farm_ssr/src/models/_entities/servers.rs b/nixin_farm_ssr/src/models/_entities/servers.rs new file mode 100644 index 0000000..7f1f434 --- /dev/null +++ b/nixin_farm_ssr/src/models/_entities/servers.rs @@ -0,0 +1,18 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "servers")] +pub struct Model { + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(primary_key)] + pub id: i32, + pub name: Option, + pub domain: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} diff --git a/nixin_farm_ssr/src/models/_entities/users.rs b/nixin_farm_ssr/src/models/_entities/users.rs new file mode 100644 index 0000000..d5441bb --- /dev/null +++ b/nixin_farm_ssr/src/models/_entities/users.rs @@ -0,0 +1,28 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "users")] +pub struct Model { + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(primary_key)] + pub id: i32, + pub pid: Uuid, + #[sea_orm(unique)] + pub email: String, + pub password: String, + #[sea_orm(unique)] + pub api_key: String, + pub name: String, + pub reset_token: Option, + pub reset_sent_at: Option, + pub email_verification_token: Option, + pub email_verification_sent_at: Option, + pub email_verified_at: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} diff --git a/nixin_farm_ssr/src/models/mod.rs b/nixin_farm_ssr/src/models/mod.rs new file mode 100644 index 0000000..2622fcb --- /dev/null +++ b/nixin_farm_ssr/src/models/mod.rs @@ -0,0 +1,4 @@ +pub mod _entities; +pub mod notes; +pub mod users; +pub mod servers; diff --git a/nixin_farm_ssr/src/models/notes.rs b/nixin_farm_ssr/src/models/notes.rs new file mode 100644 index 0000000..1102598 --- /dev/null +++ b/nixin_farm_ssr/src/models/notes.rs @@ -0,0 +1,7 @@ +use sea_orm::entity::prelude::*; + +use super::_entities::notes::ActiveModel; + +impl ActiveModelBehavior for ActiveModel { + // extend activemodel below (keep comment for generators) +} diff --git a/nixin_farm_ssr/src/models/servers.rs b/nixin_farm_ssr/src/models/servers.rs new file mode 100644 index 0000000..cb875b7 --- /dev/null +++ b/nixin_farm_ssr/src/models/servers.rs @@ -0,0 +1,7 @@ +use sea_orm::entity::prelude::*; +use super::_entities::servers::{ActiveModel, Entity}; +pub type Servers = Entity; + +impl ActiveModelBehavior for ActiveModel { + // extend activemodel below (keep comment for generators) +} diff --git a/nixin_farm_ssr/src/models/users.rs b/nixin_farm_ssr/src/models/users.rs new file mode 100644 index 0000000..b4f3aae --- /dev/null +++ b/nixin_farm_ssr/src/models/users.rs @@ -0,0 +1,298 @@ +use async_trait::async_trait; +use chrono::offset::Local; +use loco_rs::{auth::jwt, hash, prelude::*}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +pub use super::_entities::users::{self, ActiveModel, Entity, Model}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct LoginParams { + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RegisterParams { + pub email: String, + pub password: String, + pub name: String, +} + +#[derive(Debug, Validate, Deserialize)] +pub struct Validator { + #[validate(length(min = 2, message = "Name must be at least 2 characters long."))] + pub name: String, + #[validate(custom(function = "validation::is_valid_email"))] + pub email: String, +} + +impl Validatable for super::_entities::users::ActiveModel { + fn validator(&self) -> Box { + Box::new(Validator { + name: self.name.as_ref().to_owned(), + email: self.email.as_ref().to_owned(), + }) + } +} + +#[async_trait::async_trait] +impl ActiveModelBehavior for super::_entities::users::ActiveModel { + async fn before_save(self, _db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { + self.validate()?; + if insert { + let mut this = self; + this.pid = ActiveValue::Set(Uuid::new_v4()); + this.api_key = ActiveValue::Set(format!("lo-{}", Uuid::new_v4())); + Ok(this) + } else { + Ok(self) + } + } +} + +#[async_trait] +impl Authenticable for super::_entities::users::Model { + async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::ApiKey, api_key) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { + Self::find_by_pid(db, claims_key).await + } +} + +impl super::_entities::users::Model { + /// finds a user by the provided email + /// + /// # Errors + /// + /// When could not find user by the given token or DB query error + pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::Email, email) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + /// finds a user by the provided verification token + /// + /// # Errors + /// + /// When could not find user by the given token or DB query error + pub async fn find_by_verification_token( + db: &DatabaseConnection, + token: &str, + ) -> ModelResult { + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::EmailVerificationToken, token) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + /// finds a user by the provided reset token + /// + /// # Errors + /// + /// When could not find user by the given token or DB query error + pub async fn find_by_reset_token(db: &DatabaseConnection, token: &str) -> ModelResult { + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::ResetToken, token) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + /// finds a user by the provided pid + /// + /// # Errors + /// + /// When could not find user or DB query error + pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult { + let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?; + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::Pid, parse_uuid) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + /// finds a user by the provided api key + /// + /// # Errors + /// + /// When could not find user by the given token or DB query error + pub async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { + let user = users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::ApiKey, api_key) + .build(), + ) + .one(db) + .await?; + user.ok_or_else(|| ModelError::EntityNotFound) + } + + /// Verifies whether the provided plain password matches the hashed password + /// + /// # Errors + /// + /// when could not verify password + #[must_use] + pub fn verify_password(&self, password: &str) -> bool { + hash::verify_password(password, &self.password) + } + + /// Asynchronously creates a user with a password and saves it to the + /// database. + /// + /// # Errors + /// + /// When could not save the user into the DB + pub async fn create_with_password( + db: &DatabaseConnection, + params: &RegisterParams, + ) -> ModelResult { + let txn = db.begin().await?; + + if users::Entity::find() + .filter( + model::query::condition() + .eq(users::Column::Email, ¶ms.email) + .build(), + ) + .one(&txn) + .await? + .is_some() + { + return Err(ModelError::EntityAlreadyExists {}); + } + + let password_hash = + hash::hash_password(¶ms.password).map_err(|e| ModelError::Any(e.into()))?; + let user = users::ActiveModel { + email: ActiveValue::set(params.email.to_string()), + password: ActiveValue::set(password_hash), + name: ActiveValue::set(params.name.to_string()), + ..Default::default() + } + .insert(&txn) + .await?; + + txn.commit().await?; + + Ok(user) + } + + /// Creates a JWT + /// + /// # Errors + /// + /// when could not convert user claims to jwt token + pub fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult { + Ok(jwt::JWT::new(secret).generate_token(expiration, self.pid.to_string(), None)?) + } +} + +impl super::_entities::users::ActiveModel { + /// Sets the email verification information for the user and + /// updates it in the database. + /// + /// This method is used to record the timestamp when the email verification + /// was sent and generate a unique verification token for the user. + /// + /// # Errors + /// + /// when has DB query error + pub async fn set_email_verification_sent( + mut self, + db: &DatabaseConnection, + ) -> ModelResult { + self.email_verification_sent_at = ActiveValue::set(Some(Local::now().into())); + self.email_verification_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); + Ok(self.update(db).await?) + } + + /// Sets the information for a reset password request, + /// generates a unique reset password token, and updates it in the + /// database. + /// + /// This method records the timestamp when the reset password token is sent + /// and generates a unique token for the user. + /// + /// # Arguments + /// + /// # Errors + /// + /// when has DB query error + pub async fn set_forgot_password_sent(mut self, db: &DatabaseConnection) -> ModelResult { + self.reset_sent_at = ActiveValue::set(Some(Local::now().into())); + self.reset_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); + Ok(self.update(db).await?) + } + + /// Records the verification time when a user verifies their + /// email and updates it in the database. + /// + /// This method sets the timestamp when the user successfully verifies their + /// email. + /// + /// # Errors + /// + /// when has DB query error + pub async fn verified(mut self, db: &DatabaseConnection) -> ModelResult { + self.email_verified_at = ActiveValue::set(Some(Local::now().into())); + Ok(self.update(db).await?) + } + + /// Resets the current user password with a new password and + /// updates it in the database. + /// + /// This method hashes the provided password and sets it as the new password + /// for the user. + /// + /// # Errors + /// + /// when has DB query error or could not hashed the given password + pub async fn reset_password( + mut self, + db: &DatabaseConnection, + password: &str, + ) -> ModelResult { + self.password = + ActiveValue::set(hash::hash_password(password).map_err(|e| ModelError::Any(e.into()))?); + self.reset_token = ActiveValue::Set(None); + self.reset_sent_at = ActiveValue::Set(None); + Ok(self.update(db).await?) + } +} diff --git a/nixin_farm_ssr/src/tasks/mod.rs b/nixin_farm_ssr/src/tasks/mod.rs new file mode 100644 index 0000000..01fbdda --- /dev/null +++ b/nixin_farm_ssr/src/tasks/mod.rs @@ -0,0 +1 @@ +pub mod seed; diff --git a/nixin_farm_ssr/src/tasks/seed.rs b/nixin_farm_ssr/src/tasks/seed.rs new file mode 100644 index 0000000..1647beb --- /dev/null +++ b/nixin_farm_ssr/src/tasks/seed.rs @@ -0,0 +1,45 @@ +//! This task implements data seeding functionality for initializing new +//! development/demo environments. +//! +//! # Example +//! +//! Run the task with the following command: +//! ```sh +//! cargo run task +//! ``` +//! +//! To override existing data and reset the data structure, use the following +//! command with the `refresh:true` argument: +//! ```sh +//! cargo run task seed_data refresh:true +//! ``` + +use loco_rs::{db, prelude::*}; +use migration::Migrator; + +use crate::app::App; + +#[allow(clippy::module_name_repetitions)] +pub struct SeedData; +#[async_trait] +impl Task for SeedData { + fn task(&self) -> TaskInfo { + TaskInfo { + name: "seed_data".to_string(), + detail: "Task for seeding data".to_string(), + } + } + + async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> { + let refresh = vars + .cli_arg("refresh") + .is_ok_and(|refresh| refresh == "true"); + + if refresh { + db::reset::(&app_context.db).await?; + } + let path = std::path::Path::new("src/fixtures"); + db::run_app_seed::(&app_context.db, path).await?; + Ok(()) + } +} diff --git a/nixin_farm_ssr/src/views/auth.rs b/nixin_farm_ssr/src/views/auth.rs new file mode 100644 index 0000000..2240a50 --- /dev/null +++ b/nixin_farm_ssr/src/views/auth.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::_entities::users; + +#[derive(Debug, Deserialize, Serialize)] +pub struct LoginResponse { + pub token: String, + pub pid: String, + pub name: String, + pub is_verified: bool, +} + +impl LoginResponse { + #[must_use] + pub fn new(user: &users::Model, token: &String) -> Self { + Self { + token: token.to_string(), + pid: user.pid.to_string(), + name: user.name.clone(), + is_verified: user.email_verified_at.is_some(), + } + } +} diff --git a/nixin_farm_ssr/src/views/mod.rs b/nixin_farm_ssr/src/views/mod.rs new file mode 100644 index 0000000..b8613a9 --- /dev/null +++ b/nixin_farm_ssr/src/views/mod.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod user; + +pub mod server; \ No newline at end of file diff --git a/nixin_farm_ssr/src/views/server.rs b/nixin_farm_ssr/src/views/server.rs new file mode 100644 index 0000000..01e4ce3 --- /dev/null +++ b/nixin_farm_ssr/src/views/server.rs @@ -0,0 +1,39 @@ +use loco_rs::prelude::*; + +use crate::models::_entities::servers; + +/// Render a list view of servers. +/// +/// # Errors +/// +/// When there is an issue with rendering the view. +pub fn list(v: &impl ViewRenderer, items: &Vec) -> Result { + format::render().view(v, "server/list.html", data!({"items": items})) +} + +/// Render a single server view. +/// +/// # Errors +/// +/// When there is an issue with rendering the view. +pub fn show(v: &impl ViewRenderer, item: &servers::Model) -> Result { + format::render().view(v, "server/show.html", data!({"item": item})) +} + +/// Render a server create form. +/// +/// # Errors +/// +/// When there is an issue with rendering the view. +pub fn create(v: &impl ViewRenderer) -> Result { + format::render().view(v, "server/create.html", data!({})) +} + +/// Render a server edit form. +/// +/// # Errors +/// +/// When there is an issue with rendering the view. +pub fn edit(v: &impl ViewRenderer, item: &servers::Model) -> Result { + format::render().view(v, "server/edit.html", data!({"item": item})) +} diff --git a/nixin_farm_ssr/src/views/user.rs b/nixin_farm_ssr/src/views/user.rs new file mode 100644 index 0000000..9d83041 --- /dev/null +++ b/nixin_farm_ssr/src/views/user.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::_entities::users; + +#[derive(Debug, Deserialize, Serialize)] +pub struct CurrentResponse { + pub pid: String, + pub name: String, + pub email: String, +} + +impl CurrentResponse { + #[must_use] + pub fn new(user: &users::Model) -> Self { + Self { + pid: user.pid.to_string(), + name: user.name.clone(), + email: user.email.clone(), + } + } +} diff --git a/nixin_farm_ssr/src/workers/downloader.rs b/nixin_farm_ssr/src/workers/downloader.rs new file mode 100644 index 0000000..fa094d5 --- /dev/null +++ b/nixin_farm_ssr/src/workers/downloader.rs @@ -0,0 +1,40 @@ +use std::time::Duration; + +use loco_rs::prelude::*; +use serde::{Deserialize, Serialize}; +use tokio::time::sleep; + +use crate::models::users; + +pub struct DownloadWorker { + pub ctx: AppContext, +} + +#[derive(Deserialize, Debug, Serialize)] +pub struct DownloadWorkerArgs { + pub user_guid: String, +} + +#[async_trait] +impl BackgroundWorker for DownloadWorker { + fn build(ctx: &AppContext) -> Self { + Self { ctx: ctx.clone() } + } + async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> { + // TODO: Some actual work goes here... + println!("================================================"); + println!("Sending payment report to user {}", args.user_guid); + + sleep(Duration::from_millis(2000)).await; + + let all = users::Entity::find() + .all(&self.ctx.db) + .await + .map_err(Box::from)?; + for user in &all { + println!("user: {}", user.id); + } + println!("================================================"); + Ok(()) + } +} diff --git a/nixin_farm_ssr/src/workers/mod.rs b/nixin_farm_ssr/src/workers/mod.rs new file mode 100644 index 0000000..acb5733 --- /dev/null +++ b/nixin_farm_ssr/src/workers/mod.rs @@ -0,0 +1 @@ +pub mod downloader; diff --git a/nixin_farm_ssr/tests/mod.rs b/nixin_farm_ssr/tests/mod.rs new file mode 100644 index 0000000..b42f234 --- /dev/null +++ b/nixin_farm_ssr/tests/mod.rs @@ -0,0 +1,4 @@ +mod models; +mod requests; +mod tasks; +mod workers; diff --git a/nixin_farm_ssr/tests/models/mod.rs b/nixin_farm_ssr/tests/models/mod.rs new file mode 100644 index 0000000..22e060c --- /dev/null +++ b/nixin_farm_ssr/tests/models/mod.rs @@ -0,0 +1,3 @@ +mod users; + +mod servers; \ No newline at end of file diff --git a/nixin_farm_ssr/tests/models/servers.rs b/nixin_farm_ssr/tests/models/servers.rs new file mode 100644 index 0000000..66cdb82 --- /dev/null +++ b/nixin_farm_ssr/tests/models/servers.rs @@ -0,0 +1,31 @@ +use nixin_farm_ssr::app::App; +use loco_rs::testing; +use serial_test::serial; + +macro_rules! configure_insta { + ($($expr:expr),*) => { + let mut settings = insta::Settings::clone_current(); + settings.set_prepend_module_to_snapshot(false); + let _guard = settings.bind_to_scope(); + }; +} + +#[tokio::test] +#[serial] +async fn test_model() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + // query your model, e.g.: + // + // let item = models::posts::Model::find_by_pid( + // &boot.app_context.db, + // "11111111-1111-1111-1111-111111111111", + // ) + // .await; + + // snapshot the result: + // assert_debug_snapshot!(item); +} diff --git a/nixin_farm_ssr/tests/models/snapshots/can_create_with_password@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_create_with_password@users.snap new file mode 100644 index 0000000..6e66fd3 --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_create_with_password@users.snap @@ -0,0 +1,21 @@ +--- +source: tests/models/users.rs +expression: res +--- +Ok( + Model { + created_at: DATE, + updated_at: DATE, + id: ID + pid: PID, + email: "test@framework.com", + password: "PASSWORD", + api_key: "lo-PID", + name: "framework", + reset_token: None, + reset_sent_at: None, + email_verification_token: None, + email_verification_sent_at: None, + email_verified_at: None, + }, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_email-2@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email-2@users.snap new file mode 100644 index 0000000..83dc06e --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email-2@users.snap @@ -0,0 +1,7 @@ +--- +source: tests/models/main.rs +expression: non_existing_user_results +--- +Err( + EntityNotFound, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users-2.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users-2.snap new file mode 100644 index 0000000..25c700a --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users-2.snap @@ -0,0 +1,7 @@ +--- +source: tests/models/users.rs +expression: non_existing_user_results +--- +Err( + EntityNotFound, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users.snap new file mode 100644 index 0000000..067d0e7 --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_email@users.snap @@ -0,0 +1,21 @@ +--- +source: tests/models/users.rs +expression: existing_user +--- +Ok( + Model { + created_at: 2023-11-12T12:34:56.789+00:00, + updated_at: 2023-11-12T12:34:56.789+00:00, + id: 1, + pid: 11111111-1111-1111-1111-111111111111, + email: "user1@example.com", + password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc", + api_key: "lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758", + name: "user1", + reset_token: None, + reset_sent_at: None, + email_verification_token: None, + email_verification_sent_at: None, + email_verified_at: None, + }, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid-2@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid-2@users.snap new file mode 100644 index 0000000..83dc06e --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid-2@users.snap @@ -0,0 +1,7 @@ +--- +source: tests/models/main.rs +expression: non_existing_user_results +--- +Err( + EntityNotFound, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users-2.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users-2.snap new file mode 100644 index 0000000..25c700a --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users-2.snap @@ -0,0 +1,7 @@ +--- +source: tests/models/users.rs +expression: non_existing_user_results +--- +Err( + EntityNotFound, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users.snap new file mode 100644 index 0000000..067d0e7 --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_find_by_pid@users.snap @@ -0,0 +1,21 @@ +--- +source: tests/models/users.rs +expression: existing_user +--- +Ok( + Model { + created_at: 2023-11-12T12:34:56.789+00:00, + updated_at: 2023-11-12T12:34:56.789+00:00, + id: 1, + pid: 11111111-1111-1111-1111-111111111111, + email: "user1@example.com", + password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc", + api_key: "lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758", + name: "user1", + reset_token: None, + reset_sent_at: None, + email_verification_token: None, + email_verification_sent_at: None, + email_verified_at: None, + }, +) diff --git a/nixin_farm_ssr/tests/models/snapshots/can_validate_model@users.snap b/nixin_farm_ssr/tests/models/snapshots/can_validate_model@users.snap new file mode 100644 index 0000000..4d457a7 --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/can_validate_model@users.snap @@ -0,0 +1,9 @@ +--- +source: tests/models/main.rs +expression: res +--- +Err( + Custom( + "{\"email\":[{\"code\":\"invalid email\",\"message\":null}],\"name\":[{\"code\":\"length\",\"message\":\"Name must be at least 2 characters long.\"}]}", + ), +) diff --git a/nixin_farm_ssr/tests/models/snapshots/handle_create_with_password_with_duplicate@users.snap b/nixin_farm_ssr/tests/models/snapshots/handle_create_with_password_with_duplicate@users.snap new file mode 100644 index 0000000..ff28ea1 --- /dev/null +++ b/nixin_farm_ssr/tests/models/snapshots/handle_create_with_password_with_duplicate@users.snap @@ -0,0 +1,7 @@ +--- +source: tests/models/users.rs +expression: new_user +--- +Err( + EntityAlreadyExists, +) diff --git a/nixin_farm_ssr/tests/models/users.rs b/nixin_farm_ssr/tests/models/users.rs new file mode 100644 index 0000000..9788469 --- /dev/null +++ b/nixin_farm_ssr/tests/models/users.rs @@ -0,0 +1,223 @@ +use insta::assert_debug_snapshot; +use loco_rs::{model::ModelError, testing}; +use nixin_farm_ssr::{ + app::App, + models::users::{self, Model, RegisterParams}, +}; +use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel}; +use serial_test::serial; + +macro_rules! configure_insta { + ($($expr:expr),*) => { + let mut settings = insta::Settings::clone_current(); + settings.set_prepend_module_to_snapshot(false); + settings.set_snapshot_suffix("users"); + let _guard = settings.bind_to_scope(); + }; +} + +#[tokio::test] +#[serial] +async fn test_can_validate_model() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + + let res = users::ActiveModel { + name: ActiveValue::set("1".to_string()), + email: ActiveValue::set("invalid-email".to_string()), + ..Default::default() + } + .insert(&boot.app_context.db) + .await; + + assert_debug_snapshot!(res); +} + +#[tokio::test] +#[serial] +async fn can_create_with_password() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + + let params = RegisterParams { + email: "test@framework.com".to_string(), + password: "1234".to_string(), + name: "framework".to_string(), + }; + let res = Model::create_with_password(&boot.app_context.db, ¶ms).await; + + insta::with_settings!({ + filters => testing::cleanup_user_model() + }, { + assert_debug_snapshot!(res); + }); +} + +#[tokio::test] +#[serial] +async fn handle_create_with_password_with_duplicate() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let new_user: Result = Model::create_with_password( + &boot.app_context.db, + &RegisterParams { + email: "user1@example.com".to_string(), + password: "1234".to_string(), + name: "framework".to_string(), + }, + ) + .await; + assert_debug_snapshot!(new_user); +} + +#[tokio::test] +#[serial] +async fn can_find_by_email() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let existing_user = Model::find_by_email(&boot.app_context.db, "user1@example.com").await; + let non_existing_user_results = + Model::find_by_email(&boot.app_context.db, "un@existing-email.com").await; + + assert_debug_snapshot!(existing_user); + assert_debug_snapshot!(non_existing_user_results); +} + +#[tokio::test] +#[serial] +async fn can_find_by_pid() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let existing_user = + Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await; + let non_existing_user_results = + Model::find_by_pid(&boot.app_context.db, "23232323-2323-2323-2323-232323232323").await; + + assert_debug_snapshot!(existing_user); + assert_debug_snapshot!(non_existing_user_results); +} + +#[tokio::test] +#[serial] +async fn can_verification_token() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.email_verification_sent_at.is_none()); + assert!(user.email_verification_token.is_none()); + + assert!(user + .into_active_model() + .set_email_verification_sent(&boot.app_context.db) + .await + .is_ok()); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.email_verification_sent_at.is_some()); + assert!(user.email_verification_token.is_some()); +} + +#[tokio::test] +#[serial] +async fn can_set_forgot_password_sent() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.reset_sent_at.is_none()); + assert!(user.reset_token.is_none()); + + assert!(user + .into_active_model() + .set_forgot_password_sent(&boot.app_context.db) + .await + .is_ok()); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.reset_sent_at.is_some()); + assert!(user.reset_token.is_some()); +} + +#[tokio::test] +#[serial] +async fn can_verified() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.email_verified_at.is_none()); + + assert!(user + .into_active_model() + .verified(&boot.app_context.db) + .await + .is_ok()); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.email_verified_at.is_some()); +} + +#[tokio::test] +#[serial] +async fn can_reset_password() { + configure_insta!(); + + let boot = testing::boot_test::().await.unwrap(); + testing::seed::(&boot.app_context.db).await.unwrap(); + + let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap(); + + assert!(user.verify_password("12341234")); + + assert!(user + .clone() + .into_active_model() + .reset_password(&boot.app_context.db, "new-password") + .await + .is_ok()); + + assert!( + Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111") + .await + .unwrap() + .verify_password("new-password") + ); +} diff --git a/nixin_farm_ssr/tests/requests/auth.rs b/nixin_farm_ssr/tests/requests/auth.rs new file mode 100644 index 0000000..608c84c --- /dev/null +++ b/nixin_farm_ssr/tests/requests/auth.rs @@ -0,0 +1,195 @@ +use insta::{assert_debug_snapshot, with_settings}; +use loco_rs::testing; +use nixin_farm_ssr::{app::App, models::users}; +use rstest::rstest; +use serial_test::serial; + +use super::prepare_data; + +// TODO: see how to dedup / extract this to app-local test utils +// not to framework, because that would require a runtime dep on insta +macro_rules! configure_insta { + ($($expr:expr),*) => { + let mut settings = insta::Settings::clone_current(); + settings.set_prepend_module_to_snapshot(false); + settings.set_snapshot_suffix("auth_request"); + let _guard = settings.bind_to_scope(); + }; +} + +#[tokio::test] +#[serial] +async fn can_register() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + let email = "test@loco.com"; + let payload = serde_json::json!({ + "name": "loco", + "email": email, + "password": "12341234" + }); + + let _response = request.post("/api/auth/register").json(&payload).await; + let saved_user = users::Model::find_by_email(&ctx.db, email).await; + + with_settings!({ + filters => testing::cleanup_user_model() + }, { + assert_debug_snapshot!(saved_user); + }); + + with_settings!({ + filters => testing::cleanup_email() + }, { + assert_debug_snapshot!(ctx.mailer.unwrap().deliveries()); + }); + }) + .await; +} + +#[rstest] +#[case("login_with_valid_password", "12341234")] +#[case("login_with_invalid_password", "invalid-password")] +#[tokio::test] +#[serial] +async fn can_login_with_verify(#[case] test_name: &str, #[case] password: &str) { + configure_insta!(); + + testing::request::(|request, ctx| async move { + let email = "test@loco.com"; + let register_payload = serde_json::json!({ + "name": "loco", + "email": email, + "password": "12341234" + }); + + //Creating a new user + _ = request + .post("/api/auth/register") + .json(®ister_payload) + .await; + + let user = users::Model::find_by_email(&ctx.db, email).await.unwrap(); + let verify_payload = serde_json::json!({ + "token": user.email_verification_token, + }); + request.post("/api/auth/verify").json(&verify_payload).await; + + //verify user request + let response = request + .post("/api/auth/login") + .json(&serde_json::json!({ + "email": email, + "password": password + })) + .await; + + // Make sure email_verified_at is set + assert!(users::Model::find_by_email(&ctx.db, email) + .await + .unwrap() + .email_verified_at + .is_some()); + + with_settings!({ + filters => testing::cleanup_user_model() + }, { + assert_debug_snapshot!(test_name, (response.status_code(), response.text())); + }); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_login_without_verify() { + configure_insta!(); + + testing::request::(|request, _ctx| async move { + let email = "test@loco.com"; + let password = "12341234"; + let register_payload = serde_json::json!({ + "name": "loco", + "email": email, + "password": password + }); + + //Creating a new user + _ = request + .post("/api/auth/register") + .json(®ister_payload) + .await; + + //verify user request + let response = request + .post("/api/auth/login") + .json(&serde_json::json!({ + "email": email, + "password": password + })) + .await; + + with_settings!({ + filters => testing::cleanup_user_model() + }, { + assert_debug_snapshot!((response.status_code(), response.text())); + }); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_reset_password() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + let login_data = prepare_data::init_user_login(&request, &ctx).await; + + let forgot_payload = serde_json::json!({ + "email": login_data.user.email, + }); + _ = request.post("/api/auth/forgot").json(&forgot_payload).await; + + let user = users::Model::find_by_email(&ctx.db, &login_data.user.email) + .await + .unwrap(); + assert!(user.reset_token.is_some()); + assert!(user.reset_sent_at.is_some()); + + let new_password = "new-password"; + let reset_payload = serde_json::json!({ + "token": user.reset_token, + "password": new_password, + }); + + let reset_response = request.post("/api/auth/reset").json(&reset_payload).await; + + let user = users::Model::find_by_email(&ctx.db, &user.email) + .await + .unwrap(); + + assert!(user.reset_token.is_none()); + assert!(user.reset_sent_at.is_none()); + + assert_debug_snapshot!((reset_response.status_code(), reset_response.text())); + + let response = request + .post("/api/auth/login") + .json(&serde_json::json!({ + "email": user.email, + "password": new_password + })) + .await; + + assert_eq!(response.status_code(), 200); + + with_settings!({ + filters => testing::cleanup_email() + }, { + assert_debug_snapshot!(ctx.mailer.unwrap().deliveries()); + }); + }) + .await; +} diff --git a/nixin_farm_ssr/tests/requests/mod.rs b/nixin_farm_ssr/tests/requests/mod.rs new file mode 100644 index 0000000..81ed68f --- /dev/null +++ b/nixin_farm_ssr/tests/requests/mod.rs @@ -0,0 +1,4 @@ +mod auth; +mod notes; +mod prepare_data; +mod user; diff --git a/nixin_farm_ssr/tests/requests/notes.rs b/nixin_farm_ssr/tests/requests/notes.rs new file mode 100644 index 0000000..9f334d9 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/notes.rs @@ -0,0 +1,123 @@ +use insta::{assert_debug_snapshot, with_settings}; +use loco_rs::testing; +use nixin_farm_ssr::{app::App, models::_entities::notes::Entity}; +use sea_orm::entity::prelude::*; +use serial_test::serial; + +// TODO: see how to dedup / extract this to app-local test utils +// not to framework, because that would require a runtime dep on insta +macro_rules! configure_insta { + ($($expr:expr),*) => { + let mut settings = insta::Settings::clone_current(); + settings.set_prepend_module_to_snapshot(false); + settings.set_snapshot_suffix("notes_request"); + let _guard = settings.bind_to_scope(); + }; +} + +#[tokio::test] +#[serial] +async fn can_get_notes() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + testing::seed::(&ctx.db).await.unwrap(); + + let notes = request.get("/api/notes").await; + + with_settings!({ + filters => { + let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); + combined_filters + } + }, { + assert_debug_snapshot!( + (notes.status_code(), notes.text()) + ); + }); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_add_note() { + configure_insta!(); + + testing::request::(|request, _ctx| async move { + let payload = serde_json::json!({ + "title": "loco", + "content": "loco note test", + }); + + let add_note_request = request.post("/api/notes").json(&payload).await; + + with_settings!({ + filters => { + let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); + combined_filters + } + }, { + assert_debug_snapshot!( + (add_note_request.status_code(), add_note_request.text()) + ); + }); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_get_note() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + testing::seed::(&ctx.db).await.unwrap(); + + let add_note_request = request.get("/api/notes/1").await; + + with_settings!({ + filters => { + let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); + combined_filters + } + }, { + assert_debug_snapshot!( + (add_note_request.status_code(), add_note_request.text()) + ); + }); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_delete_note() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + testing::seed::(&ctx.db).await.unwrap(); + + let count_before_delete = Entity::find().all(&ctx.db).await.unwrap().len(); + let delete_note_request = request.delete("/api/notes/1").await; + + with_settings!({ + filters => { + let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); + combined_filters + } + }, { + assert_debug_snapshot!( + (delete_note_request.status_code(), delete_note_request.text()) + ); + }); + + let count_after_delete = Entity::find().all(&ctx.db).await.unwrap().len(); + assert_eq!(count_after_delete, count_before_delete - 1); + }) + .await; +} diff --git a/nixin_farm_ssr/tests/requests/prepare_data.rs b/nixin_farm_ssr/tests/requests/prepare_data.rs new file mode 100644 index 0000000..de0c572 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/prepare_data.rs @@ -0,0 +1,57 @@ +use axum::http::{HeaderName, HeaderValue}; +use loco_rs::{app::AppContext, TestServer}; +use nixin_farm_ssr::{models::users, views::auth::LoginResponse}; + +const USER_EMAIL: &str = "test@loco.com"; +const USER_PASSWORD: &str = "1234"; + +pub struct LoggedInUser { + pub user: users::Model, + pub token: String, +} + +pub async fn init_user_login(request: &TestServer, ctx: &AppContext) -> LoggedInUser { + let register_payload = serde_json::json!({ + "name": "loco", + "email": USER_EMAIL, + "password": USER_PASSWORD + }); + + //Creating a new user + request + .post("/api/auth/register") + .json(®ister_payload) + .await; + let user = users::Model::find_by_email(&ctx.db, USER_EMAIL) + .await + .unwrap(); + + let verify_payload = serde_json::json!({ + "token": user.email_verification_token, + }); + + request.post("/api/auth/verify").json(&verify_payload).await; + + let response = request + .post("/api/auth/login") + .json(&serde_json::json!({ + "email": USER_EMAIL, + "password": USER_PASSWORD + })) + .await; + + let login_response: LoginResponse = serde_json::from_str(&response.text()).unwrap(); + + LoggedInUser { + user: users::Model::find_by_email(&ctx.db, USER_EMAIL) + .await + .unwrap(), + token: login_response.token, + } +} + +pub fn auth_header(token: &str) -> (HeaderName, HeaderValue) { + let auth_header_value = HeaderValue::from_str(&format!("Bearer {}", &token)).unwrap(); + + (HeaderName::from_static("authorization"), auth_header_value) +} diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_add_note@notes_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_add_note@notes_request.snap new file mode 100644 index 0000000..f8457d7 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_add_note@notes_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/notes.rs +expression: "(add_note_request.status_code(), add_note_request.text())" +--- +( + 200, + "{\"created_at\":\"DATEZ\",\"updated_at\":\"DATEZ\",\"id\":ID,\"title\":\"loco\",\"content\":\"loco note test\"}", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_delete_note@notes_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_delete_note@notes_request.snap new file mode 100644 index 0000000..3481fc3 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_delete_note@notes_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/notes.rs +expression: "(delete_note_request.status_code(), delete_note_request.text())" +--- +( + 200, + "", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_get_current_user@auth_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_get_current_user@auth_request.snap new file mode 100644 index 0000000..85d53db --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_get_current_user@auth_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/user.rs +expression: "(response.status_code(), response.text())" +--- +( + 200, + "{\"pid\":\"PID\",\"name\":\"loco\",\"email\":\"test@loco.com\"}", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_get_note@notes_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_get_note@notes_request.snap new file mode 100644 index 0000000..8af1604 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_get_note@notes_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/notes.rs +expression: "(add_note_request.status_code(), add_note_request.text())" +--- +( + 200, + "{\"created_at\":\"DATEZ\",\"updated_at\":\"DATEZ\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"}", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_get_notes@notes_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_get_notes@notes_request.snap new file mode 100644 index 0000000..014b75c --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_get_notes@notes_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/notes.rs +expression: "(notes.status_code(), notes.text())" +--- +( + 200, + "[{\"created_at\":\"DATEZ\",\"updated_at\":\"DATEZ\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"},{\"created_at\":\"DATEZ\",\"updated_at\":\"DATEZ\",\"id\":ID,\"title\":\"Loco note 2\",\"content\":\"Loco note 2 content\"}]", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_login_without_verify@auth_request.snap b/nixin_farm_ssr/tests/requests/snapshots/can_login_without_verify@auth_request.snap new file mode 100644 index 0000000..ef54ba6 --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_login_without_verify@auth_request.snap @@ -0,0 +1,8 @@ +--- +source: tests/requests/auth.rs +expression: "(response.status_code(), response.text())" +--- +( + 200, + "{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"loco\",\"is_verified\":false}", +) diff --git a/nixin_farm_ssr/tests/requests/snapshots/can_register@auth_request-2.snap b/nixin_farm_ssr/tests/requests/snapshots/can_register@auth_request-2.snap new file mode 100644 index 0000000..45c63cb --- /dev/null +++ b/nixin_farm_ssr/tests/requests/snapshots/can_register@auth_request-2.snap @@ -0,0 +1,10 @@ +--- +source: tests/requests/auth.rs +expression: ctx.mailer.unwrap().deliveries() +--- +Deliveries { + count: 1, + messages: [ + "From: System \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\n http://localhost/verify#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\nTo: test@loco.com\r\nSubject: Welcome =?utf-8?b?bG9jbwo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nWelcome loco, you can now log in.\r\n Verify your account with the link below:\r\n\r\n http://localhost/verify#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Dear loco,\r\n Welcome to Loco! You can now log in to your account.\r\n Before you get started, please verify your account by clicking the link b=\r\nelow:\r\n \r\nTo: test@loco.com\r\nSubject: Your reset password =?utf-8?b?bGluawo=?=\r\nMIME-Version: 1.0\r\nDate: DATE\r\nContent-Type: multipart/alternative;\r\n boundary=\"IDENTIFIER\"\r\n\r\n--IDENTIFIER\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nReset your password with this link:\r\n\r\nhttp://localhost/reset#RANDOM_ID\r\n\r\n--IDENTIFIER\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n;\r\n\r\n\r\n Hey loco,\r\n Forgot your password? No worries! You can reset it by clicking the link b=\r\nelow:\r\n { + let mut settings = insta::Settings::clone_current(); + settings.set_prepend_module_to_snapshot(false); + settings.set_snapshot_suffix("auth_request"); + let _guard = settings.bind_to_scope(); + }; +} + +#[tokio::test] +#[serial] +async fn can_get_current_user() { + configure_insta!(); + + testing::request::(|request, ctx| async move { + let user = prepare_data::init_user_login(&request, &ctx).await; + + let (auth_key, auth_value) = prepare_data::auth_header(&user.token); + let response = request + .get("/api/user/current") + .add_header(auth_key, auth_value) + .await; + + with_settings!({ + filters => testing::cleanup_user_model() + }, { + assert_debug_snapshot!((response.status_code(), response.text())); + }); + }) + .await; +} diff --git a/nixin_farm_ssr/tests/tasks/mod.rs b/nixin_farm_ssr/tests/tasks/mod.rs new file mode 100644 index 0000000..01fbdda --- /dev/null +++ b/nixin_farm_ssr/tests/tasks/mod.rs @@ -0,0 +1 @@ +pub mod seed; diff --git a/nixin_farm_ssr/tests/tasks/seed.rs b/nixin_farm_ssr/tests/tasks/seed.rs new file mode 100644 index 0000000..7de7d38 --- /dev/null +++ b/nixin_farm_ssr/tests/tasks/seed.rs @@ -0,0 +1,17 @@ +use loco_rs::{boot::run_task, task, testing}; +use nixin_farm_ssr::app::App; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn test_can_seed_data() { + let boot = testing::boot_test::().await.unwrap(); + + assert!(run_task::( + &boot.app_context, + Some(&"seed_data".to_string()), + &task::Vars::default() + ) + .await + .is_ok()); +} diff --git a/nixin_farm_ssr/tests/workers/mod.rs b/nixin_farm_ssr/tests/workers/mod.rs new file mode 100644 index 0000000..090884b --- /dev/null +++ b/nixin_farm_ssr/tests/workers/mod.rs @@ -0,0 +1 @@ +// workers mod