simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 732ebe55cbccde47a14ebaece938234f9f41b955
parent a0d7b85a3154cf137f810b6f774e4324298b8fed
Author: Silas Brack <silasbrack@gmail.com>
Date:   Tue, 21 Apr 2026 22:28:16 +0200

feat: add Trailbase backend for authentication

- Replace sqlx/SQLite with Trailbase HTTP API
- Add trailbase.nix derivation and traildepot config
- Add login/register pages and handlers
- Configure record APIs with ACLs for news, categories
- Frontend uses TRAILBASE_URL env var (default localhost:4000)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
MCargo.lock | 1370++++++++++++++++++++++++++++++++++++++++++-------------------------------------
MCargo.toml | 4+++-
Mflake.nix | 6+++++-
Msrc/config.rs | 12++++++++----
Msrc/db/mod.rs | 32++++++++++++++++++++++++++++----
Dsrc/db/models.rs | 48------------------------------------------------
Dsrc/db/queries.rs | 106-------------------------------------------------------------------------------
Msrc/error.rs | 14+++++++++-----
Asrc/handlers/auth.rs | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/handlers/home.rs | 49++++++++++++++++++++++++-------------------------
Msrc/handlers/mod.rs | 2++
Msrc/handlers/search.rs | 3+--
Msrc/main.rs | 5+++--
Msrc/routes.rs | 4++++
Msrc/state.rs | 36++++++------------------------------
Msrc/templates.rs | 12++++++++++++
Asrc/trailbase.rs | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atemplates/login.html | 27+++++++++++++++++++++++++++
Atemplates/register.html | 27+++++++++++++++++++++++++++
Atrailbase.nix | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atraildepot/.gitignore | 7+++++++
Atraildepot/config.textproto | 35+++++++++++++++++++++++++++++++++++
Atraildepot/metadata.textproto | 2++
Atraildepot/migrations/main/U1_initial_schema.sql | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
24 files changed, 1355 insertions(+), 870 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -18,12 +18,6 @@ dependencies = [ ] [[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" @@ -33,6 +27,12 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] name = "askama" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -111,15 +111,6 @@ dependencies = [ ] [[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -210,12 +201,6 @@ 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 = "basic-toml" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -229,9 +214,6 @@ name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -dependencies = [ - "serde_core", -] [[package]] name = "block-buffer" @@ -249,12 +231,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[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" @@ -308,75 +284,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "crossbeam-utils", + "percent-encoding", + "time", + "version_check", ] [[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "cookie_store" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ + "core-foundation-sys", "libc", ] [[package]] -name = "crc" -version = "3.4.0" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "crc-catalog", + "core-foundation-sys", + "libc", ] [[package]] -name = "crc-catalog" -version = "2.4.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "crc32fast" -version = "1.5.0" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "cfg-if", + "libc", ] [[package]] -name = "crossbeam-queue" -version = "0.3.12" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "crossbeam-utils", + "cfg-if", ] [[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" @@ -387,14 +367,12 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "powerfmt", ] [[package]] @@ -404,9 +382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -421,18 +397,27 @@ dependencies = [ ] [[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[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" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "serde", + "cfg-if", ] [[package]] @@ -452,26 +437,10 @@ dependencies = [ ] [[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.1" +name = "fastrand" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -490,15 +459,10 @@ dependencies = [ ] [[package]] -name = "flume" -version = "0.11.1" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" @@ -507,6 +471,21 @@ 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" @@ -558,17 +537,6 @@ dependencies = [ ] [[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" @@ -636,69 +604,57 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", ] [[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - -[[package]] -name = "hashlink" -version = "0.10.0" +name = "h2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ - "hashbrown 0.15.5", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[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" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "hmac", + "foldhash", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] -name = "home" -version = "0.5.12" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" @@ -764,6 +720,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -772,6 +729,38 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -780,13 +769,23 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -896,6 +895,12 @@ dependencies = [ ] [[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" @@ -924,6 +929,24 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", ] [[package]] @@ -938,6 +961,8 @@ version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -947,9 +972,12 @@ 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" @@ -964,27 +992,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.7.4", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" +name = "linux-raw-sys" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -993,6 +1004,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1023,16 +1040,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1082,6 +1089,23 @@ dependencies = [ ] [[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" @@ -1101,62 +1125,69 @@ dependencies = [ ] [[package]] -name = "num-bigint-dig" -version = "0.8.6" +name = "num-conv" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] -name = "num-integer" -version = "0.1.46" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "num-traits", + "autocfg", ] [[package]] -name = "num-iter" -version = "0.1.45" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "num-traits" -version = "0.2.19" +name = "openssl" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ - "autocfg", - "libm", + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "parking" -version = "2.2.1" +name = "openssl-sys" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "parking_lot" @@ -1176,21 +1207,12 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] [[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" @@ -1203,39 +1225,12 @@ 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.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1245,12 +1240,19 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "zerocopy", + "proc-macro2", + "syn", ] [[package]] @@ -1263,43 +1265,35 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.45" +name = "psl-types" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] -name = "rand" -version = "0.8.6" +name = "publicsuffix" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "idna", + "psl-types", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "quote" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ - "ppv-lite86", - "rand_core", + "proc-macro2", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "redox_syscall" @@ -1311,15 +1305,6 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags", -] - -[[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1337,23 +1322,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "rsa" -version = "0.9.10" +name = "reqwest" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", + "base64", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -1391,6 +1412,52 @@ dependencies = [ ] [[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1412,12 +1479,50 @@ dependencies = [ ] [[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", + "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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1484,17 +1589,6 @@ dependencies = [ ] [[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" @@ -1531,16 +1625,6 @@ dependencies = [ ] [[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] name = "simd-adler32" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1557,14 +1641,16 @@ dependencies = [ "dotenvy", "futures", "mime_guess", + "reqwest", "rust-embed", "serde", - "sqlx", + "serde_json", "thiserror", "tokio", "tower-http", "tracing", "tracing-subscriber", + "urlencoding", ] [[package]] @@ -1578,9 +1664,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -1593,265 +1676,80 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "spki" -version = "0.7.3" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "sqlx" -version = "0.8.6" +name = "syn" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "sqlx-core" -version = "0.8.6" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "base64", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", ] [[package]] -name = "sqlx-macros" -version = "0.8.6" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "sqlx-core", - "sqlx-macros-core", "syn", ] [[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "atoi", - "base64", "bitflags", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", + "core-foundation 0.9.4", + "system-configuration-sys", ] [[package]] -name = "sqlx-sqlite" -version = "0.8.6" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror", - "tracing", - "url", -] - -[[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 = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "core-foundation-sys", + "libc", ] [[package]] -name = "sync_wrapper" -version = "1.0.2" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "proc-macro2", - "quote", - "syn", + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -1884,29 +1782,45 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.8.3" +name = "time" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ - "displaydoc", - "zerovec", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "tinyvec" -version = "1.11.0" +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ - "tinyvec_macros", + "num-conv", + "time-core", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] name = "tokio" @@ -1937,13 +1851,22 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.18" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "futures-core", - "pin-project-lite", + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", "tokio", ] @@ -1986,11 +1909,14 @@ dependencies = [ "bitflags", "bytes", "futures-core", + "futures-util", "http", "http-body", + "iri-string", "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", "tracing", @@ -2071,6 +1997,12 @@ dependencies = [ ] [[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.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2083,31 +2015,22 @@ 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" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "unicode-properties" -version = "0.1.4" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -2122,6 +2045,12 @@ dependencies = [ ] [[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" @@ -2156,16 +2085,37 @@ dependencies = [ ] [[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 = "wasite" -version = "0.1.0" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[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 = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] [[package]] name = "wasm-bindgen" @@ -2181,6 +2131,16 @@ dependencies = [ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2213,13 +2173,47 @@ dependencies = [ ] [[package]] -name = "whoami" -version = "1.6.1" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "libredox", - "wasite", + "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", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2273,6 +2267,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2292,9 +2297,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] @@ -2310,13 +2315,14 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -2325,93 +2331,173 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +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" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "writeable" -version = "0.6.3" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "yoke" -version = "0.8.2" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", + "anyhow", + "heck", + "wit-parser", ] [[package]] -name = "yoke-derive" -version = "0.8.2" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "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", - "synstructure", + "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", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "zerocopy" -version = "0.8.48" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "zerocopy-derive", + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] -name = "zerocopy-derive" -version = "0.8.48" +name = "writeable" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn", + "synstructure", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" axum = "0.8" askama = { version = "0.12", features = ["with-axum"] } askama_axum = "0.4" -sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] } +reqwest = { version = "0.12", features = ["json", "cookies"] } +serde_json = "1" +urlencoding = "2" tokio = { version = "1", features = ["full"] } tower-http = { version = "0.6", features = ["trace", "compression-gzip"] } rust-embed = "8" diff --git a/flake.nix b/flake.nix @@ -66,10 +66,11 @@ inherit cargoArtifacts; } ); + trailbase = pkgs.callPackage ./trailbase.nix { }; in { default = simple-web-app; - inherit simple-web-app; + inherit simple-web-app trailbase; } ); @@ -164,6 +165,9 @@ cargo-watch cargo-edit rust-analyzer + + # Backend + (pkgs.callPackage ./trailbase.nix { }) ] ++ lib.optionals pkgs.stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.Security pkgs.darwin.apple_sdk.frameworks.SystemConfiguration diff --git a/src/config.rs b/src/config.rs @@ -1,7 +1,7 @@ use std::env; pub struct Config { - pub database_url: String, + pub trailbase_url: String, pub debug: bool, pub port: u16, } @@ -10,8 +10,8 @@ impl Config { pub fn from_env() -> Self { dotenvy::dotenv().ok(); - let database_url = - env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:./db.sqlite3".to_string()); + let trailbase_url = + env::var("TRAILBASE_URL").unwrap_or_else(|_| "http://localhost:4000".to_string()); let debug = env::var("DEBUG") .map(|v| v.to_lowercase() == "true" || v == "1") @@ -22,6 +22,10 @@ impl Config { .and_then(|v| v.parse().ok()) .unwrap_or(8000); - Self { database_url, debug, port } + Self { + trailbase_url, + debug, + port, + } } } diff --git a/src/db/mod.rs b/src/db/mod.rs @@ -1,5 +1,29 @@ -pub mod models; -pub mod queries; +// Re-export types from trailbase client +pub use crate::trailbase::{Category, NewsItem, SearchResult}; -pub use models::*; -pub use queries::*; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct NewsWithCategories { + pub id: i64, + pub title: String, + pub text: String, + pub published: String, + pub categories: Vec<Category>, + pub time_since_published: String, +} + +impl NewsWithCategories { + pub fn from_news_item( + item: NewsItem, + categories: Vec<Category>, + time_since_published: String, + ) -> Self { + Self { + id: item.id, + title: item.title, + text: item.text, + published: item.published, + categories, + time_since_published, + } + } +} diff --git a/src/db/models.rs b/src/db/models.rs @@ -1,48 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] -pub struct NewsItem { - pub id: i64, - pub title: String, - pub text: String, - pub published: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] -pub struct SearchResult { - pub title: String, - pub text: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] -pub struct Category { - pub id: i64, - pub name: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NewsWithCategories { - pub id: i64, - pub title: String, - pub text: String, - pub published: String, - pub categories: Vec<Category>, - pub time_since_published: String, -} - -impl NewsWithCategories { - pub fn from_news_item( - item: NewsItem, - categories: Vec<Category>, - time_since_published: String, - ) -> Self { - Self { - id: item.id, - title: item.title, - text: item.text, - published: item.published, - categories, - time_since_published, - } - } -} diff --git a/src/db/queries.rs b/src/db/queries.rs @@ -1,106 +0,0 @@ -use sqlx::SqlitePool; - -use super::models::{Category, NewsItem, SearchResult}; - -pub async fn get_news( - pool: &SqlitePool, - limit: i64, - offset: i64, - max_published_time: &str, -) -> Result<Vec<NewsItem>, sqlx::Error> { - sqlx::query_as::<_, NewsItem>( - r#" - SELECT id, title, text, published - FROM news_item - WHERE language = 'english' - AND (published < ?) - ORDER BY published DESC, id ASC - LIMIT ? - OFFSET ? - "#, - ) - .bind(max_published_time) - .bind(limit) - .bind(offset) - .fetch_all(pool) - .await -} - -pub async fn get_news_by_category( - pool: &SqlitePool, - category_id: i64, - limit: i64, - offset: i64, - max_published_time: &str, -) -> Result<Vec<NewsItem>, sqlx::Error> { - sqlx::query_as::<_, NewsItem>( - r#" - SELECT ni.id, ni.title, ni.text, ni.published - FROM news_item AS ni - INNER JOIN news_item_category AS nic - ON nic.news_item_id = ni.id - WHERE ni.language = 'english' - AND (ni.published < ?) - AND nic.category_id = ? - ORDER BY ni.published DESC, ni.id ASC - LIMIT ? - OFFSET ? - "#, - ) - .bind(max_published_time) - .bind(category_id) - .bind(limit) - .bind(offset) - .fetch_all(pool) - .await -} - -pub async fn search_news( - pool: &SqlitePool, - query: &str, - limit: i64, -) -> Result<Vec<SearchResult>, sqlx::Error> { - sqlx::query_as::<_, SearchResult>( - r#" - SELECT title, text - FROM news_item_fts(?) - LIMIT ? - "#, - ) - .bind(query) - .bind(limit) - .fetch_all(pool) - .await -} - -pub async fn get_categories_for_news( - pool: &SqlitePool, - news_item_id: i64, -) -> Result<Vec<Category>, sqlx::Error> { - sqlx::query_as::<_, Category>( - r#" - SELECT c.id, c.name - FROM news_item_category AS nic - INNER JOIN category AS c - ON c.id = nic.category_id - WHERE nic.news_item_id = ? - "#, - ) - .bind(news_item_id) - .fetch_all(pool) - .await -} - -pub async fn get_categories(pool: &SqlitePool, limit: i64) -> Result<Vec<Category>, sqlx::Error> { - sqlx::query_as::<_, Category>( - r#" - SELECT id, name - FROM category - ORDER BY id - LIMIT ? - "#, - ) - .bind(limit) - .fetch_all(pool) - .await -} diff --git a/src/error.rs b/src/error.rs @@ -4,28 +4,32 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { - #[error("Database error: {0}")] - Database(#[from] sqlx::Error), + #[error("API error: {0}")] + Api(#[from] reqwest::Error), #[error("Template error: {0}")] Template(#[from] askama::Error), #[error("Not found")] NotFound, + + #[error("Unauthorized")] + Unauthorized, } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match &self { - AppError::Database(e) => { - tracing::error!("Database error: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Database error") + AppError::Api(e) => { + tracing::error!("API error: {}", e); + (StatusCode::BAD_GATEWAY, "API error") } AppError::Template(e) => { tracing::error!("Template error: {}", e); (StatusCode::INTERNAL_SERVER_ERROR, "Template error") } AppError::NotFound => (StatusCode::NOT_FOUND, "Not found"), + AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), }; (status, message).into_response() diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs @@ -0,0 +1,79 @@ +use askama::Template; +use axum::{ + extract::State, + response::{Html, Redirect}, + Form, +}; +use serde::Deserialize; + +use crate::error::AppError; +use crate::state::AppState; +use crate::templates::{ApplicationTemplate, LoginTemplate, RegisterTemplate}; + +pub async fn show_login() -> Result<Html<String>, AppError> { + let login = LoginTemplate { + error: None, + }; + let app = ApplicationTemplate { + content: login.render()?, + }; + Ok(Html(app.render()?)) +} + +pub async fn show_register() -> Result<Html<String>, AppError> { + let register = RegisterTemplate { + error: None, + }; + let app = ApplicationTemplate { + content: register.render()?, + }; + Ok(Html(app.render()?)) +} + +#[derive(Debug, Deserialize)] +pub struct LoginForm { + pub email: String, + pub password: String, +} + +pub async fn login( + State(state): State<AppState>, + Form(form): Form<LoginForm>, +) -> Result<Redirect, Html<String>> { + match state.client.login(&form.email, &form.password).await { + Ok(_auth) => { + // TODO: Set cookie with auth token + Ok(Redirect::to("/")) + } + Err(e) => { + let login = LoginTemplate { + error: Some(format!("Login failed: {}", e)), + }; + let app = ApplicationTemplate { + content: login.render().unwrap_or_default(), + }; + Err(Html(app.render().unwrap_or_default())) + } + } +} + +pub async fn register( + State(state): State<AppState>, + Form(form): Form<LoginForm>, +) -> Result<Redirect, Html<String>> { + match state.client.register(&form.email, &form.password).await { + Ok(_auth) => { + // TODO: Set cookie with auth token + Ok(Redirect::to("/")) + } + Err(e) => { + let register = RegisterTemplate { + error: Some(format!("Registration failed: {}", e)), + }; + let app = ApplicationTemplate { + content: register.render().unwrap_or_default(), + }; + Err(Html(app.render().unwrap_or_default())) + } + } +} diff --git a/src/handlers/home.rs b/src/handlers/home.rs @@ -5,9 +5,8 @@ use axum::{ }; use chrono::{DateTime, Utc}; use serde::Deserialize; -use sqlx::SqlitePool; -use crate::db::{self, Category, NewsItem, NewsWithCategories}; +use crate::db::{Category, NewsItem, NewsWithCategories}; use crate::error::AppError; use crate::extractors::HtmxRequest; use crate::state::AppState; @@ -25,7 +24,6 @@ pub struct HomeQuery { // --- Pure functions --- -/// Parse current time from query string, or use now. Returns consistent values. fn resolve_current_time(current_time_param: Option<&str>) -> (DateTime<Utc>, String) { let utc = current_time_param .and_then(|s| DateTime::parse_from_rfc3339(s).ok()) @@ -34,7 +32,6 @@ fn resolve_current_time(current_time_param: Option<&str>) -> (DateTime<Utc>, Str (utc, utc.to_rfc3339()) } -/// Format time since published as human-readable string. fn format_time_since_published(published: &str, current_time: DateTime<Utc>) -> String { let published_time = DateTime::parse_from_str(published, "%Y-%m-%dT%H:%M:%S%.f%z") .or_else(|_| DateTime::parse_from_rfc3339(published)) @@ -42,10 +39,13 @@ fn format_time_since_published(published: &str, current_time: DateTime<Utc>) -> .unwrap_or(current_time); let duration = current_time.signed_duration_since(published_time); - format!("{} days, {} hours ago", duration.num_days(), duration.num_hours() % 24) + format!( + "{} days, {} hours ago", + duration.num_days(), + duration.num_hours() % 24 + ) } -/// Enrich news items with their categories and formatted time. fn enrich_news( rows: Vec<NewsItem>, categories: Vec<Vec<Category>>, @@ -60,22 +60,6 @@ fn enrich_news( .collect() } -// --- IO helper --- - -/// Fetch news, optionally filtered by category. -async fn fetch_news( - pool: &SqlitePool, - category_id: Option<i64>, - limit: i64, - offset: i64, - max_time: &str, -) -> Result<Vec<NewsItem>, sqlx::Error> { - match category_id { - Some(id) => db::get_news_by_category(pool, id, limit, offset, max_time).await, - None => db::get_news(pool, limit, offset, max_time).await, - } -} - // --- Handler --- pub async fn show_home_page( @@ -88,8 +72,23 @@ pub async fn show_home_page( let offset = query.page * PAGE_LIMIT; let (categories, rows) = tokio::try_join!( - db::get_categories(&state.pool, 20), - fetch_news(&state.pool, query.category_id, PAGE_LIMIT + 1, offset, &current_time_str) + state.client.get_categories(20), + async { + match query.category_id { + Some(cat_id) => { + state + .client + .get_news_by_category(cat_id, PAGE_LIMIT + 1, offset, &current_time_str) + .await + } + None => { + state + .client + .get_news(PAGE_LIMIT + 1, offset, &current_time_str) + .await + } + } + } )?; let reached_end = rows.len() as i64 <= PAGE_LIMIT; @@ -97,7 +96,7 @@ pub async fn show_home_page( let category_futures: Vec<_> = rows .iter() - .map(|row| db::get_categories_for_news(&state.pool, row.id)) + .map(|row| state.client.get_categories_for_news(row.id)) .collect(); let news_categories = futures::future::try_join_all(category_futures).await?; diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs @@ -1,7 +1,9 @@ +pub mod auth; pub mod home; pub mod search; pub mod settings; +pub use auth::*; pub use home::*; pub use search::*; pub use settings::*; diff --git a/src/handlers/search.rs b/src/handlers/search.rs @@ -2,7 +2,6 @@ use askama::Template; use axum::{extract::State, response::Html, Form}; use serde::Deserialize; -use crate::db; use crate::error::AppError; use crate::state::AppState; use crate::templates::{SearchResultsTemplate, SearchTemplate}; @@ -24,7 +23,7 @@ pub async fn search( let news = if form.search.is_empty() { Vec::new() } else { - db::search_news(&state.pool, &form.search, 10).await? + state.client.search_news(&form.search, 10).await? }; let template = SearchResultsTemplate { news }; diff --git a/src/main.rs b/src/main.rs @@ -14,6 +14,7 @@ mod handlers; mod routes; mod state; mod templates; +mod trailbase; use config::Config; use state::AppState; @@ -55,9 +56,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing::info!("Starting server with DEBUG={}", config.debug); // Create application state - let state = AppState::new(&config).await?; + let state = AppState::new(&config); - tracing::info!("Database connected and migrations applied"); + tracing::info!("Trailbase client configured at {}", config.trailbase_url); // Build router with middleware let app = routes::create_router(state) diff --git a/src/routes.rs b/src/routes.rs @@ -49,6 +49,10 @@ pub fn create_router(state: AppState) -> Router { .route("/search", post(handlers::search)) .route("/settings", get(handlers::open_settings)) .route("/settings/tab", get(handlers::open_settings_tab)) + .route("/login", get(handlers::show_login)) + .route("/login", post(handlers::login)) + .route("/register", get(handlers::show_register)) + .route("/register", post(handlers::register)) .route("/static/{*path}", get(static_handler)) .with_state(state) } diff --git a/src/state.rs b/src/state.rs @@ -1,39 +1,15 @@ -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; -use sqlx::SqlitePool; -use std::str::FromStr; - use crate::config::Config; +use crate::trailbase::TrailbaseClient; #[derive(Clone)] pub struct AppState { - pub pool: SqlitePool, + pub client: TrailbaseClient, } impl AppState { - pub async fn new(config: &Config) -> Result<Self, sqlx::Error> { - let options = SqliteConnectOptions::from_str(&config.database_url)? - .create_if_missing(true) - .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) - .foreign_keys(true) - .synchronous(sqlx::sqlite::SqliteSynchronous::Normal) - .busy_timeout(std::time::Duration::from_secs(5)); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect_with(options) - .await?; - - // Run additional pragmas - sqlx::query("PRAGMA cache_size = 1000000000") - .execute(&pool) - .await?; - sqlx::query("PRAGMA temp_store = MEMORY") - .execute(&pool) - .await?; - - // Run migrations - sqlx::migrate!("./migrations").run(&pool).await?; - - Ok(Self { pool }) + pub fn new(config: &Config) -> Self { + Self { + client: TrailbaseClient::new(config), + } } } diff --git a/src/templates.rs b/src/templates.rs @@ -74,3 +74,15 @@ pub struct SettingsTemplate { pub struct SettingsTabTemplate { pub tab: String, } + +#[derive(Template)] +#[template(path = "login.html")] +pub struct LoginTemplate { + pub error: Option<String>, +} + +#[derive(Template)] +#[template(path = "register.html")] +pub struct RegisterTemplate { + pub error: Option<String>, +} diff --git a/src/trailbase.rs b/src/trailbase.rs @@ -0,0 +1,224 @@ +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +use crate::config::Config; + +#[derive(Clone)] +pub struct TrailbaseClient { + client: Client, + base_url: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewsItem { + pub id: i64, + pub title: String, + pub text: String, + pub published: String, + pub url: Option<String>, + pub author: Option<String>, + pub language: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Category { + pub id: i64, + pub name: Option<String>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchResult { + pub title: String, + pub text: String, +} + +#[derive(Debug, Deserialize)] +struct ListResponse<T> { + data: Vec<T>, + cursor: Option<String>, +} + +#[derive(Debug, Serialize)] +pub struct LoginRequest { + pub email: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +pub struct RegisterRequest { + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize)] +pub struct AuthResponse { + pub auth_token: String, + pub refresh_token: String, +} + +impl TrailbaseClient { + pub fn new(config: &Config) -> Self { + Self { + client: Client::builder() + .cookie_store(true) + .build() + .expect("Failed to create HTTP client"), + base_url: config.trailbase_url.clone(), + } + } + + pub async fn get_news( + &self, + limit: i64, + offset: i64, + _max_published_time: &str, + ) -> Result<Vec<NewsItem>, reqwest::Error> { + let url = format!( + "{}/api/records/v1/news_item?limit={}&offset={}&order=-published", + self.base_url, limit, offset + ); + let response: ListResponse<NewsItem> = self.client.get(&url).send().await?.json().await?; + Ok(response.data) + } + + pub async fn get_news_by_category( + &self, + category_id: i64, + limit: i64, + offset: i64, + _max_published_time: &str, + ) -> Result<Vec<NewsItem>, reqwest::Error> { + // First get news_item_ids for this category + let url = format!( + "{}/api/records/v1/news_item_category?filter=category_id={}", + self.base_url, category_id + ); + let response: ListResponse<serde_json::Value> = + self.client.get(&url).send().await?.json().await?; + + let news_ids: Vec<i64> = response + .data + .iter() + .filter_map(|v| v.get("news_item_id").and_then(|id| id.as_i64())) + .collect(); + + if news_ids.is_empty() { + return Ok(vec![]); + } + + // Then get the news items + let ids_filter = news_ids + .iter() + .map(|id| format!("id={}", id)) + .collect::<Vec<_>>() + .join(","); + let url = format!( + "{}/api/records/v1/news_item?filter={}&limit={}&offset={}&order=-published", + self.base_url, ids_filter, limit, offset + ); + let response: ListResponse<NewsItem> = self.client.get(&url).send().await?.json().await?; + Ok(response.data) + } + + pub async fn get_categories(&self, limit: i64) -> Result<Vec<Category>, reqwest::Error> { + let url = format!( + "{}/api/records/v1/category?limit={}", + self.base_url, limit + ); + let response: ListResponse<Category> = self.client.get(&url).send().await?.json().await?; + Ok(response.data) + } + + pub async fn get_categories_for_news( + &self, + news_item_id: i64, + ) -> Result<Vec<Category>, reqwest::Error> { + let url = format!( + "{}/api/records/v1/news_item_category?filter=news_item_id={}", + self.base_url, news_item_id + ); + let response: ListResponse<serde_json::Value> = + self.client.get(&url).send().await?.json().await?; + + let category_ids: Vec<i64> = response + .data + .iter() + .filter_map(|v| v.get("category_id").and_then(|id| id.as_i64())) + .collect(); + + if category_ids.is_empty() { + return Ok(vec![]); + } + + let ids_filter = category_ids + .iter() + .map(|id| format!("id={}", id)) + .collect::<Vec<_>>() + .join(","); + let url = format!( + "{}/api/records/v1/category?filter={}", + self.base_url, ids_filter + ); + let response: ListResponse<Category> = self.client.get(&url).send().await?.json().await?; + Ok(response.data) + } + + pub async fn search_news( + &self, + query: &str, + limit: i64, + ) -> Result<Vec<SearchResult>, reqwest::Error> { + // Trailbase doesn't have built-in FTS, so we do a simple LIKE search + let url = format!( + "{}/api/records/v1/news_item?filter=title~{}&limit={}", + self.base_url, + urlencoding::encode(query), + limit + ); + let response: ListResponse<NewsItem> = self.client.get(&url).send().await?.json().await?; + Ok(response + .data + .into_iter() + .map(|n| SearchResult { + title: n.title, + text: n.text, + }) + .collect()) + } + + pub async fn login(&self, email: &str, password: &str) -> Result<AuthResponse, reqwest::Error> { + let url = format!("{}/api/auth/v1/login", self.base_url); + let response = self + .client + .post(&url) + .json(&LoginRequest { + email: email.to_string(), + password: password.to_string(), + }) + .send() + .await? + .json() + .await?; + Ok(response) + } + + pub async fn register( + &self, + email: &str, + password: &str, + ) -> Result<AuthResponse, reqwest::Error> { + let url = format!("{}/api/auth/v1/register", self.base_url); + let response = self + .client + .post(&url) + .json(&RegisterRequest { + email: email.to_string(), + password: password.to_string(), + }) + .send() + .await? + .json() + .await?; + Ok(response) + } +} diff --git a/templates/login.html b/templates/login.html @@ -0,0 +1,27 @@ +<article> + <header> + <h2>Login</h2> + </header> + + {% if let Some(err) = error %} + <p role="alert" style="color: var(--pico-del-color);">{{ err }}</p> + {% endif %} + + <form method="post" action="/login"> + <label> + Email + <input type="email" name="email" required autocomplete="email" /> + </label> + + <label> + Password + <input type="password" name="password" required autocomplete="current-password" /> + </label> + + <button type="submit">Login</button> + </form> + + <footer> + <p>Don't have an account? <a href="/register">Register</a></p> + </footer> +</article> diff --git a/templates/register.html b/templates/register.html @@ -0,0 +1,27 @@ +<article> + <header> + <h2>Register</h2> + </header> + + {% if let Some(err) = error %} + <p role="alert" style="color: var(--pico-del-color);">{{ err }}</p> + {% endif %} + + <form method="post" action="/register"> + <label> + Email + <input type="email" name="email" required autocomplete="email" /> + </label> + + <label> + Password + <input type="password" name="password" required minlength="8" autocomplete="new-password" /> + </label> + + <button type="submit">Register</button> + </form> + + <footer> + <p>Already have an account? <a href="/login">Login</a></p> + </footer> +</article> diff --git a/trailbase.nix b/trailbase.nix @@ -0,0 +1,68 @@ +{ lib +, stdenv +, fetchzip +, autoPatchelfHook +, zlib +, openssl +}: + +let + # Map Nix system to TrailBase release architecture + platformInfo = { + "x86_64-linux" = { + arch = "x86_64_linux"; + sha256 = "sha256-cHm3gDDiitJgd8QFenbfAOfEF5ynGjBrSQxWm0qs4wc="; + }; + "aarch64-linux" = { + arch = "arm64_linux"; + sha256 = lib.fakeSha256; + }; + "x86_64-darwin" = { + arch = "x86_64_apple_darwin"; + sha256 = lib.fakeSha256; + }; + "aarch64-darwin" = { + arch = "arm64_apple_darwin"; + sha256 = "sha256-bUTGA9WJohFjhPXt5bOdBuy8z5TytWJ5hLJX2yMYvbM="; + }; + }; + + platform = platformInfo.${stdenv.system} or (throw "Unsupported system: ${stdenv.system}"); +in +stdenv.mkDerivation rec { + pname = "trailbase"; + version = "0.23.0"; + + src = fetchzip { + url = "https://github.com/trailbaseio/trailbase/releases/download/v${version}/trailbase_v${version}_${platform.arch}.zip"; + sha256 = platform.sha256; + stripRoot = false; + }; + + nativeBuildInputs = lib.optionals stdenv.isLinux [ autoPatchelfHook ]; + + buildInputs = lib.optionals stdenv.isLinux [ + stdenv.cc.cc.lib + zlib + openssl + ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + cp trail $out/bin/ + chmod +x $out/bin/trail + + runHook postInstall + ''; + + meta = with lib; { + description = "A blazingly fast, open-source application server built on Rust & SQLite"; + homepage = "https://github.com/trailbaseio/trailbase"; + license = licenses.osl3; + platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + maintainers = [ ]; + }; +} + diff --git a/traildepot/.gitignore b/traildepot/.gitignore @@ -0,0 +1,7 @@ +data/ +backups/ +uploads/ +secrets/ +*.db +*.db-wal +*.db-shm diff --git a/traildepot/config.textproto b/traildepot/config.textproto @@ -0,0 +1,35 @@ +# TrailBase config for simple-web-app +email {} +server { + application_name: "SimpleWebApp" + logs_retention_sec: 604800 +} +auth { + auth_token_ttl_sec: 3600 + refresh_token_ttl_sec: 2592000 +} +jobs {} + +record_apis: [{ + name: "news_item" + table_name: "news_item" + acl_world: [READ] + acl_authenticated: [READ, CREATE, UPDATE, DELETE] + create_access_rule: "_REQ_.created_by = _USER_.id" + update_access_rule: "_ROW_.created_by = _USER_.id" + delete_access_rule: "_ROW_.created_by = _USER_.id" +}, { + name: "category" + table_name: "category" + acl_world: [READ] + acl_authenticated: [READ, CREATE] +}, { + name: "news_item_category" + table_name: "news_item_category" + acl_world: [READ] + acl_authenticated: [READ, CREATE, DELETE] +}, { + name: "news_with_categories" + table_name: "news_with_categories" + acl_world: [READ] +}] diff --git a/traildepot/metadata.textproto b/traildepot/metadata.textproto @@ -0,0 +1,2 @@ +# Auto-generated metadata +schema_version: 1 diff --git a/traildepot/migrations/main/U1_initial_schema.sql b/traildepot/migrations/main/U1_initial_schema.sql @@ -0,0 +1,53 @@ +CREATE TABLE category ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT +) STRICT; + +CREATE TABLE news_item ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'url' TEXT, + 'title' TEXT NOT NULL, + 'text' TEXT NOT NULL, + 'published' TEXT NOT NULL, + 'author' TEXT, + 'language' TEXT NOT NULL, + 'created_by' BLOB REFERENCES '_user'('id') +) STRICT; + +CREATE TABLE news_item_category ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'news_item_id' INTEGER NOT NULL REFERENCES 'news_item'('id'), + 'category_id' INTEGER NOT NULL REFERENCES 'category'('id') +) STRICT; + +-- Full-text search +CREATE VIRTUAL TABLE news_item_fts USING fts5( + title, + text, + content='news_item', + content_rowid='id' +); + +CREATE TRIGGER news_item_ai AFTER INSERT ON news_item BEGIN + INSERT INTO news_item_fts(rowid, title, text) + VALUES (new.id, new.title, new.text); +END; + +CREATE TRIGGER news_item_au AFTER UPDATE ON news_item BEGIN + UPDATE news_item_fts SET title=new.title, text=new.text + WHERE rowid=old.id; +END; + +CREATE TRIGGER news_item_ad AFTER DELETE ON news_item BEGIN + DELETE FROM news_item_fts WHERE rowid=old.id; +END; + +-- View for news with categories +CREATE VIEW news_with_categories AS +SELECT + n.*, + GROUP_CONCAT(c.name) as category_names +FROM news_item n +LEFT JOIN news_item_category nc ON nc.news_item_id = n.id +LEFT JOIN category c ON c.id = nc.category_id +GROUP BY n.id;