From 9f12d44489968275548871607e544b1e65730914 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 30 Dec 2025 03:28:03 +0100 Subject: [PATCH] feat: Implement a new layered architecture for the QR generator, integrating Axum, Maud, and HTMX, and updating documentation. --- README.md | 79 +- backend/.dockerignore | 1 + backend/Cargo.lock | 2299 +++++++++++++++---- backend/Cargo.toml | 31 +- backend/Dockerfile | 35 +- backend/src/application/generate_qr.rs | 53 + backend/src/application/mod.rs | 1 + backend/src/domain/error.rs | 23 + backend/src/domain/mod.rs | 2 + backend/src/domain/qr.rs | 125 + backend/src/infrastructure/config.rs | 29 + backend/src/infrastructure/http/favicon.ico | Bin 0 -> 47481 bytes backend/src/infrastructure/http/handlers.rs | 28 + backend/src/infrastructure/http/logo.png | Bin 0 -> 43954 bytes backend/src/infrastructure/http/mod.rs | 3 + backend/src/infrastructure/http/server.rs | 36 + backend/src/infrastructure/http/style.css | 250 ++ backend/src/infrastructure/http/views.rs | 67 + backend/src/infrastructure/mod.rs | 3 + backend/src/infrastructure/qr_adapter.rs | 28 + backend/src/lib.rs | 4 + backend/src/main.rs | 108 +- backend/src/ports/mod.rs | 18 + docker-compose.yml | 19 +- nginx/default.conf | 7 - 25 files changed, 2713 insertions(+), 536 deletions(-) create mode 100644 backend/.dockerignore create mode 100644 backend/src/application/generate_qr.rs create mode 100644 backend/src/application/mod.rs create mode 100644 backend/src/domain/error.rs create mode 100644 backend/src/domain/mod.rs create mode 100644 backend/src/domain/qr.rs create mode 100644 backend/src/infrastructure/config.rs create mode 100644 backend/src/infrastructure/http/favicon.ico create mode 100644 backend/src/infrastructure/http/handlers.rs create mode 100644 backend/src/infrastructure/http/logo.png create mode 100644 backend/src/infrastructure/http/mod.rs create mode 100644 backend/src/infrastructure/http/server.rs create mode 100644 backend/src/infrastructure/http/style.css create mode 100644 backend/src/infrastructure/http/views.rs create mode 100644 backend/src/infrastructure/mod.rs create mode 100644 backend/src/infrastructure/qr_adapter.rs create mode 100644 backend/src/lib.rs create mode 100644 backend/src/ports/mod.rs delete mode 100644 nginx/default.conf diff --git a/README.md b/README.md index 85ec8bb..e2612f8 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,80 @@ -# QR Code generator +# K-QR - Rust QR Code Generator -Simple qr code generator written in Rust +A lightweight, high-performance QR code generator built with Rust, Axum, Maud, and HTMX. -# Build +## Features -```bash -cargo build --release -``` +- **Blazing Fast**: Built with Rust for maximum performance. +- **Embedded UI**: The web interface is completely embedded in the binary. +- **Interactive**: Uses HTMX for seamless, single-page-like experience without complex JS frameworks. +- **Configurable**: Easily customize host, port, and default styles via environment variables. + +## Tech Stack + +- **Backend**: Rust / Axum +- **Templating**: Maud (Type-safe HTML) +- **Frontend Interactivity**: HTMX +- **QR Generation**: `qrcode` crate + +## Configuration + +The application can be configured using environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `SERVER_HOST` | Network interface to bind to | `0.0.0.0` | +| `SERVER_PORT` | Port to listen on | `3000` | +| `QR_DEFAULT_COLOR` | Default color for generated QR codes (HEX) | `#000000` | +| `RUST_LOG` | Logging level (`debug`, `info`, `warn`, `error`) | `qr_generator=debug` | + +## Getting Started + +### Prerequisites + +- [Rust](https://rustup.rs/) (latest stable) +- [Docker](https://www.docker.com/) (optional, for containerized deployment) + +### Local Development + +1. Clone the repository: + ```bash + git clone + cd qr-generator + ``` + +2. Run the backend: + ```bash + cd backend + cargo run + ``` + +3. Open your browser at `http://localhost:3000`. + +### Docker Deployment + +Use Docker Compose to spin up the application: ```bash docker-compose up --build -d ``` -# License +Or pull the pre-built image: -MIT +```bash +docker run -d -p 3000:3000 --name k-qr registry.gabrielkaszewski.dev/k-qr:latest +``` + +## API Usage + +You can also generate QR codes directly via the API: + +```bash +GET /api/qr?data=https://github.com +``` + +**Parameters:** +- `data` (required): The URL or text to encode in the QR code. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7d57a17..5978b8d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1,33 +1,63 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "addr2line" -version = "0.21.0" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "gimli", + "memchr", ] [[package]] -name = "adler" -version = "1.0.2" +name = "aligned" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] [[package]] -name = "adler32" -version = "1.2.0" +name = "aligned-vec" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] [[package]] -name = "async-trait" -version = "0.1.73" +name = "anstyle" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", @@ -35,21 +65,102 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "arraydeque" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] [[package]] name = "axum" -version = "0.7.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "async-trait", "axum-core", "axum-macros", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -62,12 +173,11 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", "tower", "tower-layer", @@ -77,20 +187,18 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", - "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -98,81 +206,101 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ - "heck", "proc-macro2", "quote", "syn", ] [[package]] -name = "backtrace" -version = "0.3.69" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.1", - "object", - "rustc-demangle", + "serde_core", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bitstream-io" +version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] [[package]] -name = "bitflags" -version = "2.6.0" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] -name = "byteorder" -version = "1.5.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.5.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", + "jobserver", "libc", + "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "checked_int_cast" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "color_quant" @@ -181,104 +309,321 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "crc32fast" -version = "1.3.2" +name = "config" +version = "0.15.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] -name = "deflate" -version = "0.8.6" +name = "equator" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" dependencies = [ - "adler32", - "byteorder", + "equator-macro", ] [[package]] -name = "either" -version = "1.9.0" +name = "equator-macro" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "erased-serde" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "fragile" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", @@ -287,49 +632,98 @@ dependencies = [ ] [[package]] -name = "gif" -version = "0.11.4" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" dependencies = [ "color_quant", "weezl", ] [[package]] -name = "gimli" -version = "0.28.0" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] -name = "hermit-abi" -version = "0.3.3" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] [[package]] name = "http" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -337,12 +731,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -350,9 +744,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -362,95 +756,330 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", ] [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "hyper", "pin-project-lite", "tokio", + "tower-service", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "image" -version = "0.23.14" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", + "exr", "gif", - "jpeg-decoder", - "num-iter", - "num-rational", + "image-webp", + "moxcms", "num-traits", "png", - "scoped_threadpool", + "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core 0.5.0", + "zune-jpeg 0.5.8", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] -name = "jpeg-decoder" -version = "0.1.22" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "rayon", + "getrandom 0.3.4", + "libc", ] [[package]] -name = "libc" -version = "0.2.149" +name = "json5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "k-qr" +version = "0.2.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "base64", + "config", + "image", + "maud", + "mockall", + "qrcode", + "serde", + "thiserror", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "maud" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df518b75016b4289cdddffa1b01f2122f4a49802c93191f3133f6dc2472ebcaa" +checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" dependencies = [ "axum-core", "http", @@ -460,30 +1089,31 @@ dependencies = [ [[package]] name = "maud_macros" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa453238ec218da0af6b11fc5978d3b5c3a45ed97b722391a2a11f3306274e18" +checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" dependencies = [ - "proc-macro-error", "proc-macro2", + "proc-macro2-diagnostics", "quote", "syn", ] [[package]] -name = "memchr" -version = "2.6.4" +name = "maybe-rayon" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] [[package]] -name = "memoffset" -version = "0.9.0" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -493,129 +1123,106 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.3.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", + "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "mockall" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ - "autocfg", "num-traits", + "pxfm", ] [[package]] -name = "num-iter" -version = "0.1.43" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] -name = "num-rational" -version = "0.3.2" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] -name = "once_cell" -version = "1.18.0" +name = "noop_proc_macro" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] -name = "percent-encoding" -version = "2.3.0" +name = "nu-ansi-term" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pin-project" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "pin-project-internal", + "windows-sys 0.61.2", ] [[package]] -name = "pin-project-internal" -version = "1.1.3" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", @@ -623,10 +1230,145 @@ dependencies = [ ] [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "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", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -636,84 +1378,233 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "png" -version = "0.16.8" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "crc32fast", - "deflate", - "miniz_oxide 0.3.7", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "potential_utf" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", + "zerovec", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] [[package]] -name = "qr-generator" -version = "0.1.0" +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "axum", - "image", - "maud", - "qrcode", - "tokio", - "tower-http", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", ] [[package]] name = "qrcode" -version = "0.12.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" dependencies = [ - "checked_int_cast", "image", ] [[package]] -name = "quote" -version = "1.0.33" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] -name = "rayon" -version = "1.8.0" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -721,37 +1612,81 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scopeguard" @@ -761,18 +1696,40 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -781,23 +1738,35 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", ] [[package]] @@ -813,26 +1782,83 @@ dependencies = [ ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "libc", - "windows-sys", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "syn" -version = "2.0.38" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -841,48 +1867,15 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] -name = "sync_wrapper" -version = "1.0.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] - -[[package]] -name = "tokio" -version = "1.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" -dependencies = [ - "backtrace", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -890,15 +1883,142 @@ dependencies = [ ] [[package]] -name = "tower" -version = "0.4.13" +name = "termtree" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -907,95 +2027,266 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.6.0", + "bitflags", "bytes", "http", "http-body", - "http-body-util", "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "cfg-if", "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-core" +name = "tracing-attributes" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "once_cell", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "weezl" -version = "0.1.7" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -1004,42 +2295,228 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" +dependencies = [ + "zune-core 0.5.0", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8110164..5551b98 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,13 +1,24 @@ [package] -name = "qr-generator" -version = "0.1.0" -edition = "2021" - +name = "k-qr" +version = "0.2.0" +edition = "2024" [dependencies] -axum = { version = "0.7.5", features = ["macros"] } -tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } -qrcode = "0.12.0" -image = "0.23.14" -maud = { version = "0.26.0", features = ["axum"] } -tower-http = { version = "0.5.2", features = ["cors"] } +axum = { version = "0.8.8", features = ["macros"] } +tokio = { version = "1.0", features = ["full"] } +maud = { version = "0.27", features = ["axum"] } +qrcode = "0.14" +image = "0.25" +tower-http = { version = "0.6.8", features = ["trace"] } +serde = { version = "1.0", features = ["derive"] } +thiserror = "2.0.17" +anyhow = "1.0" +config = "0.15.19" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +url = "2.5" +base64 = "0.22.1" +async-trait = "0.1.89" + +[dev-dependencies] +mockall = "0.14.0" diff --git a/backend/Dockerfile b/backend/Dockerfile index 9993c51..bfd1aa7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,23 +1,40 @@ -FROM rust:1 AS chef -# We only pay the installation cost once, -# it will be cached from the second build onwards +FROM rust:1-slim AS chef + RUN cargo install cargo-chef WORKDIR /app FROM chef AS planner COPY . . -RUN cargo chef prepare --recipe-path recipe.json +RUN cargo chef prepare --recipe-path recipe.json FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json -# Build dependencies - this is the caching Docker layer! +# Build dependencies - this is the caching layer RUN cargo chef cook --release --recipe-path recipe.json # Build application COPY . . -RUN cargo build --release --bin qr-generator +RUN cargo build --release --bin k-qr -# We do not need the Rust toolchain to run the binary! +# Runtime stage FROM debian:bookworm-slim AS runtime WORKDIR /app -COPY --from=builder /app/target/release/qr-generator /usr/local/bin -ENTRYPOINT ["/usr/local/bin/qr-generator"] \ No newline at end of file + +# Install necessary runtime dependencies (if any, usually none for static rust binaries) +# Create a non-root user +RUN groupadd -g 10001 appgroup && \ + useradd -u 10001 -g appgroup appuser + +# Copy the binary from the builder stage +COPY --from=builder /app/target/release/k-qr /usr/local/bin/k-qr + +# Set default environment variables +ENV SERVER_HOST=0.0.0.0 +ENV SERVER_PORT=3000 +ENV RUST_LOG=k_qr=info + +# Use the non-root user +USER appuser + +EXPOSE 3000 + +ENTRYPOINT ["/usr/local/bin/k-qr"] \ No newline at end of file diff --git a/backend/src/application/generate_qr.rs b/backend/src/application/generate_qr.rs new file mode 100644 index 0000000..3004b56 --- /dev/null +++ b/backend/src/application/generate_qr.rs @@ -0,0 +1,53 @@ +use crate::domain::{ + error::QrError, + qr::{QrData, QrOptions}, +}; +use crate::ports::QrCodeGenerator; +use std::sync::Arc; + +pub struct GenerateQrUseCase { + generator: Arc, +} + +impl GenerateQrUseCase { + pub fn new(generator: Arc) -> Self { + Self { generator } + } + + pub async fn execute(&self, data: String, options: QrOptions) -> Result, QrError> { + let qr_data = QrData::new(data)?; + self.generator.generate(&qr_data, &options).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ports::MockQrCodeGenerator; + + #[tokio::test] + async fn test_generate_qr_success() { + let mut mock_generator = MockQrCodeGenerator::new(); + mock_generator + .expect_generate() + .times(1) + .returning(|_, _| Ok(vec![1, 2, 3])); + + let use_case = GenerateQrUseCase::new(Arc::new(mock_generator)); + let result = use_case + .execute("https://example.com".to_string(), QrOptions::default()) + .await; + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), vec![1, 2, 3]); + } + + #[tokio::test] + async fn test_generate_qr_invalid_data() { + let mock_generator = MockQrCodeGenerator::new(); + let use_case = GenerateQrUseCase::new(Arc::new(mock_generator)); + + let result = use_case.execute("".to_string(), QrOptions::default()).await; + assert!(matches!(result, Err(QrError::EmptyData))); + } +} diff --git a/backend/src/application/mod.rs b/backend/src/application/mod.rs new file mode 100644 index 0000000..2a47407 --- /dev/null +++ b/backend/src/application/mod.rs @@ -0,0 +1 @@ +pub mod generate_qr; diff --git a/backend/src/domain/error.rs b/backend/src/domain/error.rs new file mode 100644 index 0000000..df296eb --- /dev/null +++ b/backend/src/domain/error.rs @@ -0,0 +1,23 @@ +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ConfigError { + #[error("Environment variable not found: {0}")] + EnvVarMissing(String), + #[error("Invalid integer configuration: {0}")] + InvalidInteger(String), + #[error("Invalid option: {0}")] + InvalidOption(String), +} + +#[derive(Error, Debug, PartialEq)] +pub enum QrError { + #[error("QR Code data cannot be empty")] + EmptyData, + #[error("QR Code data is too long: {0} characters")] + DataTooLong(usize), + #[error("Failed to generate QR code: {0}")] + GenerationFailed(String), + #[error("Invalid option: {0}")] + InvalidOption(String), +} diff --git a/backend/src/domain/mod.rs b/backend/src/domain/mod.rs new file mode 100644 index 0000000..2d3fbf8 --- /dev/null +++ b/backend/src/domain/mod.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod qr; diff --git a/backend/src/domain/qr.rs b/backend/src/domain/qr.rs new file mode 100644 index 0000000..acd4f13 --- /dev/null +++ b/backend/src/domain/qr.rs @@ -0,0 +1,125 @@ +use super::error::QrError; +use serde::Deserialize; + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct QrData(String); + +impl QrData { + pub fn new(data: impl Into) -> Result { + let data = data.into(); + if data.trim().is_empty() { + return Err(QrError::EmptyData); + } + if data.len() > 2048 { + return Err(QrError::DataTooLong(data.len())); + } + Ok(Self(data)) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +/// Newtype for validated hex color codes. +#[derive(Debug, Clone, PartialEq)] +pub struct HexColor(String); + +impl HexColor { + pub fn new(color: impl Into) -> Result { + let color = color.into(); + let trimmed = color.trim(); + + // Must start with # and be 7 chars (#RRGGBB) or 4 chars (#RGB) + if !trimmed.starts_with('#') { + return Err(QrError::InvalidOption(format!( + "Color must start with #: {}", + color + ))); + } + + let hex_part = &trimmed[1..]; + if hex_part.len() != 6 && hex_part.len() != 3 { + return Err(QrError::InvalidOption(format!( + "Invalid color length: {}", + color + ))); + } + + if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(QrError::InvalidOption(format!( + "Invalid hex characters: {}", + color + ))); + } + + Ok(Self(trimmed.to_string())) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl Default for HexColor { + fn default() -> Self { + Self("#000000".to_string()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct QrOptions { + pub color: HexColor, +} + +impl Default for QrOptions { + fn default() -> Self { + Self { + color: HexColor::default(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_qr_data_valid() { + let input = "https://example.com"; + let data = QrData::new(input); + assert!(data.is_ok()); + assert_eq!(data.unwrap().as_str(), input); + } + + #[test] + fn test_qr_data_empty() { + let input = " "; + let data = QrData::new(input); + assert_eq!(data, Err(QrError::EmptyData)); + } + + #[test] + fn test_qr_data_too_long() { + let input = "a".repeat(2049); + let data = QrData::new(&input); + match data { + Err(QrError::DataTooLong(len)) => assert_eq!(len, 2049), + _ => panic!("Expected DataTooLong error"), + } + } + + #[test] + fn test_hex_color_valid() { + assert!(HexColor::new("#000000").is_ok()); + assert!(HexColor::new("#fff").is_ok()); + assert!(HexColor::new("#E57E1D").is_ok()); + } + + #[test] + fn test_hex_color_invalid() { + assert!(HexColor::new("000000").is_err()); // Missing # + assert!(HexColor::new("#gggggg").is_err()); // Invalid chars + assert!(HexColor::new("#12345").is_err()); // Wrong length + } +} diff --git a/backend/src/infrastructure/config.rs b/backend/src/infrastructure/config.rs new file mode 100644 index 0000000..6aefbb7 --- /dev/null +++ b/backend/src/infrastructure/config.rs @@ -0,0 +1,29 @@ +use crate::domain::error::ConfigError; +use config::Config as Configuration; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone)] +pub struct AppConfig { + pub server_host: String, + pub server_port: u16, + pub qr_default_color: String, +} + +impl AppConfig { + pub fn load() -> Result { + let builder = Configuration::builder() + .set_default("server_host", "0.0.0.0") + .map_err(|e| ConfigError::InvalidOption(e.to_string()))? + .set_default("server_port", 3000) + .map_err(|e| ConfigError::InvalidInteger(e.to_string()))? + .set_default("qr_default_color", "#000000") + .map_err(|e| ConfigError::InvalidOption(e.to_string()))? + .add_source(config::Environment::default()); + + builder + .build() + .map_err(|e| ConfigError::EnvVarMissing(e.to_string()))? + .try_deserialize() + .map_err(|e| ConfigError::InvalidOption(e.to_string())) + } +} diff --git a/backend/src/infrastructure/http/favicon.ico b/backend/src/infrastructure/http/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c8e0c8054ff989912ca5a6951c5f1c674de9d8a2 GIT binary patch literal 47481 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�jF=3 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 literal 0 HcmV?d00001 diff --git a/backend/src/infrastructure/http/handlers.rs b/backend/src/infrastructure/http/handlers.rs new file mode 100644 index 0000000..43d2772 --- /dev/null +++ b/backend/src/infrastructure/http/handlers.rs @@ -0,0 +1,28 @@ +use crate::application::generate_qr::GenerateQrUseCase; +use crate::domain::qr::QrOptions; +use crate::infrastructure::http::views::{index_page, qr_component}; +use axum::{ + extract::{Query, State}, + response::IntoResponse, +}; +use std::collections::HashMap; +use std::sync::Arc; + +pub async fn index() -> impl IntoResponse { + index_page() +} + +pub async fn generate( + State(use_case): State>, + Query(params): Query>, +) -> impl IntoResponse { + let data = params.get("data").cloned().unwrap_or_default(); + + // Simple options for now, can be extended to parse from params + let options = QrOptions::default(); + + match use_case.execute(data, options).await { + Ok(image_data) => qr_component(&image_data).into_response(), + Err(e) => format!("
Error: {}
", e).into_response(), + } +} diff --git a/backend/src/infrastructure/http/logo.png b/backend/src/infrastructure/http/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..960dc98161038bc0071b33958fd2c6296e37e544 GIT binary patch literal 43954 zcmeEu_dk~J`~PJWC8JkGl8^>v6d@EcOG1kzqwHPD%DC?)p(3kWWE3iUXWk7VdxVg^ zS9aFzzP`tGYrkHf&*S?Ce13R8-rn!4>%5NhIFI=}p67i}Q(c8|?UuC&f-oLCs(1=P zXyH#VjYp<$a5BwqWN;*m4X zVWVxX6I;)Qqv$l@*irhRSG31sCJZA(n040ah<6GV>2?p_6*k!C$B{yRlTEXSQJ8NJ z(;hm-jqCG29M_^cqI9(<*R3ngDmrOR9>aQ`OY2CmqlPE|{7huhpSaS>8M;<+#9VX+rP!iV%bvtKcYdzlw~wDM-^I?qHeEKcEK*TIHK#!&f25wKh?a zV%>oz3p%PupVbIHgX*%6nmy%PPjfy<;d0?yxgf=+-5=16gkZ}7*w$f*1}UN%takVS zf?pfZEqj>m&<>-Uj;sh$V;^sDT~Fd_>IY{6Gh$#8gat-Vf*j!q-|% z8K1%p!-?A1df>Qw6ur@zV05Mt^}v)Ur?fiz|WNT7}ugxrXv%Au=q0T z4kXtyQAKWsjeGlb%euE2EVdB+DO=cA*F?3=GQ8tCC#~E#wuk8EqlG)`^D?n>>vSuZ zlEuE+{)`Ga1LwBE_u@w5TM&D~&rfC!wz%)Ro+AE%tTM2k zV_Ok~TS_*iD&iTaat`*T3MbHGay|)@P;q4Q2uXE}1<3{5(Uh-Lp2Dk9w2wDWOsUdn z1Xbl>i}8NIT`zB+r5xu>RcaL~2delQMTG=Xx^ZD@@h0o)F+))8VQ242oE`zY`|N~0 zH5Y$$N2Ib2J8xC9Cqe@QNp%>{pY0_ z5QMMbga}h+m0c|pH8s?Jt>JS5(%+UVGsi z>%>2kSs6Z&{SeADEjLAVihiX1oSlunq2>=8#N|;U0H9$c!Z<}R$#0^gLwIF zL=`Hlv?ph*%-~x=L1bFPn+_g%UQc%U{wd2!xn{M&=JRi^qMNL5#0( z0Mu{*Z$%RQ_se6Ne4}q2RBIP*kjzQ@CJB4mXuU(C>{>8(-uNTAwNj`Se9!Sdd=DZ} z{cf^w6LvO!wV=zj@I>Dmp1gplB^P^)BBRK8i*Q+GP=81?6+#QPpZ#&hog#%~L8TO~ z0L(yGkp`g${r_gj+{yPvn9?c1xHB2f?S_T;qrJw9w<%({U%TT#J`D#)+5{WpK1F&c zDs8YNafTwlOpC2!A?xLEI5=rc{yOU&J|>}2m;*!}XVo1rv+zVxHo$u?-F6 zn#}2_UV$PbUo;>6>TeNB`P$9zcC2|8a?nl?K4eIoqQ_NFY{Q9=oHUi-!KzavW-)1l zd-chf7e*QmX&}4K4P;xvf_GuT$mXL$fkVeBClh$VJRD5}@hw68RRP^NU$uv2n_pt@ z$sJpidh&Mt0Iy)sbBm9)_>#qe8i6W<-11A@HO6^ zb67azmOHC?8(C#3{-{rcGa1u7H_P6H=>TxuvsmDSm}OQvV?+`DhU9xGbi){n+$Qjd zwcH=jeT019TbU4v5n9s%-y%vRj>KGd6@;YY#*1MTWytAW3U_MZ!KXoNnRyJRa~7Q$ zM)y&U>_*F;XB|>ReK;~xII?Y4;*Yq>uEkQ`d$U|4g$sr~SHOygcYMGt7NLEVU2Wgr zEJc(DEox!!fb4X}q0cF@|Fbu7y2RWQDT5QK;XOqn>cx&*U~#~!qBU{42@bN%3!cz` z9r{@$`tC0~3C}L2pDe!ExB!6|p9XPyMh*;szrKWG0E)w^Bc$CBKTII-42tlROFm{O z7i&_4_y1|Wk!m2K9=P9eW2v*!bZ{uW!RH3dEa7vv;d4sAneU^=E9T3`04EZ1bkZPX z=m`4;ES({NMSIv)bO&@n4iPAMt{qGF&o_uorpV7nnA05~MsZT}(ZG9bM<->^$Euk4 zHev}uQ{;*9R$EFo_Tq#@!Vr3YN5K>%c`SbHoD~}((ZO-1g8D~!Ar5qq;)u)16w~K> zIm3<&r6XEU5?xZJK|ZTgDnF8$#d%W>;E}Frc*HACYA^5#L$0HfIB&IVN>TK}>vY0& zHe{zukgQ9Od81KSOj5^uT2AS?Rq~p~g)!K~X7K5&a5@jmuf)FKt2wpKzRa5 z%~#FJ#8>kHwg)lYimE1R+fPi?MPU4X3hDl&c(rj#@Yg(vT87JNNYk=#z}}TXnU97% z-cMptBIc(VtMM7)eM9dX=ctuX;`fW!n;I8B!nYoSv5NS0qiO_?7nqG8G*uZ@=s8#o zPT&pqu}N9AUBWA6C9*k1U-?ovPFb3s1=Re8Ta4qQnaQFrCOne=#w>954 z5P9OXm+Rxv_mrdSTvZp34(TBy;Dk}2=ymex{?Cbm(h8U{AdW*NjSHf12H2y~JKzEv z&=880Z?>Jts!Fb5qPh+7C4~+aEh$|V*=2#Hr#IUY1Fs}F6h7mmrH9~Y$PD>4T%?i` z!|_L4;vC4XusuysvZI1G>MwG5kC87{mm=%Rl-WIX2eKhD$wRpQd>_(Uv>I1E7X0yQ zh34VJqpG6m>2czjE*(IMQ?P0B0QV?^NK7|7Bq}_E<13jMXIBYiTd`<)yTvl zsG6tP=FOkeV>9fU%Q6E6*ycn#lJlCXG+}Y>cIb07n*XT3D9|0mZI#MxP?f z;43>w%@Eb-sTh$r>nH`L=1Osa=Jbg14CoSqaz+gz7Xuct+c;OA za;$47*Nl*Y;59*bO$S1XKZKO^l#rt7n@GutV9w@|RQNLRg7Na1l^nqLX)bYk9L^kj zqWY*C_ehP|9E(wSZ=;0iy`T`KXYk;+kc{lnIybNz{LLGDGziLjUE|zi6cNr8e2_wU zAtd{oBax?`&uehahZKGrr!+~3X66rT*4(%Sc@YSO)|psJIz(u@PK}VvAgn1xfpmYA zmw4{y+Gu?q(g<7j1^^(a7OIN!Yq=Cn(e{~+kcbdqDNne6i^(j{KQ=CG0t3+p;p?DL zT9zgn6z`w}O8Pog_LB1h(hw8$qv0t;`Dv5I2P-R%xs8zAU4fQe&~X85EoR5BP6^NVy;Z8gtV4=YSSTtJDGsH`zr5y+f8#Th7E~>Q9XwJU=?x7)lQSTQrDNeMlQ7B-0_&vMmsg^)JEZkmI022a85fFM z@MUV8A0S3@Qh#PUfUt-`iYv{~DyHw_14;O@f7)>`#QKx)-iV2m3gvryx~`9qTET`~ z;CmUA+Jc6nV@>{&a|7Ct{HDOb&B0@?5+1BXEmfVBX20~-Y9tzz-z>5$x3bod^$6(% zSWkjEv|tbhnMubDmYh;3!k?+To<5z4*M-=s1BZ=if2G+s&g|kh>KCviaN3ORKv?eb zh#9d~yn2#KXTF>2YEFez2RNBAZ1<%9ul~u{p|-lkw3$n~Wk15rce;3%uorz9%SluE ztE;+&?G%bwUE@#)aix}hcOMX3B|^wlhT8q%=R^aF(*%?PZBwfvk~pb13shxz-Q9?u zHa-t_S^BDNfB$If`C_|9b!z0IsxrHM55L1{BNOj~Co-#qA$hD@9jBvs1p08hSsqH- z*AaAnZd90mGaf}!c5@vrH0|H|v2NqZ^E-{A9iDb9@HmH=tiSUUSuoD*(=jp?wq~qA z@NFD95&Id;n&?cq<;T;iaL{t6Ky$}(=oi|6PpI3A4sn7j6-bW-x%cyyOv=eAT0HZG zdQlhp7gwkk-@=*3n=Ga{GI8%E<``8KCLW$x2id1RrW$!Y85nv@IaN_9@H;M%vO7#~ z{efTYoZs2SPw31Lis0WK?^x|uxC!y9ab(|+Kxyytpkz7%t4o_mLA3H~NRxG}P?GI= zi$nZM*gDRMCgWC>pHUoAA0a&fhs=9V*z^{nVW`?1M8mWe9tSi`Nie;OA3NH(RJzUS zhFWdwx|ETkA%H;&z*h7ic0vA*XrGE`U+jP)x^%eMQ>vc>t!%&{pV`K7XDh?R{A2b+ z2a;(!kB+z{J2cOXday0NRUfBMvhUDWDgIcdQH z6sxH&+}iP2DSgD0nCtitcSv*Nf)Z$UBO^i!MtV^sF&HcB{2H9JvnuC2-Y23+&GB*4 zl8k-(4v)2XzMuFDltecH&0#CQM!o)xFu26qJ1CZ3bJp}2X&tLO8w`mxX{fc0CE2in`kan6sx zy)>WmGL4EhX(Xq5DJ48etxEpHbYr;t%bK%4p&XsIZ=Mfwx@9r7Z;_b|4JCNcmY-9! zxZ~gfz1JBwGxZn#-RM?A5=1lOOozPv>1>%5{WdMfkjPMFMhVldgk1LhXQbEol>=tQ zW$q~_RtumZl-zXW#Gw7kl572%o5=At9`pOM$u#)b^j+Twb3|`C>g}pSw~%@|YTE{; zo5N6fZK_nhnxhs0h>r2>JgZUFg*p-G*GpW;8AksWR7G^S-_O7082*A#b`|ctH0>*Z zF4h8M`2*-I9ex*)RhhhPU|1Hp-RY1#C~9z>*NKW-wYqdOpapT9v<=|)cfc`MP$Of9 zjtHs#o31%C%m`18M<*l51&1jxDR=x6T+{xhFD2lT^tCDv;tBQoAJbTW#5cBRmN5R4 zk0HK2RGS9ePRGWj@i9BYd75OD2Dxq4>rpHarpNX827N(^6lw#~U`g*b_@d}_E(#3g z*ANn%T^Zd`|CkQ%S4nP_vF|e8M(@RE^XSS8ho@Xyq6Z+kvVcDCtFd8WW$KA^e}Zo7 z-6<~QU9b#IXYpyvUF;vGSWo5_OQ9FXSombC{00Gj{DD{bHGIiyHSJqnhC{sGK2LQw zZNG;(Vy5v{i*N`Iw0%&K9d5GTF;{xv1Qz3}xkesqki+OuDmk^n2@Nq(hpopiC=Th5 zkk+*DXxfHx)}984TcDovyDQo+b_X|41UmkM$gvtR?=yW1CiLz;;#CjTR9=QcWIl** zwvx+Ro*Q7U$VeP}>@sIR%Y|GzF!woBa$x*iKNye(QWrs;k#Q?B?4W408Kg*F5qBL- zqJEp=iVL4bS{)%NBE9?Fy!QK;HC4Xo!YoMUQaI}ENFN9o%M57M$oTr1;-g~%a#|te zG#m2R$!#P$WWV&#giO6NCy_=j5Xza@PUP+1|Ui|38CzxKP(ZF=$ zYL~TF5&(q<2p)hVm+o=z?0~E>JrI`}0Hc5*lcjMtLBmBuId1tlodGs*Nf)Rfnjjjg z#Wsi(L!Xtl_>IQKzU|f*@OhJ&Z_k!0@*Z@>Azn~0^(0PLo6{YU#NIf-aZDlhI23L= zHqK>_>shla9NS+rATb9rY%2ssM@&blFnw-mTmXzUaw{C#UWh{HV#P$1&9QHMs&4J? z;OYU73~c6L9uW=E=o3N|E#iGWMo8DkxV>PAaZL%zRK9v0%rLZU2OsA+O?S#7FK)W* zZmf8oDu7!b!;v@*6OSA7S5_CvK!NSvjb6o6KO}~x+uE=bb)?rIk@O$=jBKSctEe}p zhFXUk0AO&XhvexF&;)bo0ZJBr)WB)qmyE^QvsY{5txSuZgWq`70m`wzg@;9=%H#8H zFwnjA4wxKSwi2erH-np}5-{N@kr7^^wJ9~SJ)>`OMpVbLb)aWh;d?Qv$#ZZ zH{^LuB{TIwQ7)1S#U%r;Ja+JicVsELw;H($$GZX&a>tisZ?-S)4z@Ur(#Y-nGsGpO zRN+*mX}h6~&4EL!8g(1vlBnU4V*Ao6KDT^tmI-#S=GQ=o1Gln$sv{w$RpeEFW`ra%#^S{gCvCj_15PiN&P1&SYJu5jp3?pC z=m!XJZw%ZAxvctq*9hEOA9Ge@*ZF}3J>J##(>X<;nw%?}VZ>y4lFcsadeW+M7KX)# zcDr*LE}nlgGmDqI-EI{g5yweA2}Sb}?0B`lt{{|Yi|>+K`x_e$lu{wWVn%KeIfD&{ zSXfTB?C8-PIV5|zD6$556A5K@rieRq0)9aqU-p}|wTK5oRi<~0tw1;9{@3 z6)aP3>%sDiP-~2b46SE(z`Wgl-1bQj2Omm>yD+SDSi^B?En}{EZ z38zI^Zb2P49YAnM=bDq=u3n!w?F1I|8PnEr;%4T+AI@LI2Q(@tA-n5koY8w3GbzGM!1k;E6BbOlx z|8c~bn>#wNkZI!b2uXR2 zn;rt=D723&ciz8UONXn(IPII0vD)La(@-^pa~fp*`IUaD ztIPLp2w+(22p$RFqg=%u!E~1=m|L>@Zn3TCZl1kCM?<|{Y6YUDXJAC~Vf6OicuIr` z5EWywadK8pS2h;q2A|raN!9rS=O`6ii2u2Ph+3wbm%)e_pkUo8;;xQ)!QD7tLi!66 z$IElFNTko*#``%Yrft55<*@2zXQK-kY8MzPx)Y{d4YCD`vT9gvQbOCs%NwsiZmMy& zJD5>@VRH3XYPrB4$%b+*O|*XrWPaT+Kc5@}vikY9a4`u^|X?FJJp{}XNYYY;|1 z@8?=#$2@W{XZVEJbc@0{RbJKO3DW>N%F&r6dV^Vdt5MR|m|rehu~Tq*o)vSIJ;!m> z6VKQkL~nCYvBbS>Trg?j(S%9aCV<<2MmUFh1&pUTn8OF%C!CJ#&Qcqiy&XX zC}4Wb$7+_a7EX2U4&qWcO)5s?NXYo zy>viN%|N2Sw3w?jZOh5UPzUlY*!b3}Gn*i^FFrwMvQbK89B6A6XO^vPZ6c@swM59$ z%!^x#6{LV12IQn_1eKo*gJF;(%f?%)Zia|1qsycK+cV=PU9;N%BG! z76=p+9tG`jAtw?E5}d_#TTY%|fpk7}3Y=XJ*>`(ArPKM@sSUN`SQ#d}Z(q1@=fdN9 z1pJP4y+Feti@4v%w5ihLi`l({;g`aRdeXGlAFh^L1&Q(uR34P-CX@(;+^j@F!7M5% zP~@}W>Xl7bFRIf(zc#h^Qp8i)m!|ELa{|fhV=}ZJ8-@?)9p72P{)!rceUFIVO9(Z? z_u`!Nmpk-pI4pI%*mn1J@9BLPwi+6^UhN_o&hM?=9O)Tee|`nquhBh#s6wr`Uj$kn zd1!fb7W2@VIaKK)kA9e+PrM(4{TAc^;%9JjY34R@NHoIrFf*-UVAkVsC@f!a(iSMt zME1h>4NR_kV)?^U!m+}|X_ovJS6w2Xe~^bwn}@Cc00Q})82!z{l6*|e{?q2*X0JNp z`GM+srW@^D6=yDe9}##c8~^M8(_Y7;{H)y{*5tfio3k&D3gEwmP0h%`?l&^_@2_D| zs_sKmYU4ss%aIz6jdyo~dzG6=>0_aA3^(|=8dXWvMo<5(!Za$s-1$tg>3)yk7O%Rs zBP8Q7mO;iU)tx&(pahX+EFCvAjultve-e3;y$Nmp`~6(1eU`5&1^EQe9S?yTL9_D% zI3RMgsnA5;2LIR<5OM`TFz)zd)T#B$jAm*!S=RF}OHz_RPT7TjHfvwn^G8Jg8cju^ zDd-SnFc@oiQm{BI=eddFM%)~V$4=r-ew)0Is)CTmH2uQTZMU$yZlv1+!DHfzmgp(~ zK}M=7w_kTD4BD+}`}ptYPDl?fjN9P&*ff(t@1bVW~Se2$N zX!qm}k@;EF)}EUpwEwAXhrAdkoh4?Zl=2W2| z^!Rsk>K_X(WaM_=i?Brf7%p+kZbAH^{BJc5#m>xfDEGla5phIzethAQzNY0`mpq7a zo)`8Fte=`&vv81cVSbIyXzQ4dT>_zrc+=!y^tC4rWZ+noJ`~jeK2XH2`omZjcRboA zYj(2S+F4clBg2Qn$WQVp^bo12l(pJIVcZYg!NYLu2(l$L0emZh|{&%FwjblraZ3JeGqCTl4J0^<^$ z-KTzzxk@dk#f?mbtO+*K83Flc*jWR&{FfzZ-5nGEvoVL60?^TnRcN^7p(+r^I1@m+ z@e5iqY5g*5evp~$D&ze+#i!#?S+F2;p~eE4>EQV70*!QMdt5}$d?twrIoa}Kf(D!! zZj7(QNNA786h0fkY5#Sst8Ee8%G%wh>lGrXyUi_PYMJwL`x@5>Q-7hNfH-I(IZ{eiZ0Qsg2ruaaG7osmw}pB2}QMJex&WH|x;6Oy81pXa0t7GL=^5?gM%V-B@xsZE4^3 z5fWRbf+4uKqTFw3viSpGpk`L@iHi}J{=J!@!uf&si@s0Fpo6H>!m3OOOS-pDYY{KZ zwGCpkG1oqy6J^xj&iU;Y`V^{-e{E&yN3tCBs#}>r-Z)U&)?#vOQO%f$W?JK2?yDrFI13eCJChU}+wMJl)!aX=v4 zC`!@siZe%gp~!KII34Jh{UobtTb)_b^$l3ZJ=B2C2;l+qo{&F)P(y#aGI#xv9yz*V zP@*AEmHZ(k8jNw*w-gqA8*=#Nv5Zj3uYCf|>tmiIPNUJB)WSv0JNEroS}gLubC6+j zBO@LFlE86$Z!AV00U9XuBPZ^kmF_;ULaZTspFSoB)8x656*5AaD%WC&3`y8jpy)68 zb@^o6E>wn*RfEUAr@$VPyZM?cyVe{{_YV`Ucfw9JCV2^_c_D@5|6Bk-MIpjz3EsJS zb?V-$g$^Etj`X97G#KeCuG-oCNA@G<+5{&&Af}efhuv}ZUM(kc04bZ3R9XY+!nm>H z8{y%J{_icA?f@@2${c_`dPQLJ?5tP8V7rA%PW#40n+>>yYWYJSHk__hh9d< zHOtB!&(0O{s%q;D$O6CY0CE%N+eEc^<8=*K13mXOOD894U7sxIkKxJ|Z0gC^F|ctb zlq#XejFu|$H&8kO9G}!%DWbCU(S}-t9-MP!@{CXL_;#-30%`g+7irGz-&*wgffYL| z4l#|8u8pyrEooLDY((pgb79)**N`q~q;Bv9AETr(-j>cah5y(ns)oW>aK_aUYn5L z(3ZbpmNwAg-{)XC&tk+{o%H17drD_ZnTvb9il&MLzDTV|4C+c5p3UYMWK_1Hr=fj5 z%*8M{C0L{&)K94yNKPywRnnw_K8Lz4_t;4(4>}lmCDR=GBt<+XeRNYTq6rMajzEi~ zUyVV~NFzEx2^2rmGmeg@C;xp%YvF4T%9O3|Q1r}+5zwt8p;Wxzd{+r6} zs++PlVW47NIGMi6i!6yFp@9spIRj_H)x#>ln5WwF+0 zoc2S)$A{6wLZNn)9d~x+F+sKPpy~Nxs;{lbG#3o3`v!$hyycA2&;sPUFId7M@qPS7U@e zY{N3 zCUl<4v6&*ud@58TOGU)&$~uR>!XF9k?@igd^hCM$Pf1*6gVcH!W8mFyIr0j>TJ8w6 zf7c0BKzA_CysA)AQIOXgLPl>=QreFSoGeYxKEO$C&$Vd3CnjnkQ0}TYv_eNXLuKYg z4T2ntrK7~2CQ@~7rU}V(Y{v(~|5UCmHm0wC>A*Vg)vzCH0C~zDOe;|RmegjJ9xT*N zSzcJy02H7@{%=du&DNV44khe=x>F`P-f2HN)(WZ|$4i;8^H0D^_3 z*#Pw}%xUUU9%I#asQ!z}ce+Ki??K)fXo|kGEi64hEt?5hFp_8^z0Ya(LsC6x(z^Np za&Wx&gCj>6z{NuvRF(%SvgWq0e3gaBc{w%*g2g55jRzsHcRSN#p>MusxTi5ugr zTLBvx2+#j)dn-g{-9C3d@Hh&TA3vA(X z9PmR{RP{{&%U%z0Am@Q$fzz^q05Vo$%Ul*2nAh$lKeMr2Qpn^;_}BhTM1KSOv3|}? z2^ZM{*IIJnAXl!8z^F*JdB0s=fz2yDa(ZM1H0f8$vKY;g%k3Y1#)41eb`pm^VHA!T zmG2Y|MV-s6ga73*=dkr%9 z=;!4Von@SrbZlxyF4mH;1};Ejuil&m#JnnLm(;eY91^40bg_aCnOAk?j!N{pgEZG} z{E^s91+59PvWek4*8NKbeWKqGBM*^u+iyJ{XMYesE~(HMa)B0~%Gc|nOTaYLaUYl9 zoNf!cu#98+*D)>>jKBb4sWuQMj6W~D?C%-upm1Gh#w}MeTfy`&>Zl%t`m8d6%HnrB zCf#M;-yf{wU|&O~%uKDrA-yXCDS4Cz#$HX!F(}OnDdb~XR!-A&CvkuwXEXnk=q-pF zQ^e`w>LaiodAJC}x?Ct&*H2Q|4sgnEN1%W|9_xfb1=pR8O2VAjvdP5F0QUtZa`)KJ zhi#Xo1Eo4gE$nWnIBCSUiY3NQXJEk6(`%E8yZZNJCa(Ak=mM&Pa4{fdCVNZgx5~to zQogT0=tZ+s!B|fzQP@&9P`G79Ufp7GFeIb!BjvQ55{4d^<32Q?dKgrfw#gV1zjpwu zG=&?S^(w=z9qfZhy^_Lw4|dxFZ7@rXfl{`&Uaa_$!uDZEY4chU8P96|DSJO=MtsN? zu`iE5q>s=rucqC8pj8U!r;66*h%per_F!xpYm%TS>E7aPCO?Quu5rIDReSYzw zaiQAE(+gHu_s5iWzPhEK4c9x2K7|Q~w&J0oFUjkIG5XVk!;Z~J6H&ETcw4UL?)Df6lI}?JjlmBhFaT`O5%e4F^ zCEe8hBW^)Z8r2agd;CN_@$^=1trcdS7gfnTMoy*?If0QxGCTbrEKI?$gL(DecJn#! z;fW-I4(_-MuyrWcl-AQcm^cEnq#rbvAB3{c(twWg>mDf{n2=QRU;;=a#ff)L)bwN6 zpQILgXnqT@`(T|N=h=+{pCJUj%xD`{nRmC))jofVzOHI0c~q23=D{B%mw%u#bp`T3 zNcjvurnuk6Bddjb-wF3tf8_1$4!N}c?Ad@}jA3G5dK62FA-V&yiJaSw!E2y(zJjrn zumpn{1t)hCAVo))qu2!%IW1Y|7e<8(9l+9>+VewF{Qhy&GgNYfuWS`IP`TIW;=#PM zeQRwV*wwhd#Z#=({hgy$6mJbf46J=aOgkO$3}y}AsT?c!w7h_x?YthslG)SPy^r9& zAwEF}J=^*v^{(>n6yY*0m>ys^vQp~#Rb~hpg?Bq3H^b!txXKsJxdRB?`p@Aa-}ZV> z%&8MS(u}GK!gbLb?q`2&6dzu9sVjF#Oq48Fwpwv$Z{xy4b5Hs;DpS)GCOPl#;1KXT z-W2}a=o$g#OdAey;Ulc25=oAP0b0T^r)_Io~t#0VOAFPQ$4~nY0LL@4&*`VkkKg`TDd
shYB;IvIJSOB|6@;@yESoU+`G!~%$kfl0`TU%4d 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 g|Gyi*&_=iU@*VPZoPx# literal 0 HcmV?d00001 diff --git a/backend/src/infrastructure/http/mod.rs b/backend/src/infrastructure/http/mod.rs new file mode 100644 index 0000000..1b6f60b --- /dev/null +++ b/backend/src/infrastructure/http/mod.rs @@ -0,0 +1,3 @@ +pub mod handlers; +pub mod server; +pub mod views; diff --git a/backend/src/infrastructure/http/server.rs b/backend/src/infrastructure/http/server.rs new file mode 100644 index 0000000..7523cea --- /dev/null +++ b/backend/src/infrastructure/http/server.rs @@ -0,0 +1,36 @@ +use crate::application::generate_qr::GenerateQrUseCase; +use crate::ports::HttpServer; +use async_trait::async_trait; +use axum::{routing::get, Router}; +use std::net::SocketAddr; +use std::sync::Arc; + +use super::handlers::{generate, index}; + +pub struct AxumServer { + use_case: Arc, +} + +impl AxumServer { + pub fn new(use_case: Arc) -> Self { + Self { use_case } + } +} + +#[async_trait] +impl HttpServer for AxumServer { + async fn run(&self, host: &str, port: u16) -> anyhow::Result<()> { + let app = Router::new() + .route("/", get(index)) + .route("/api/qr", get(generate)) + .with_state(self.use_case.clone()); + + let addr: SocketAddr = format!("{}:{}", host, port).parse()?; + tracing::info!("Server listening on {}", addr); + + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app).await?; + + Ok(()) + } +} diff --git a/backend/src/infrastructure/http/style.css b/backend/src/infrastructure/http/style.css new file mode 100644 index 0000000..58dc094 --- /dev/null +++ b/backend/src/infrastructure/http/style.css @@ -0,0 +1,250 @@ +:root { + /* K-QR Color Palette */ + --carbon-black: #1D1D1D; + --dim-grey: #656565; + --black: #000000; + --tiger-orange: #E57E1D; + + /* Semantic mapping */ + --primary: var(--tiger-orange); + --primary-hover: #cc6e18; + --bg: var(--black); + --card-bg: var(--carbon-black); + --text: #ffffff; + --text-muted: #a0a0a0; + --border: #3a3a3a; + --input-bg: #0a0a0a; +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--text); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; +} + +.container { + background: var(--card-bg); + padding: 2.5rem; + border-radius: 20px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8); + width: 100%; + max-width: 480px; + text-align: center; + border: 1px solid var(--border); +} + +.logo { + width: 100px; + height: 100px; + margin: 0 auto 1.25rem; + border-radius: 20px; + display: block; +} + +h1 { + font-weight: 700; + font-size: 2.25rem; + margin: 0 0 0.5rem; + color: var(--tiger-orange); +} + +.subtitle { + color: var(--text-muted); + font-size: 1rem; + margin: 0 0 2rem; + font-weight: 400; +} + +.input-group { + text-align: center; + margin-bottom: 1.5rem; +} + +label { + display: block; + font-size: 1rem; + font-weight: 500; + margin-bottom: 0.75rem; + color: var(--text); +} + +/* Input styling - targeted specifically */ +#data, +input[type="text"], +.input-group input { + width: 100%; + padding: 1rem 1.25rem; + border: 2px solid var(--tiger-orange); + border-radius: 14px; + font-size: 1.125rem; + font-family: inherit; + background-color: var(--input-bg); + color: var(--text); + transition: all 0.2s ease; + text-align: center; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +#data::placeholder, +input[type="text"]::placeholder { + color: var(--text-muted); + opacity: 1; +} + +#data:focus, +input[type="text"]:focus { + border-color: var(--tiger-orange); + box-shadow: 0 0 0 4px rgba(229, 126, 29, 0.3); + background-color: #111; +} + +button { + width: 100%; + background: var(--tiger-orange); + color: white; + padding: 1rem; + border: none; + border-radius: 14px; + font-size: 1.125rem; + font-weight: 600; + font-family: inherit; + cursor: pointer; + transition: all 0.2s ease; + margin-top: 0.5rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +button:hover { + background: var(--primary-hover); + transform: translateY(-2px); + box-shadow: 0 12px 24px -8px rgba(229, 126, 29, 0.5); +} + +button:active { + transform: translateY(0); +} + +.htmx-indicator { + display: none; + margin-top: 1.25rem; + font-size: 1rem; + color: var(--text-muted); +} + +.htmx-request .htmx-indicator { + display: block; +} + +.htmx-request button { + opacity: 0.7; + pointer-events: none; +} + +.qr-result { + margin-top: 2rem; + animation: fadeIn 0.4s ease-out; +} + +.qr-result img { + max-width: 100%; + width: 220px; + height: 220px; + border-radius: 16px; + border: 2px solid var(--border); + background: white; + padding: 12px; +} + +.error { + color: #ff6b6b; + padding: 1.25rem; + background: rgba(255, 107, 107, 0.1); + border: 1px solid rgba(255, 107, 107, 0.3); + border-radius: 14px; + margin-top: 1.25rem; + font-size: 1rem; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Responsive Design */ +@media (max-width: 480px) { + body { + padding: 0.75rem; + align-items: flex-start; + padding-top: 2rem; + } + + .container { + padding: 1.75rem; + border-radius: 16px; + } + + .logo { + width: 72px; + height: 72px; + } + + h1 { + font-size: 1.75rem; + } + + .subtitle { + font-size: 0.9rem; + } + + #data, + input[type="text"], + button { + padding: 0.875rem; + font-size: 1rem; + } + + .qr-result img { + width: 180px; + height: 180px; + } +} + +@media (min-width: 768px) { + .container { + padding: 3rem; + } + + .logo { + width: 120px; + height: 120px; + } + + h1 { + font-size: 2.5rem; + } +} \ No newline at end of file diff --git a/backend/src/infrastructure/http/views.rs b/backend/src/infrastructure/http/views.rs new file mode 100644 index 0000000..114a877 --- /dev/null +++ b/backend/src/infrastructure/http/views.rs @@ -0,0 +1,67 @@ +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use maud::{html, Markup, PreEscaped, DOCTYPE}; + +const LOGO_BYTES: &[u8] = include_bytes!("logo.png"); +const FAVICON_BYTES: &[u8] = include_bytes!("favicon.ico"); + +pub fn layout(title: &str, content: Markup) -> Markup { + let favicon = STANDARD.encode(FAVICON_BYTES); + html! { + (DOCTYPE) + html lang="en" { + head { + meta charset="utf-8"; + meta name="viewport" content="width=device-width, initial-scale=1.0"; + title { (title) } + link rel="icon" type="image/x-icon" href=(format!("data:image/x-icon;base64,{}", favicon)); + link rel="preconnect" href="https://fonts.googleapis.com"; + link rel="preconnect" href="https://fonts.gstatic.com" crossorigin; + link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"; + script src="https://unpkg.com/htmx.org@1.9.10" {} + style { + (PreEscaped(include_str!("style.css"))) + } + } + body { + (content) + } + } + } +} + +fn logo_base64() -> String { + STANDARD.encode(LOGO_BYTES) +} + +pub fn index_page() -> Markup { + let logo = logo_base64(); + layout( + "K-QR - QR Code Generator", + html! { + div.container { + img.logo src=(format!("data:image/png;base64,{}", logo)) alt="K-QR Logo"; + h1 { "K-QR" } + p.subtitle { "Generate QR codes instantly" } + form hx-get="/api/qr" hx-target="#result" hx-indicator=".container" { + div.input-group { + label for="data" { "Enter URL or text" } + input type="text" name="data" id="data" placeholder="https://example.com" required autocomplete="off"; + } + button type="submit" { "Generate QR Code" } + } + div id="loading" class="htmx-indicator" { "Generating..." } + div id="result" {} + } + }, + ) +} + +pub fn qr_component(image_data: &[u8]) -> Markup { + let base64_str = STANDARD.encode(image_data); + html! { + div.qr-result { + img src=(format!("data:image/png;base64,{}", base64_str)) alt="Generated QR Code"; + } + } +} diff --git a/backend/src/infrastructure/mod.rs b/backend/src/infrastructure/mod.rs new file mode 100644 index 0000000..0671667 --- /dev/null +++ b/backend/src/infrastructure/mod.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod http; +pub mod qr_adapter; diff --git a/backend/src/infrastructure/qr_adapter.rs b/backend/src/infrastructure/qr_adapter.rs new file mode 100644 index 0000000..d6b2a4e --- /dev/null +++ b/backend/src/infrastructure/qr_adapter.rs @@ -0,0 +1,28 @@ +use crate::domain::{ + error::QrError, + qr::{QrData, QrOptions}, +}; +use crate::ports::QrCodeGenerator; +use async_trait::async_trait; +use image::Luma; +use qrcode::QrCode; +use std::io::Cursor; + +pub struct QrCodeAdapter; + +#[async_trait] +impl QrCodeGenerator for QrCodeAdapter { + async fn generate(&self, data: &QrData, _options: &QrOptions) -> Result, QrError> { + let qr = + QrCode::new(data.as_str()).map_err(|e| QrError::GenerationFailed(e.to_string()))?; + + let qr_image = qr.render::>().build(); + let mut buffer: Vec = Vec::new(); + + qr_image + .write_to(&mut Cursor::new(&mut buffer), image::ImageFormat::Png) + .map_err(|e| QrError::GenerationFailed(e.to_string()))?; + + Ok(buffer) + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..4830b2f --- /dev/null +++ b/backend/src/lib.rs @@ -0,0 +1,4 @@ +pub mod application; +pub mod domain; +pub mod infrastructure; +pub mod ports; diff --git a/backend/src/main.rs b/backend/src/main.rs index 733c2d4..c4bdd18 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,92 +1,30 @@ -use std::collections::HashMap; +use std::sync::Arc; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use axum::{ - debug_handler, - extract::Query, - http::{header::CONTENT_TYPE, Method, StatusCode}, - response::IntoResponse, - routing::get, - Router, +use k_qr::application::generate_qr::GenerateQrUseCase; +use k_qr::infrastructure::{ + config::AppConfig, http::server::AxumServer, qr_adapter::QrCodeAdapter, }; -use image::{png::PngEncoder, ColorType, Luma}; -use maud::{html, Markup}; -use tower_http::cors::{Any, CorsLayer}; +use k_qr::ports::HttpServer; #[tokio::main] -async fn main() { - let app = Router::new() - .route("/", get(index)) - .route("/qr", get(get_qr_code)) - .layer( - CorsLayer::new() - .allow_origin(Any) - .allow_methods([Method::GET]), - ); +async fn main() -> anyhow::Result<()> { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "k_qr=debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + let config = AppConfig::load()?; + tracing::info!("Configuration loaded: {:?}", config); - axum::serve(listener, app).await.unwrap(); -} - -#[debug_handler] -async fn index() -> Markup { - html! { - html { - head { - title { "QR Code Generator" } - } - body { - h1 { "QR Code Generator" } - form action="/qr" method="get" { - label for="link" { "Value: " } - input type="text" name="link" id="link" required; - input type="submit" value="Generate QR Code"; - } - } - } - } -} - -async fn get_qr_code(Query(params): Query>) -> impl IntoResponse { - let link = match params.get("link") { - Some(l) => l, - None => { - return ( - StatusCode::BAD_REQUEST, - [(CONTENT_TYPE, "text/plain")], - "Missing link", - ) - .into_response(); - } - }; - - let qr_code = match qrcode::QrCode::new(link) { - Ok(qr) => qr, - Err(_) => { - return ( - StatusCode::BAD_REQUEST, - [(CONTENT_TYPE, "text/plain")], - "Invalid link", - ) - .into_response() - } - }; - - let qr_image = qr_code.render::>().build(); - let mut buffer: Vec = Vec::new(); - let width = qr_image.width(); - let height = qr_image.height(); - let encoder = PngEncoder::new(&mut buffer); - match encoder.encode(&qr_image.into_raw(), width, height, ColorType::L8) { - Ok(_) => (), - Err(_) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - [(CONTENT_TYPE, "text/plain")], - "Failed to encode QR code", - ) - .into_response() - } - } - (StatusCode::OK, [(CONTENT_TYPE, "image/png")], buffer).into_response() + let qr_adapter = Arc::new(QrCodeAdapter); + let generate_use_case = Arc::new(GenerateQrUseCase::new(qr_adapter)); + + let server: Box = Box::new(AxumServer::new(generate_use_case)); + + server.run(&config.server_host, config.server_port).await?; + + Ok(()) } diff --git a/backend/src/ports/mod.rs b/backend/src/ports/mod.rs new file mode 100644 index 0000000..b3c92a8 --- /dev/null +++ b/backend/src/ports/mod.rs @@ -0,0 +1,18 @@ +use crate::domain::{ + error::QrError, + qr::{QrData, QrOptions}, +}; +use async_trait::async_trait; + +/// Port for QR code generation +#[cfg_attr(test, mockall::automock)] +#[async_trait] +pub trait QrCodeGenerator: Send + Sync { + async fn generate(&self, data: &QrData, options: &QrOptions) -> Result, QrError>; +} + +/// Port for HTTP server - abstracts the web framework +#[async_trait] +pub trait HttpServer: Send + Sync { + async fn run(&self, host: &str, port: u16) -> anyhow::Result<()>; +} diff --git a/docker-compose.yml b/docker-compose.yml index 224cded..14489cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,16 @@ services: backend: - build: ./backend - nginx: - image: nginx:latest + build: + context: ./backend + dockerfile: Dockerfile + image: registry.gabrielkaszewski.dev/k-qr:latest + container_name: k-qr + ports: - - '8000:80' - volumes: - - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + - "${SERVER_PORT:-3000}:3000" + environment: + - SERVER_HOST=0.0.0.0 + - SERVER_PORT=3000 + - QR_DEFAULT_COLOR=${QR_DEFAULT_COLOR:-#000000} + - RUST_LOG=info + restart: unless-stopped diff --git a/nginx/default.conf b/nginx/default.conf deleted file mode 100644 index e4a85ef..0000000 --- a/nginx/default.conf +++ /dev/null @@ -1,7 +0,0 @@ -server { - listen 80; - - location / { - proxy_pass http://backend:3000/; - } -} \ No newline at end of file