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:
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, ¤t_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, ¤t_time_str)
+ .await
+ }
+ None => {
+ state
+ .client
+ .get_news(PAGE_LIMIT + 1, offset, ¤t_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;