commit 5893cd5e31e71dea60d15d5f2eaa28a8ab539660 Author: lychang <632945494@qq.com> Date: Fri Mar 20 02:31:07 2026 +0800 init diff --git a/.env b/.env new file mode 100644 index 0000000..1851c03 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite:data/imager.db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cad7c46 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3522 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags 2.11.0", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64 0.22.1", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[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 2.0.117", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[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 = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +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 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "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 = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "imager" +version = "0.1.0" +dependencies = [ + "actix-files", + "actix-web", + "chrono", + "config", + "image", + "reqwest", + "serde", + "serde_json", + "sqlx", + "tokio", + "uuid", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[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 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[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 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[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.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.12", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.11.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.11.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.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 = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[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 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[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 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8a0c99e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "imager" +version = "0.1.0" +edition = "2024" + +[dependencies] +actix-web = "4" +actix-files = "0.6" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "migrate"] } +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1.0", features = ["v4", "serde"] } +config = "0.13" +image = { version = "0.24", default-features = false, features = ["jpeg", "png", "gif", "webp"] } +reqwest = { version = "0.11", features = ["json"] } \ No newline at end of file diff --git a/assets/album.html b/assets/album.html new file mode 100644 index 0000000..5885773 --- /dev/null +++ b/assets/album.html @@ -0,0 +1,43 @@ + + + + + + 相册详情 - 图片展示服务 + + + + + +
+
+ + 返回首页 + +

加载中...

+
+ 正在加载... +
+
+ +
+
+
加载图片中... +
+
+
+ + + + + + + \ No newline at end of file diff --git a/assets/css/album.css b/assets/css/album.css new file mode 100644 index 0000000..4af095b --- /dev/null +++ b/assets/css/album.css @@ -0,0 +1,212 @@ +/* 相册详情页面特定样式 - 蓝白配色体系 */ +:root { + --primary-blue: #1e40af; + --secondary-blue: #3b82f6; + --light-blue: #dbeafe; + --accent-blue: #60a5fa; + --white: #ffffff; + --light-gray: #f8fafc; + --medium-gray: #e2e8f0; + --dark-gray: #475569; + --text-dark: #1e293b; + --text-light: #64748b; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1); + --border-radius: 12px; + --border-radius-lg: 20px; +} + +body { + font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; + background: linear-gradient(135deg, var(--light-blue) 0%, var(--white) 100%); + min-height: 100vh; + color: var(--text-dark); + line-height: 1.6; +} + +.container { + max-width: 1400px; + margin: 0 auto; + padding: 30px 20px; +} + +.header { margin-bottom: 40px; } +.back-link { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: var(--white); + color: var(--primary-blue); + text-decoration: none; + border-radius: var(--border-radius); + box-shadow: var(--shadow-sm); + margin-bottom: 25px; + transition: all 0.3s ease; + border: 1px solid var(--medium-gray); + font-weight: 500; +} +.back-link:hover { + background: var(--light-blue); + transform: translateX(-5px); + box-shadow: var(--shadow-md); + color: var(--primary-blue); +} +.album-title { + color: var(--text-dark); + font-size: 2.5rem; + text-align: center; + margin-bottom: 15px; + font-weight: 700; + background: linear-gradient(135deg, var(--primary-blue) 0%, var(--secondary-blue) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.album-info { + text-align: center; + color: var(--text-light); + font-size: 1.2rem; + margin-bottom: 40px; + background: var(--white); + padding: 20px; + border-radius: var(--border-radius); + box-shadow: var(--shadow-sm); + border: 1px solid var(--medium-gray); + max-width: 800px; + margin-left: auto; + margin-right: auto; +} + +.images-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 25px; + margin-bottom: 50px; +} +.image-item { + position: relative; + overflow: hidden; + border-radius: var(--border-radius); + box-shadow: var(--shadow-lg); + background: var(--white); + transition: all 0.3s ease; + cursor: pointer; + border: 1px solid var(--medium-gray); +} +.image-item:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-xl); + border-color: var(--accent-blue); +} +.image-item img { + width: 100%; + height: 220px; + object-fit: cover; + display: block; + transition: transform 0.5s ease; +} +.image-item:hover img { + transform: scale(1.05); +} +.image-caption { + padding: 12px; + text-align: center; + color: var(--text-dark); + font-size: 0.9rem; + font-weight: 500; + line-height: 1.4; +} + +/* 图片查看器样式 */ +.lightbox { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(30, 41, 59, 0.95); + z-index: 1000; + justify-content: center; + align-items: center; + flex-direction: column; + backdrop-filter: blur(10px); +} +.lightbox img { + max-width: 85%; + max-height: 75%; + object-fit: contain; + border-radius: 12px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); +} +.lightbox-close { + position: absolute; + top: 30px; + right: 40px; + color: var(--white); + font-size: 36px; + cursor: pointer; + z-index: 1001; + background: rgba(255, 255, 255, 0.1); + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} +.lightbox-close:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} +.lightbox-nav { + position: absolute; + top: 50%; + width: 100%; + display: flex; + justify-content: space-between; + padding: 0 20px; + transform: translateY(-50%); + z-index: 1001; +} +.lightbox-nav button { + background: rgba(255,255,255,0.2); + border: none; + color: var(--white); + font-size: 30px; + padding: 12px 24px; + cursor: pointer; + border-radius: var(--border-radius); + backdrop-filter: blur(10px); + transition: all 0.3s ease; + border: 1px solid rgba(255, 255, 255, 0.1); +} +.lightbox-nav button:hover { + background: rgba(255,255,255,0.3); + transform: scale(1.1); + border-color: rgba(255, 255, 255, 0.3); +} +.lightbox-info { + color: var(--white); + text-align: center; + margin-top: 25px; + font-size: 1.2rem; + background: rgba(255, 255, 255, 0.1); + padding: 15px 25px; + border-radius: var(--border-radius); + backdrop-filter: blur(10px); +} + +@media (max-width: 768px) { + .images-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); } + .album-title { font-size: 1.8rem; } + .lightbox img { max-width: 95%; max-height: 60%; } + .lightbox-nav button { font-size: 24px; padding: 10px 18px; } + .container { padding: 20px 15px; } + .album-info { font-size: 1.1rem; padding: 15px; } +} \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..0d5d590 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,228 @@ +/* 现代化蓝白配色体系 */ +:root { + --primary-blue: #1e40af; + --secondary-blue: #3b82f6; + --light-blue: #dbeafe; + --accent-blue: #60a5fa; + --white: #ffffff; + --light-gray: #f8fafc; + --medium-gray: #e2e8f0; + --dark-gray: #475569; + --text-dark: #1e293b; + --text-light: #64748b; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1); + --border-radius: 12px; + --border-radius-lg: 20px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } +body { + font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; + background: linear-gradient(135deg, var(--light-blue) 0%, var(--white) 100%); + min-height: 100vh; + color: var(--text-dark); + line-height: 1.6; +} +.container { + max-width: 1400px; + margin: 0 auto; + padding: 30px 20px; +} + +.header { + text-align: center; + margin-bottom: 50px; + color: var(--text-dark); +} +.header h1 { + font-size: 3rem; + margin-bottom: 15px; + font-weight: 700; + background: linear-gradient(135deg, var(--primary-blue) 0%, var(--secondary-blue) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.header p { + font-size: 1.3rem; + color: var(--text-light); + max-width: 600px; + margin: 0 auto; +} + +.search-container { + text-align: center; + margin-bottom: 50px; +} +.search-box { + position: relative; + display: inline-block; + max-width: 500px; + width: 100%; +} +.search-box input { + width: 100%; + padding: 18px 60px 18px 25px; + font-size: 16px; + border: 2px solid var(--medium-gray); + border-radius: 50px; + background: var(--white); + box-shadow: var(--shadow-md); + transition: all 0.3s ease; + color: var(--text-dark); +} +.search-box input:focus { + outline: none; + border-color: var(--secondary-blue); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + transform: translateY(-2px); +} +.search-box button { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + background: var(--secondary-blue); + border: none; + color: var(--white); + font-size: 18px; + cursor: pointer; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} +.search-box button:hover { + background: var(--primary-blue); + transform: translateY(-50%) scale(1.05); +} + +.stats { + text-align: center; + color: var(--text-dark); + margin-bottom: 40px; + font-size: 1.1rem; + background: var(--white); + padding: 20px; + border-radius: var(--border-radius); + box-shadow: var(--shadow-sm); + border: 1px solid var(--medium-gray); +} + +.albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 30px; + margin-bottom: 40px; +} +.album-card { + background: var(--white); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-lg); + overflow: hidden; + transition: all 0.3s ease; + position: relative; + border: 1px solid var(--medium-gray); +} +.album-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-xl); + border-color: var(--accent-blue); +} +.album-cover { + height: 220px; + overflow: hidden; + position: relative; + background: var(--light-gray); +} +.album-cover img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s ease; +} +.album-card:hover .album-cover img { + transform: scale(1.05); +} +.album-info { + padding: 25px; +} +.album-info h2 { + font-size: 1.4rem; + margin-bottom: 12px; + color: var(--text-dark); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + font-weight: 600; +} +.album-meta { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + color: var(--text-light); + font-size: 0.9rem; +} +.album-meta span { + display: flex; + align-items: center; + gap: 6px; +} +.view-button { + display: block; + text-align: center; + padding: 14px; + background: linear-gradient(135deg, var(--secondary-blue) 0%, var(--primary-blue) 100%); + color: var(--white); + text-decoration: none; + border-radius: 10px; + font-weight: 600; + transition: all 0.3s ease; + border: none; + cursor: pointer; + font-size: 1rem; +} +.view-button:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3); + background: linear-gradient(135deg, var(--primary-blue) 0%, var(--secondary-blue) 100%); +} + +.no-results { + text-align: center; + color: var(--text-light); + font-size: 1.2rem; + padding: 60px 40px; + background: var(--white); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-md); + border: 1px solid var(--medium-gray); +} + +.loading { + text-align: center; + color: var(--text-light); + font-size: 1.2rem; + padding: 60px 40px; + background: var(--white); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-md); + border: 1px solid var(--medium-gray); +} + +@media (max-width: 768px) { + .albums-grid { grid-template-columns: 1fr; } + .search-box { max-width: 100%; } + .header h1 { font-size: 2.2rem; } + .container { padding: 20px 15px; } + .album-info { padding: 20px; } +} \ No newline at end of file diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..111ab73 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,37 @@ + + + + + + 流云图库 + + + + +
+
+

流云图库

+

发现精彩瞬间,浏览精美图片

+
+ +
+ +
+ +
+ 正在加载相册数据... +
+ +
+
+
加载中... +
+
+
+ + + + \ No newline at end of file diff --git a/assets/js/album.js b/assets/js/album.js new file mode 100644 index 0000000..28c42b4 --- /dev/null +++ b/assets/js/album.js @@ -0,0 +1,182 @@ +// 全局变量 +let currentAlbum = null; +let currentImageIndex = 0; +let images = []; + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + const urlParams = new URLSearchParams(window.location.search); + const albumId = urlParams.get('id'); + + if (albumId) { + loadAlbum(albumId); + } else { + showError('缺少相册ID参数'); + } +}); + +// 加载相册详情 +async function loadAlbum(albumId) { + try { + const response = await fetch(`/api/album/${albumId}`); + if (!response.ok) { + if (response.status === 404) { + throw new Error('相册不存在'); + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const albumData = await response.json(); + currentAlbum = albumData.album; + images = albumData.images; + + updateAlbumInfo(); + displayImages(); + } catch (error) { + console.error('加载相册失败:', error); + showError('加载相册失败: ' + error.message); + } +} + +// 更新相册信息 +function updateAlbumInfo() { + document.getElementById('album-title').textContent = currentAlbum.title; + document.getElementById('album-info').innerHTML = + ` 共 ${images.length} 张图片`; + document.title = `${currentAlbum.title} - 图片展示服务`; +} + +// 显示图片列表 +function displayImages() { + const container = document.getElementById('images-container'); + + if (images.length === 0) { + container.innerHTML = '

此相册暂无图片
'; + return; + } + + container.innerHTML = images.map((image, index) => { + // 优先使用缩略图API,如果没有则使用原始URL + let imageUrl = '/assets/placeholder.svg'; + if (image.id) { + imageUrl = `/api/thumbnail/${image.id}`; + } else if (image.original_url) { + imageUrl = image.original_url; + } + + const title = image.title || `图片 ${index + 1}`; + + return ` +
+ ${title} +
+ ${image.width ? `${image.width}×${image.height}` : ''} + ${image.file_size ? `${(image.file_size / 1024 ).toFixed(2)} KB` : ''} +
+
+ `; + }).join(''); +} + +// 打开图片查看器 +function openLightbox(index) { + currentImageIndex = index; + const lightbox = document.getElementById('lightbox'); + const lightboxImg = document.getElementById('lightbox-img'); + const lightboxInfo = document.getElementById('lightbox-info'); + + const image = images[index]; + // 在lightbox中优先使用原始图片以获得更好的显示效果 + let imageUrl = '/assets/placeholder.svg'; + if (image.id) { + imageUrl = `/api/original/${image.id}`; + } else if (image.original_url) { + imageUrl = image.original_url; + } + + lightboxImg.src = imageUrl; + + const title = image.title || `图片 ${index + 1}`; + let infoText = `${title} (${index + 1}/${images.length})`; + if (image.width && image.height) { + infoText += ` | ${image.width}×${image.height}`; + } + if (image.file_size) { + infoText += ` | ${(image.file_size / 1024 ).toFixed(2)} KB`; + } + + lightboxInfo.textContent = infoText; + lightbox.style.display = 'flex'; + document.body.style.overflow = 'hidden'; +} + +// 关闭图片查看器 +function closeLightbox() { + const lightbox = document.getElementById('lightbox'); + lightbox.style.display = 'none'; + document.body.style.overflow = 'auto'; +} + +// 下一张图片 +function nextImage() { + currentImageIndex = (currentImageIndex + 1) % images.length; + updateLightboxImage(); +} + +// 上一张图片 +function prevImage() { + currentImageIndex = (currentImageIndex - 1 + images.length) % images.length; + updateLightboxImage(); +} + +// 更新图片查看器显示 +function updateLightboxImage() { + const lightboxImg = document.getElementById('lightbox-img'); + const lightboxInfo = document.getElementById('lightbox-info'); + + const image = images[currentImageIndex]; + // 在lightbox中优先使用原始图片以获得更好的显示效果 + let imageUrl = '/assets/placeholder.svg'; + if (image.id) { + imageUrl = `/api/original/${image.id}`; + } else if (image.original_url) { + imageUrl = image.original_url; + } + + lightboxImg.src = imageUrl; + + const title = image.title || `图片 ${currentImageIndex + 1}`; + let infoText = `${title} (${currentImageIndex + 1}/${images.length})`; + if (image.width && image.height) { + infoText += ` | ${image.width}×${image.height}`; + } + if (image.file_size) { + infoText += ` | ${(image.file_size / 1024 ).toFixed(2)} KB`; + } + + lightboxInfo.textContent = infoText; +} + +// 键盘导航 +document.addEventListener('keydown', function(event) { + const lightbox = document.getElementById('lightbox'); + if (lightbox.style.display === 'flex') { + if (event.key === 'Escape') closeLightbox(); + if (event.key === 'ArrowRight') nextImage(); + if (event.key === 'ArrowLeft') prevImage(); + } +}); + +// 点击背景关闭 +document.getElementById('lightbox').addEventListener('click', function(event) { + if (event.target === this) closeLightbox(); +}); + +// 显示错误信息 +function showError(message) { + const container = document.getElementById('images-container'); + container.innerHTML = `

${message}
`; + + document.getElementById('album-title').textContent = '加载失败'; + document.getElementById('album-info').innerHTML = ''; +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..3a20022 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,115 @@ +// 全局变量 +let allAlbums = []; + +// API基础URL +const API_BASE = '/api'; + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + loadAlbums(); +}); + +// 加载所有相册 +async function loadAlbums() { + try { + const response = await fetch(`${API_BASE}/albums`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + allAlbums = await response.json(); + displayAlbums(allAlbums); + updateStats(allAlbums); + } catch (error) { + console.error('加载相册失败:', error); + showError('加载相册数据失败,请刷新页面重试'); + } +} + +// 搜索相册 +async function searchAlbums() { + const searchTerm = document.getElementById('search-input').value.toLowerCase().trim(); + + if (searchTerm === '') { + // 显示所有相册 + displayAlbums(allAlbums); + updateStats(allAlbums); + return; + } + + try { + const response = await fetch(`${API_BASE}/search?q=${encodeURIComponent(searchTerm)}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const filteredAlbums = await response.json(); + displayAlbums(filteredAlbums); + + if (filteredAlbums.length === 0) { + document.getElementById('stats').innerHTML = `没有找到包含 "${searchTerm}" 的相册`; + } else { + document.getElementById('stats').innerHTML = `找到 ${filteredAlbums.length} 个包含 "${searchTerm}" 的相册`; + } + } catch (error) { + console.error('搜索失败:', error); + showError('搜索失败,请重试'); + } +} + +// 显示相册列表 +function displayAlbums(albums) { + const container = document.getElementById('albums-container'); + + if (albums.length === 0) { + container.innerHTML = '

没有找到匹配的相册
'; + return; + } + + container.innerHTML = albums.map(album => { + // 处理封面图片URL + let coverImageUrl = '/assets/placeholder.svg'; + if (album.cover_image_id) { + coverImageUrl = `/api/thumbnail/${album.cover_image_id}`; + } + + // 格式化日期 + const createdDate = new Date(album.created_at).toLocaleDateString('zh-CN'); + + return ` +
+
+ ${album.title} +
+
+

${album.title}

+
+ ${album.image_count} 张图片 + ${createdDate} +
+ + 查看相册 + +
+
+ `; + }).join(''); +} + +// 更新统计信息 +function updateStats(albums) { + const totalImages = albums.reduce((sum, album) => sum + album.image_count, 0); + document.getElementById('stats').innerHTML = `共发现 ${albums.length} 个相册,包含 ${totalImages} 张图片`; +} + +// 显示错误信息 +function showError(message) { + const container = document.getElementById('albums-container'); + container.innerHTML = `

${message}
`; + document.getElementById('stats').innerHTML = ''; +} + +// 键盘事件处理 +function handleKeyPress(event) { + if (event.key === 'Enter') { + searchAlbums(); + } +} \ No newline at end of file diff --git a/assets/placeholder.svg b/assets/placeholder.svg new file mode 100644 index 0000000..7542b3a --- /dev/null +++ b/assets/placeholder.svg @@ -0,0 +1,7 @@ + + + + + + 图片加载中 + \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..ba3dd5c --- /dev/null +++ b/config.toml @@ -0,0 +1,10 @@ +[database] +url = "sqlite:data/imager.db" + +[server] +host = "127.0.0.1" +port = 8080 + +[storage] +images_dir = "albums" +thumbnails_dir = "thumbnails" \ No newline at end of file diff --git a/migrations/001_initial.sql b/migrations/001_initial.sql new file mode 100644 index 0000000..c12a543 --- /dev/null +++ b/migrations/001_initial.sql @@ -0,0 +1,33 @@ +-- 创建相册表 +CREATE TABLE IF NOT EXISTS albums ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + document TEXT NOT NULL, + description TEXT, + cover_image_id TEXT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (cover_image_id) REFERENCES images(id) +); + +-- 创建图片表 +CREATE TABLE IF NOT EXISTS images ( + id TEXT PRIMARY KEY, + album_id TEXT NOT NULL, + filename TEXT NOT NULL, + original_url TEXT, + title TEXT, + description TEXT, + file_size INTEGER NOT NULL, + width INTEGER NOT NULL, + height INTEGER NOT NULL, + mime_type TEXT NOT NULL, + created_at DATETIME NOT NULL, + FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE +); + +-- 创建索引以提高查询性能 +CREATE INDEX IF NOT EXISTS idx_albums_created_at ON albums(created_at); +CREATE INDEX IF NOT EXISTS idx_images_album_id ON images(album_id); +CREATE INDEX IF NOT EXISTS idx_images_created_at ON images(created_at); +CREATE INDEX IF NOT EXISTS idx_albums_title ON albums(title); \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..4ddcbfe --- /dev/null +++ b/src/config.rs @@ -0,0 +1,41 @@ +use serde::Deserialize; +use std::path::PathBuf; + +#[derive(Debug, Deserialize, Clone)] +pub struct Config { + pub database: DatabaseConfig, + pub server: ServerConfig, + pub storage: StorageConfig, +} + + +#[derive(Debug, Deserialize, Clone)] +pub struct DatabaseConfig { + pub url: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct ServerConfig { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StorageConfig { + pub images_dir: PathBuf, + pub thumbnails_dir: PathBuf, +} + +impl Config { + pub fn load() -> Result { + let config = config::Config::builder() + .add_source(config::File::with_name("config")) + .build()?; + + config.try_deserialize() + } + + pub fn server_address(&self) -> String { + format!("{}:{}", self.server.host, self.server.port) + } +} \ No newline at end of file diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..3632376 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,407 @@ +use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Row}; +use crate::config::Config; +use crate::models::*; +use uuid::Uuid; + +pub struct Database { + pool: SqlitePool, +} + +impl Database { + pub async fn new(config: &Config) -> Result { + // 从URL中提取文件路径 + let url = &config.database.url; + let file_path = if url.starts_with("sqlite:") { + &url[7..] // 移除 "sqlite:" 前缀 + } else { + url + }; + + // 确保数据目录存在 + if let Some(parent) = std::path::Path::new(file_path).parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } + } + + // 如果数据库文件不存在,创建一个空文件 + if !std::path::Path::new(file_path).exists() { + std::fs::File::create(file_path)?; + println!("创建新的数据库文件: {}", file_path); + } + + let pool = SqlitePoolOptions::new() + .max_connections(5) + .connect(url) + .await?; + + // 创建表 + sqlx::migrate!("./migrations").run(&pool).await?; + + Ok(Database { pool }) + } + + + // 相册操作 + pub async fn get_all_albums(&self) -> Result, sqlx::Error> { + let albums = sqlx::query( + r#" + SELECT + a.id, + a.title, + a.document, + a.description, + a.cover_image_id, + COUNT(img.id) as image_count, + a.created_at + FROM albums a + LEFT JOIN images img ON img.album_id = a.id + LEFT JOIN images i ON i.id = a.cover_image_id + ORDER BY a.document DESC + "# + ) + .fetch_all(&self.pool) + .await?; + + let mut result = Vec::new(); + for row in albums { + let id_str = row.get::("id"); + let id = match Uuid::parse_str(&id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e); + continue; // 跳过无效的记录 + } + }; + + result.push(AlbumInfo { + id, + title: row.get("title"), + document: row.get("document"), + description: row.get("description"), + cover_image_id: row.get("cover_image_id"), + image_count: row.get("image_count"), + created_at: row.get::("created_at"), + }); + } + + Ok(result) + } + + pub async fn search_albums(&self, query: &str) -> Result, sqlx::Error> { + let search_pattern = format!("%{}%", query); + + let albums = sqlx::query( + r#" + SELECT + a.id, + a.title, + a.document, + a.description, + a.cover_image_id, + COUNT(img.id) as image_count, + a.created_at + FROM albums a + LEFT JOIN images img ON img.album_id = a.id + LEFT JOIN images i ON i.id = a.cover_image_id + WHERE a.title LIKE ? + GROUP BY a.id, a.created_at + ORDER BY a.created_at DESC + "# + ) + .bind(&search_pattern) + .fetch_all(&self.pool) + .await?; + + let mut result = Vec::new(); + for row in albums { + let id_str = row.get::("id"); + let id = match Uuid::parse_str(&id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的UUID格式: {} - 错误: {}", id_str, e); + continue; // 跳过无效的记录 + } + }; + + result.push(AlbumInfo { + id, + title: row.get("title"), + document: row.get("document"), + description: row.get("description"), + cover_image_id: row.get("cover_image_id"), + image_count: row.get("image_count"), + created_at: row.get::("created_at"), + }); + } + + Ok(result) + } + + pub async fn get_album_with_images(&self, album_id: Uuid) -> Result, sqlx::Error> { + // 获取相册基本信息 + let album_row = sqlx::query( + "SELECT * FROM albums WHERE id = ?" + ) + .bind(album_id.to_string()) + .fetch_optional(&self.pool) + .await?; + + if let Some(row) = album_row { + let id_str = row.get::("id"); + let id = match Uuid::parse_str(&id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的相册UUID格式: {} - 错误: {}", id_str, e); + return Ok(None); + } + }; + + let cover_image_id = match row.get::, _>("cover_image_id") { + Some(s) => match Uuid::parse_str(&s) { + Ok(uuid) => Some(uuid), + Err(e) => { + eprintln!("无效的封面图片UUID格式: {} - 错误: {}", s, e); + None + } + }, + None => None + }; + + let album = Album { + id, + title: row.get("title"), + description: row.get("description"), + cover_image_id, + created_at: row.get::("created_at"), + updated_at: row.get::("updated_at"), + }; + + // 获取相册中的图片 + let image_rows = sqlx::query( + "SELECT * FROM images WHERE album_id = ? ORDER BY created_at ASC" + ) + .bind(album_id.to_string()) + .fetch_all(&self.pool) + .await?; + + let mut images = Vec::new(); + for row in image_rows { + let image_id_str = row.get::("id"); + let image_id = match Uuid::parse_str(&image_id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的图片UUID格式: {} - 错误: {}", image_id_str, e); + continue; + } + }; + + let album_id_str = row.get::("album_id"); + let album_id = match Uuid::parse_str(&album_id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的相册UUID格式: {} - 错误: {}", album_id_str, e); + continue; + } + }; + + images.push(Image { + id: image_id, + album_id, + filename: row.get("filename"), + original_url: row.get("original_url"), + title: row.get("title"), + description: row.get("description"), + file_size: row.get("file_size"), + width: row.get("width"), + height: row.get("height"), + mime_type: row.get("mime_type"), + created_at: row.get::("created_at"), + }); + } + + let image_count = images.len() as i64; + + Ok(Some(AlbumWithImages { + album, + images, + image_count, + })) + } else { + Ok(None) + } + } + + pub async fn create_album(&self, album: CreateAlbum) -> Result { + let id = Uuid::new_v4(); + let now = chrono::Utc::now().to_rfc3339(); + + sqlx::query( + "INSERT INTO albums (id, title, document, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)" + ) + .bind(id.to_string()) + .bind(album.title) + .bind(album.document) + .bind(album.description) + .bind(&now) + .bind(&now) + .execute(&self.pool) + .await?; + + Ok(id) + } + + // 图片操作 + pub async fn add_image(&self, image: CreateImage) -> Result { + let id = Uuid::new_v4(); + let now = chrono::Utc::now().to_rfc3339(); + + sqlx::query( + "INSERT INTO images (id, album_id, filename, original_url, title, description, file_size, width, height, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ) + .bind(id.to_string()) + .bind(image.album_id.to_string()) + .bind(image.filename) + .bind(image.original_url) + .bind(image.title) + .bind(image.description) + .bind(image.file_size) + .bind(image.width) + .bind(image.height) + .bind(image.mime_type) + .bind(&now) + .execute(&self.pool) + .await?; + + // 如果这是相册的第一张图片,设置为封面 + let image_count = sqlx::query_scalar::<_, i64>( + "SELECT COUNT(*) FROM images WHERE album_id = ?" + ) + .bind(image.album_id.to_string()) + .fetch_one(&self.pool) + .await?; + + if image_count == 1 { + sqlx::query( + "UPDATE albums SET cover_image_id = ? WHERE id = ?" + ) + .bind(id.to_string()) + .bind(image.album_id.to_string()) + .execute(&self.pool) + .await?; + } + + Ok(id) + } + + pub async fn add_images(&self, images: Vec) -> Result, sqlx::Error> { + if images.is_empty() { + return Ok(Vec::new()); + } + + let now = chrono::Utc::now().to_rfc3339(); + let mut ids = Vec::new(); + + // 保存第一个相册ID用于封面设置 + let first_album_id = images.first().map(|img| img.album_id); + + // 使用事务确保批量插入的原子性 + let mut tx = self.pool.begin().await?; + + for image in &images { + let id = Uuid::new_v4(); + ids.push(id); + + sqlx::query( + "INSERT INTO images (id, album_id, filename, original_url, title, description, file_size, width, height, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ) + .bind(id.to_string()) + .bind(image.album_id.to_string()) + .bind(&image.filename) + .bind(&image.original_url) + .bind(&image.title) + .bind(&image.description) + .bind(image.file_size) + .bind(image.width) + .bind(image.height) + .bind(&image.mime_type) + .bind(&now) + .execute(&mut *tx) + .await?; + } + + // 提交事务 + tx.commit().await?; + + // 批量设置封面图片(只对每个相册的第一批图片设置) + if let Some(album_id) = first_album_id { + // 检查相册是否已有图片 + let existing_count = sqlx::query_scalar::<_, i64>( + "SELECT COUNT(*) FROM images WHERE album_id = ?" + ) + .bind(album_id.to_string()) + .fetch_one(&self.pool) + .await?; + + // 如果这是相册的第一批图片,设置第一张为封面 + if existing_count == images.len() as i64 { + if let Some(first_id) = ids.first() { + sqlx::query( + "UPDATE albums SET cover_image_id = ? WHERE id = ?" + ) + .bind(first_id.to_string()) + .bind(album_id.to_string()) + .execute(&self.pool) + .await?; + } + } + } + + Ok(ids) + } + + pub async fn get_image(&self, image_id: Uuid) -> Result, sqlx::Error> { + let row = sqlx::query( + "SELECT * FROM images WHERE id = ?" + ) + .bind(image_id.to_string()) + .fetch_optional(&self.pool) + .await?; + + if let Some(row) = row { + let image_id_str = row.get::("id"); + let image_id = match Uuid::parse_str(&image_id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的图片UUID格式: {} - 错误: {}", image_id_str, e); + return Ok(None); + } + }; + + let album_id_str = row.get::("album_id"); + let album_id = match Uuid::parse_str(&album_id_str) { + Ok(uuid) => uuid, + Err(e) => { + eprintln!("无效的相册UUID格式: {} - 错误: {}", album_id_str, e); + return Ok(None); + } + }; + + Ok(Some(Image { + id: image_id, + album_id, + filename: row.get("filename"), + original_url: row.get("original_url"), + title: row.get("title"), + description: row.get("description"), + file_size: row.get("file_size"), + width: row.get("width"), + height: row.get("height"), + mime_type: row.get("mime_type"), + created_at: row.get::("created_at"), + })) + } else { + Ok(None) + } + } +} \ No newline at end of file diff --git a/src/image_processor.rs b/src/image_processor.rs new file mode 100644 index 0000000..2f32b7d --- /dev/null +++ b/src/image_processor.rs @@ -0,0 +1,113 @@ +use std::path::Path; +use std::fs; +use image::{GenericImageView, ImageFormat}; +use uuid::Uuid; +use crate::config::StorageConfig; + +pub struct ImageProcessor { + pub config: StorageConfig, +} + +impl ImageProcessor { + pub fn new(config: StorageConfig) -> Self { + // 确保目录存在 + fs::create_dir_all(&config.images_dir).unwrap_or(()); + fs::create_dir_all(&config.thumbnails_dir).unwrap_or(()); + + Self { config } + } + + pub async fn save_image(&self, image_data: &[u8], filename: &str) -> Result> { + let image_id = Uuid::new_v4(); + let file_extension = Path::new(filename) + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("jpg"); + + let new_filename = format!("{}.{}", image_id, file_extension); + let image_path = self.config.images_dir.join(&new_filename); + + // 保存原始图片 + fs::write(&image_path, image_data)?; + + // 获取图片信息 + let img = image::load_from_memory(image_data)?; + let (width, height) = img.dimensions(); + let file_size = image_data.len() as i64; + + // 生成缩略图 + let thumbnail_path = self.config.thumbnails_dir.join(format!("thumb_{}", new_filename)); + self.generate_thumbnail(&img, &thumbnail_path, 300, 200)?; + + Ok(ImageInfo { + id: image_id, + filename: new_filename, + width: width as i32, + height: height as i32, + file_size, + mime_type: self.detect_mime_type(file_extension), + }) + } + + pub fn generate_thumbnail( + &self, + img: &image::DynamicImage, + output_path: &Path, + max_width: u32, + max_height: u32, + ) -> Result<(), Box> { + let (width, height) = img.dimensions(); + + // 计算缩略图尺寸 + let ratio = width as f32 / height as f32; + let (new_width, new_height) = if width > height { + (max_width, (max_width as f32 / ratio) as u32) + } else { + ((max_height as f32 * ratio) as u32, max_height) + }; + + let thumbnail = img.thumbnail(new_width, new_height); + thumbnail.save_with_format(output_path, ImageFormat::Jpeg)?; + + Ok(()) + } + + pub fn detect_mime_type(&self, extension: &str) -> String { + match extension.to_lowercase().as_str() { + "jpg" | "jpeg" => "image/jpeg".to_string(), + "png" => "image/png".to_string(), + "gif" => "image/gif".to_string(), + "webp" => "image/webp".to_string(), + _ => "application/octet-stream".to_string(), + } + } + + pub fn get_image_url(&self, filename: &str) -> String { + format!("/images/{}", filename) + } + + pub fn get_thumbnail_url(&self, filename: &str) -> String { + format!("/thumbnails/thumb_{}", filename) + } + + pub async fn download_and_save_image( + &self, + url: &str, + filename: &str, + ) -> Result> { + let response = reqwest::get(url).await?; + let image_data = response.bytes().await?; + + self.save_image(&image_data, filename).await + } +} + +#[derive(Debug)] +pub struct ImageInfo { + pub id: Uuid, + pub filename: String, + pub width: i32, + pub height: i32, + pub file_size: i64, + pub mime_type: String, +} \ No newline at end of file diff --git a/src/import.rs b/src/import.rs new file mode 100644 index 0000000..d800830 --- /dev/null +++ b/src/import.rs @@ -0,0 +1,250 @@ +use std::fs; +use std::path::Path; +use image::GenericImageView; +use serde::Deserialize; +use crate::config::Config; +use crate::database::Database; +use crate::models::*; + +#[derive(Debug, Deserialize)] +struct AlbumIndex { + title: String, + images: Vec, +} + +pub async fn import_existing_data(config: &Config, db: &Database) -> Result<(), Box> { + println!("开始扫描图片目录并导入新相册..."); + + let albums_dir = Path::new(&config.storage.images_dir); + + if !albums_dir.exists() { + println!("albums目录不存在,跳过导入"); + return Ok(()); + } + + // 获取数据库中现有的相册列表 + let existing_albums = db.get_all_albums().await?; + let existing_titles: Vec = existing_albums.iter() + .map(|album| album.document.to_lowercase()) + .collect(); + + println!("数据库中现有相册数量: {}", existing_albums.len()); + + let entries = fs::read_dir(albums_dir)?; + let mut imported_count = 0; + + for entry in entries { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + if let Some(folder_name) = path.file_name().and_then(|s| s.to_str()) { + // 检查是否已存在同名相册 + if existing_titles.contains(&folder_name.to_lowercase()) { + println!("跳过已存在的相册: {}", folder_name); + continue; + } + + println!("发现新相册: {}", folder_name); + + // 读取index.json文件 + let index_file_path = path.join("index.json"); + let album_title = if index_file_path.exists() { + match fs::read_to_string(&index_file_path) { + Ok(content) => { + match serde_json::from_str::(&content) { + Ok(index) => { + println!(" 从index.json读取相册标题: {}", index.title); + index.title + } + Err(e) => { + println!(" 解析index.json失败: {}, 使用文件夹名作为标题", e); + folder_name.to_string() + } + } + } + Err(e) => { + println!(" 读取index.json失败: {}, 使用文件夹名作为标题", e); + folder_name.to_string() + } + } + } else { + println!(" index.json不存在,使用文件夹名作为标题"); + folder_name.to_string() + }; + + // 创建新相册 + let new_album = CreateAlbum { + title: album_title.clone(), + document: folder_name.to_string(), + description: Some(format!("从文件夹 {} 自动导入的相册", folder_name)), + }; + + let album_id = db.create_album(new_album).await?; + println!(" 创建相册: {} (ID: {})", album_title, album_id); + + // 读取图片URL列表(如果有index.json) + let image_urls = if index_file_path.exists() { + match fs::read_to_string(&index_file_path) { + Ok(content) => { + match serde_json::from_str::(&content) { + Ok(index) => { + println!(" 从index.json读取到 {} 个图片URL", index.images.len()); + Some(index.images) + } + Err(_) => None + } + } + Err(_) => None + } + } else { + None + }; + + // 扫描文件夹中的图片文件 + let image_entries = fs::read_dir(&path)?; + let mut image_files = Vec::new(); + + for image_entry in image_entries { + let image_entry = image_entry?; + let image_path = image_entry.path(); + + if image_path.is_file() { + if let Some(extension) = image_path.extension().and_then(|s| s.to_str()) { + let valid_extensions = ["jpg", "jpeg", "png", "gif", "webp", "bmp"]; + + if valid_extensions.contains(&extension.to_lowercase().as_str()) { + let filename = image_path.file_name() + .and_then(|s| s.to_str()) + .unwrap_or("unknown.jpg") + .to_string(); + + image_files.push((filename, image_path)); + } + } + } + } + + // 按数字顺序排序文件名 + image_files.sort_by(|a, b| { + // 提取文件名中的数字部分进行排序 + let num_a = a.0.chars() + .take_while(|c| c.is_ascii_digit()) + .collect::() + .parse::() + .unwrap_or(0); + + let num_b = b.0.chars() + .take_while(|c| c.is_ascii_digit()) + .collect::() + .parse::() + .unwrap_or(0); + + num_a.cmp(&num_b) + }); + + let mut image_count = 0; + let mut images_to_insert = Vec::new(); + + // 收集所有图片信息 + for (filename, image_path) in image_files { + image_count += 1; + + // 获取对应的URL(如果有的话) + let original_url = if let Some(ref urls) = image_urls { + if urls.len() >= image_count { + Some(urls[image_count - 1].clone()) + } else { + None + } + } else { + None + }; + + // 生成图片标题 + let image_title = if let Some(ref urls) = image_urls { + if urls.len() >= image_count { + Some(format!("图片 {}", image_count)) + } else { + Some(format!("图片 {}", image_count)) + } + } else { + Some(format!("图片 {}", image_count)) + }; + + // 获取图片的实际信息 + let (file_size, width, height) = match get_image_info(&image_path) { + Ok(info) => info, + Err(e) => { + eprintln!("获取图片信息失败: {} - {}", image_path.display(), e); + (0, 0, 0) + } + }; + + let image = CreateImage { + album_id, + filename: format!("{}/{}", folder_name, filename).clone(), + original_url: original_url.clone(), + title: image_title, + description: Some(format!("从文件夹 {} 导入的图片", folder_name)), + file_size, + width, + height, + mime_type: match image_path.extension().and_then(|s| s.to_str()).unwrap_or("jpg").to_lowercase().as_str() { + "jpg" | "jpeg" => "image/jpeg".to_string(), + "png" => "image/png".to_string(), + "gif" => "image/gif".to_string(), + "webp" => "image/webp".to_string(), + "bmp" => "image/bmp".to_string(), + _ => "image/jpeg".to_string(), + }, + }; + + images_to_insert.push(image.clone()); + } + + // 批量插入图片 + if !images_to_insert.is_empty() { + println!(" 批量插入 {} 张图片...", images_to_insert.len()); + match db.add_images(images_to_insert.clone()).await { + Ok(ids) => { + println!(" 批量插入成功,共插入 {} 张图片", ids.len()); + } + Err(e) => { + eprintln!(" 批量插入失败: {}", e); + // 如果批量插入失败,尝试逐条插入 + println!(" 尝试逐条插入图片..."); + let mut success_count = 0; + for image in images_to_insert { + match db.add_image(image).await { + Ok(_) => success_count += 1, + Err(e) => eprintln!(" 单条插入失败: {}", e), + } + } + println!(" 逐条插入完成,成功 {} 张", success_count); + } + } + } + + println!(" 相册 {} 导入完成,共导入 {} 张图片", folder_name, image_count); + imported_count += 1; + } + } + } + + println!("导入完成!共导入 {} 个新相册", imported_count); + Ok(()) +} + +/// 获取图片的实际信息(文件大小、宽度、高度) +fn get_image_info(image_path: &Path) -> Result<(i64, i32, i32), Box> { + // 获取文件大小 + let metadata = fs::metadata(image_path)?; + let file_size = metadata.len() as i64; + + // 使用image库获取图片尺寸 + let img = image::open(image_path)?; + let (width, height) = img.dimensions(); + + Ok((file_size, width as i32, height as i32)) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c1785b0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,104 @@ +mod config; +mod models; +mod database; +mod image_processor; +mod routes; +mod import; + +use actix_files as afs; +use actix_web::{web, App, HttpServer, HttpResponse}; +use std::sync::Arc; +use std::fs; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // 加载配置 + let config = match config::Config::load() { + Ok(cfg) => cfg, + Err(e) => { + eprintln!("加载配置失败: {}", e); + std::process::exit(1); + } + }; + + println!("启动图片展示服务..."); + println!("服务地址: http://{}", config.server_address()); + + // 初始化数据库 + let db = match database::Database::new(&config).await { + Ok(db) => { + println!("数据库连接成功"); + Arc::new(db) + } + Err(e) => { + eprintln!("数据库连接失败: {}", e); + std::process::exit(1); + } + }; + + // 在后台线程中自动导入新相册(不阻塞HTTP服务启动) + let config_clone = config.clone(); + let db_clone = Arc::clone(&db); + tokio::spawn(async move { + println!("[后台任务] 开始扫描图片目录并导入新相册..."); + match import::import_existing_data(&config_clone, &db_clone).await { + Ok(_) => println!("[后台任务] 相册导入完成"), + Err(e) => eprintln!("[后台任务] 相册导入失败: {}", e), + } + }); + + // 初始化图片处理器 + let image_processor = Arc::new(image_processor::ImageProcessor::new(config.storage.clone())); + + println!("API接口:"); + println!(" - GET /api/albums - 获取所有相册"); + println!(" - GET /api/search?q=关键词 - 搜索相册"); + println!(" - GET /api/album/:id - 获取相册详情"); + println!(" - POST /api/albums - 创建新相册"); + println!(" - GET /api/thumbnail/:id - 获取缩略图"); + println!(" - GET /api/original/:id - 获取原始图片"); + println!("按 Ctrl+C 停止服务"); + + // 创建数据目录 + std::fs::create_dir_all(&config.storage.images_dir).unwrap_or(()); + std::fs::create_dir_all(&config.storage.thumbnails_dir).unwrap_or(()); + std::fs::create_dir_all("./data").unwrap_or(()); + + // 在闭包外提取需要的路径 + let server_address = config.server_address(); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(Arc::clone(&db))) + .app_data(web::Data::new(Arc::clone(&image_processor))) + // API路由 + .route("/api/albums", web::get().to(routes::get_albums)) + .route("/api/search", web::get().to(routes::search_albums)) + .route("/api/album/{id}", web::get().to(routes::get_album_detail)) + .route("/api/albums", web::post().to(routes::create_album)) + .route("/api/thumbnail/{id}", web::get().to(routes::get_thumbnail)) + .route("/api/original/{id}", web::get().to(routes::get_original_image)) + // 静态文件服务 + .service(afs::Files::new("/assets", "./assets").show_files_listing()) + // HTML页面路由 + .route("/", web::get().to(|| async { + match fs::read_to_string("./assets/index.html") { + Ok(content) => HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(content), + Err(_) => HttpResponse::NotFound().body("首页文件不存在") + } + })) + .route("/album", web::get().to(|| async { + match fs::read_to_string("./assets/album.html") { + Ok(content) => HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(content), + Err(_) => HttpResponse::NotFound().body("相册页面文件不存在") + } + })) + }) + .bind(server_address)? + .run() + .await +} \ No newline at end of file diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..de86b64 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Album { + pub id: Uuid, + pub title: String, + pub description: Option, + pub cover_image_id: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Image { + pub id: Uuid, + pub album_id: Uuid, + pub filename: String, + pub original_url: Option, + pub title: Option, + pub description: Option, + pub file_size: i64, + pub width: i32, + pub height: i32, + pub mime_type: String, + pub created_at: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AlbumWithImages { + pub album: Album, + pub images: Vec, + pub image_count: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AlbumInfo { + pub id: Uuid, + pub title: String, + pub document: String, + pub description: Option, + pub cover_image_id: Option, + pub image_count: i64, + pub created_at: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateAlbum { + pub title: String, + pub document: String, + pub description: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CreateImage { + pub album_id: Uuid, + pub filename: String, + pub original_url: Option, + pub title: Option, + pub description: Option, + pub file_size: i64, + pub width: i32, + pub height: i32, + pub mime_type: String, +} \ No newline at end of file diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..ba7f502 --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,266 @@ +use crate::database::Database; +use crate::image_processor::ImageProcessor; +use crate::models::*; +use actix_web::{HttpResponse, Responder, web}; +use std::fs; +use std::sync::Arc; +use uuid::Uuid; + +pub async fn get_albums(db: web::Data>) -> impl Responder { + match db.get_all_albums().await { + Ok(albums) => HttpResponse::Ok().json(albums), + Err(e) => { + eprintln!("获取相册列表失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "获取相册列表失败" + })) + } + } +} + +pub async fn get_original_image( + db: web::Data>, + image_processor: web::Data>, + path: web::Path, +) -> impl Responder { + let image_id = match Uuid::parse_str(&path.into_inner()) { + Ok(id) => id, + Err(_) => { + return HttpResponse::BadRequest().json(serde_json::json!({ + "error": "无效的图片ID" + })); + } + }; + + match db.get_image(image_id).await { + Ok(Some(image)) => { + let original_path = image_processor.config.images_dir.join(&image.filename); + + if original_path.exists() { + match fs::read(&original_path) { + Ok(data) => HttpResponse::Ok().content_type(image.mime_type).body(data), + Err(e) => { + eprintln!("读取原始图片失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "读取原始图片失败" + })) + } + } + } else { + HttpResponse::NotFound().json(serde_json::json!({ + "error": "原始图片文件不存在" + })) + } + } + Ok(None) => HttpResponse::NotFound().json(serde_json::json!({ + "error": "图片不存在" + })), + Err(e) => { + eprintln!("获取图片信息失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "获取图片信息失败" + })) + } + } +} + +pub async fn search_albums( + db: web::Data>, + query: web::Query>, +) -> impl Responder { + let q = query.get("q").map(|s| s.as_str()).unwrap_or(""); + + if q.is_empty() { + return match db.get_all_albums().await { + Ok(albums) => HttpResponse::Ok().json(albums), + Err(e) => { + eprintln!("获取相册列表失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "获取相册列表失败" + })) + } + }; + } + + match db.search_albums(q).await { + Ok(albums) => HttpResponse::Ok().json(albums), + Err(e) => { + eprintln!("搜索相册失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "搜索失败" + })) + } + } +} + +pub async fn get_album_detail( + db: web::Data>, + path: web::Path, +) -> impl Responder { + let album_id = match Uuid::parse_str(&path.into_inner()) { + Ok(id) => id, + Err(_) => { + return HttpResponse::BadRequest().json(serde_json::json!({ + "error": "无效的相册ID" + })); + } + }; + + match db.get_album_with_images(album_id).await { + Ok(Some(album)) => HttpResponse::Ok().json(album), + Ok(None) => HttpResponse::NotFound().json(serde_json::json!({ + "error": "相册不存在" + })), + Err(e) => { + eprintln!("获取相册详情失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "获取相册详情失败" + })) + } + } +} + +pub async fn create_album( + db: web::Data>, + album_data: web::Json, +) -> impl Responder { + match db.create_album(album_data.into_inner()).await { + Ok(album_id) => HttpResponse::Created().json(serde_json::json!({ + "id": album_id, + "message": "相册创建成功" + })), + Err(e) => { + eprintln!("创建相册失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "创建相册失败" + })) + } + } +} + +pub async fn get_thumbnail( + db: web::Data>, + image_processor: web::Data>, + path: web::Path, +) -> impl Responder { + let image_id = match Uuid::parse_str(&path.into_inner()) { + Ok(id) => id, + Err(_) => { + return HttpResponse::BadRequest().json(serde_json::json!({ + "error": "无效的图片ID" + })); + } + }; + + match db.get_image(image_id).await { + Ok(Some(image)) => { + // 优先返回缩略图,如果不存在则生成 + let thumbnail_path = image_processor + .config + .thumbnails_dir + .join(image.id.to_string()); + let original_path = image_processor.config.images_dir.join(&image.filename); + + if thumbnail_path.exists() { + // 返回缩略图 + match fs::read(&thumbnail_path) { + Ok(data) => { + let mime_type = image_processor.detect_mime_type("jpg"); // 缩略图总是JPEG格式 + HttpResponse::Ok().content_type(mime_type).body(data) + } + Err(e) => { + eprintln!("读取缩略图失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "读取缩略图失败" + })) + } + } + } else if original_path.exists() { + // 生成缩略图并返回 + match fs::read(&original_path) { + Ok(data) => { + match image::load_from_memory(&data) { + Ok(img) => { + // 在内存中生成缩略图,避免重复文件读写 + let thumbnail = img.thumbnail(800, 600); + + // 使用临时文件来生成缩略图数据 + let temp_dir = std::env::temp_dir(); + let temp_file = + temp_dir.join(format!("temp_thumb_{}", Uuid::new_v4())); + + if let Err(e) = + thumbnail.save_with_format(&temp_file, image::ImageFormat::Jpeg) + { + eprintln!("生成缩略图失败: {}", e); + // 如果生成缩略图失败,返回原始图片 + return HttpResponse::Ok() + .content_type(image.mime_type) + .body(data); + } + + // 读取缩略图数据 + match fs::read(&temp_file) { + Ok(thumbnail_data) => { + // 异步保存缩略图到文件(不阻塞响应) + let config_clone = image_processor.config.clone(); + let filename_clone = image.id.clone(); + let thumbnail_data_clone = thumbnail_data.clone(); + tokio::spawn(async move { + let thumbnail_path = config_clone + .thumbnails_dir + .join(filename_clone.to_string()); + if let Err(e) = + fs::write(&thumbnail_path, &thumbnail_data_clone) + { + eprintln!("保存缩略图文件失败: {}", e); + } + }); + + // 清理临时文件 + let _ = fs::remove_file(&temp_file); + + // 返回缩略图数据 + HttpResponse::Ok() + .content_type("image/jpeg") + .body(thumbnail_data) + } + Err(e) => { + eprintln!("读取缩略图数据失败: {}", e); + // 如果读取失败,返回原始图片 + HttpResponse::Ok().content_type(image.mime_type).body(data) + } + } + } + Err(e) => { + eprintln!("加载图片失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "加载图片失败" + })) + } + } + } + Err(e) => { + eprintln!("读取原始图片失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "读取图片失败" + })) + } + } + } else { + HttpResponse::NotFound().json(serde_json::json!({ + "error": "图片文件不存在" + })) + } + } + Ok(None) => HttpResponse::NotFound().json(serde_json::json!({ + "error": "图片不存在" + })), + Err(e) => { + eprintln!("获取图片信息失败: {}", e); + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "获取图片信息失败" + })) + } + } +}