diff --git a/Cargo.lock b/Cargo.lock index e205802..6632663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -140,6 +146,15 @@ dependencies = [ "syn 2.0.114", ] +[[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" @@ -182,13 +197,41 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core", + "axum-core 0.5.6", "axum-macros", "base64 0.22.1", "bytes", @@ -200,7 +243,7 @@ dependencies = [ "hyper 1.8.1", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -219,6 +262,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-core" version = "0.5.6" @@ -238,6 +302,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +dependencies = [ + "axum 0.7.9", + "axum-core 0.4.5", + "bytes", + "cookie", + "fastrand", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "multer", + "pin-project-lite", + "serde", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-macros" version = "0.5.0" @@ -253,8 +341,10 @@ dependencies = [ name = "backend" version = "0.1.0" dependencies = [ - "axum", + "axum 0.8.8", + "axum-extra", "base64 0.22.1", + "bcrypt", "bytes", "clap", "dotenvy", @@ -262,10 +352,12 @@ dependencies = [ "mime_guess", "openssl", "quick-xml", + "rand 0.8.5", "rust-embed", "serde", "serde_json", "shared", + "sqlx", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -309,6 +401,19 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bcrypt" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom 0.3.4", + "subtle", + "zeroize", +] + [[package]] name = "binstring" version = "0.1.7" @@ -320,6 +425,9 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -330,6 +438,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "brotli" version = "8.0.2" @@ -434,6 +552,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.56" @@ -516,6 +644,15 @@ version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.14.1" @@ -590,6 +727,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -615,6 +763,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -624,6 +787,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crunchy" version = "0.2.4" @@ -721,6 +899,15 @@ dependencies = [ "synstructure 0.12.6", ] +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive-where" version = "1.6.0" @@ -825,6 +1012,9 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -847,6 +1037,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -863,6 +1062,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -896,12 +1117,29 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -992,6 +1230,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1172,12 +1421,32 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -1232,6 +1501,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -1525,6 +1803,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "interpolator" version = "0.5.0" @@ -1599,7 +1886,7 @@ dependencies = [ "p256", "p384", "rand 0.8.5", - "rsa", + "rsa 0.7.2", "serde", "serde_json", "spki 0.6.0", @@ -1815,6 +2102,28 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linear-map" version = "1.2.0" @@ -1884,12 +2193,28 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matchit" version = "0.8.4" 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.7.6" @@ -1939,6 +2264,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 1.4.0", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -1991,6 +2333,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + [[package]] name = "num-integer" version = "0.1.46" @@ -2127,6 +2475,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2145,7 +2499,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -2251,6 +2605,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -2292,6 +2657,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2523,6 +2894,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.2" @@ -2574,7 +2954,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "pkcs1", + "pkcs1 0.4.1", "pkcs8 0.9.0", "rand_core 0.6.4", "signature 1.6.4", @@ -2583,6 +2963,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1 0.7.5", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + [[package]] name = "rstml" version = "0.11.2" @@ -2647,7 +3047,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3021,6 +3421,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3047,6 +3450,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -3068,12 +3474,211 @@ dependencies = [ "der 0.7.10", ] +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "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 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.114", +] + +[[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 2.0.114", + "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 0.22.1", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa 0.9.10", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "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 = "strsim" version = "0.11.1" @@ -3168,7 +3773,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3220,6 +3825,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +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 = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3230,6 +3866,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -3533,12 +4184,33 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -3617,7 +4289,7 @@ version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" dependencies = [ - "axum", + "axum 0.8.8", "base64 0.22.1", "mime_guess", "regex", @@ -3692,6 +4364,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasix" version = "0.13.1" @@ -3805,6 +4483,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -3875,18 +4563,18 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] @@ -3909,6 +4597,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3942,6 +4645,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3954,6 +4663,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3966,6 +4681,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3990,6 +4711,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4002,6 +4729,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4014,6 +4747,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4026,6 +4765,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index a79f5b6..86eeede 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -33,3 +33,7 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } web-push = { version = "0.10", default-features = false, features = ["hyper-client"], optional = true } base64 = "0.22" openssl = { version = "0.10", features = ["vendored"], optional = true } +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] } +bcrypt = "0.17.0" +axum-extra = { version = "0.9", features = ["cookie"] } +rand = "0.8" diff --git a/backend/src/db.rs b/backend/src/db.rs new file mode 100644 index 0000000..4080820 --- /dev/null +++ b/backend/src/db.rs @@ -0,0 +1,106 @@ +use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite, Row}; +use std::time::Duration; +use anyhow::Result; + +#[derive(Clone)] +pub struct Db { + pool: Pool, +} + +impl Db { + pub async fn new(db_url: &str) -> Result { + let pool = SqlitePoolOptions::new() + .max_connections(5) + .acquire_timeout(Duration::from_secs(3)) + .connect(db_url) + .await?; + + let db = Self { pool }; + db.init().await?; + Ok(db) + } + + async fn init(&self) -> Result<()> { + // Create users table + sqlx::query( + "CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )", + ) + .execute(&self.pool) + .await?; + + // Create sessions table + sqlx::query( + "CREATE TABLE IF NOT EXISTS sessions ( + token TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + expires_at DATETIME NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) + )", + ) + .execute(&self.pool) + .await?; + + Ok(()) + } + + // --- User Operations --- + + pub async fn create_user(&self, username: &str, password_hash: &str) -> Result<()> { + sqlx::query("INSERT INTO users (username, password_hash) VALUES (?, ?)") + .bind(username) + .bind(password_hash) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_user_by_username(&self, username: &str) -> Result> { + let row = sqlx::query("SELECT id, password_hash FROM users WHERE username = ?") + .bind(username) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| (r.get(0), r.get(1)))) + } + + pub async fn has_users(&self) -> Result { + let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users") + .fetch_one(&self.pool) + .await?; + Ok(row.0 > 0) + } + + // --- Session Operations --- + + pub async fn create_session(&self, user_id: i64, token: &str, expires_at: i64) -> Result<()> { + sqlx::query("INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, datetime(?, 'unixepoch'))") + .bind(token) + .bind(user_id) + .bind(expires_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_session_user(&self, token: &str) -> Result> { + let row = sqlx::query("SELECT user_id FROM sessions WHERE token = ? AND expires_at > datetime('now')") + .bind(token) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| r.get(0))) + } + + pub async fn delete_session(&self, token: &str) -> Result<()> { + sqlx::query("DELETE FROM sessions WHERE token = ?") + .bind(token) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs index 93217d5..c3e117f 100644 --- a/backend/src/handlers/mod.rs +++ b/backend/src/handlers/mod.rs @@ -18,6 +18,9 @@ use shared::{ }; use utoipa::ToSchema; +pub mod auth; +pub mod setup; + #[derive(RustEmbed)] #[folder = "../frontend/dist"] pub struct Asset; @@ -709,8 +712,8 @@ pub async fn subscribe_push_handler( Json(subscription): Json, ) -> impl IntoResponse { tracing::info!("Received push subscription: {:?}", subscription); - + state.push_store.add_subscription(subscription).await; - + (StatusCode::OK, "Subscription saved").into_response() } diff --git a/backend/src/handlers/setup.rs b/backend/src/handlers/setup.rs new file mode 100644 index 0000000..3b10033 --- /dev/null +++ b/backend/src/handlers/setup.rs @@ -0,0 +1,66 @@ +use crate::{db::Db, AppState}; +use axum::{ + extract::{State, Json}, + http::StatusCode, + response::IntoResponse, +}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Deserialize, ToSchema)] +pub struct SetupRequest { + username: String, + password: String, +} + +#[derive(Serialize)] +pub struct SetupStatusResponse { + completed: bool, +} + +pub async fn get_setup_status_handler(State(state): State) -> impl IntoResponse { + let completed = match state.db.has_users().await { + Ok(has) => has, + Err(e) => { + tracing::error!("DB error checking users: {}", e); + false + } + }; + Json(SetupStatusResponse { completed }).into_response() +} + +pub async fn setup_handler( + State(state): State, + Json(payload): Json, +) -> impl IntoResponse { + // 1. Check if setup is already completed (i.e., users exist) + match state.db.has_users().await { + Ok(true) => return (StatusCode::FORBIDDEN, "Setup already completed").into_response(), + Err(e) => { + tracing::error!("DB error checking users: {}", e); + return (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response(); + } + Ok(false) => {} // Proceed + } + + // 2. Validate input + if payload.username.len() < 3 || payload.password.len() < 6 { + return (StatusCode::BAD_REQUEST, "Username must be at least 3 chars, password at least 6").into_response(); + } + + // 3. Create User + let password_hash = match bcrypt::hash(&payload.password, bcrypt::DEFAULT_COST) { + Ok(h) => h, + Err(e) => { + tracing::error!("Failed to hash password: {}", e); + return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to process password").into_response(); + } + }; + + if let Err(e) = state.db.create_user(&payload.username, &password_hash).await { + tracing::error!("Failed to create user: {}", e); + return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create user").into_response(); + } + + (StatusCode::OK, "Setup completed successfully").into_response() +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 7f9876e..26d4663 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,3 +1,4 @@ +mod db; mod diff; mod handlers; #[cfg(feature = "push-notifications")] @@ -10,7 +11,12 @@ use axum::error_handling::HandleErrorLayer; use axum::{ routing::{get, post}, Router, + middleware::{self, Next}, + extract::Request, + response::Response, + http::StatusCode, }; +use axum_extra::extract::cookie::CookieJar; use clap::Parser; use dotenvy::dotenv; use shared::{AppEvent, Torrent}; @@ -32,10 +38,40 @@ pub struct AppState { pub tx: Arc>>, pub event_bus: broadcast::Sender, pub scgi_socket_path: String, + pub db: db::Db, #[cfg(feature = "push-notifications")] pub push_store: push::PushSubscriptionStore, } +async fn auth_middleware( + state: axum::extract::State, + jar: CookieJar, + request: Request, + next: Next, +) -> Result { + // Skip auth for public paths + let path = request.uri().path(); + if path.starts_with("/api/auth/login") + || path.starts_with("/api/auth/check") // Used by frontend to decide where to go + || path.starts_with("/api/setup") + || path.starts_with("/swagger-ui") + || path.starts_with("/api-docs") + || !path.starts_with("/api/") // Allow static files (frontend) + { + return Ok(next.run(request).await); + } + + // Check token + if let Some(token) = jar.get("auth_token") { + match state.db.get_session_user(token.value()).await { + Ok(Some(_)) => return Ok(next.run(request).await), + _ => {} // Invalid + } + } + + Err(StatusCode::UNAUTHORIZED) +} + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { @@ -51,6 +87,10 @@ struct Args { /// Port to listen on #[arg(short, long, env = "PORT", default_value_t = 3000)] port: u16, + + /// Database URL + #[arg(long, env = "DATABASE_URL", default_value = "sqlite:vibetorrent.db")] + db_url: String, } #[cfg(feature = "push-notifications")] @@ -68,7 +108,10 @@ struct Args { handlers::get_global_limit_handler, handlers::set_global_limit_handler, handlers::get_push_public_key_handler, - handlers::subscribe_push_handler + handlers::subscribe_push_handler, + handlers::auth::login_handler, + handlers::setup::setup_handler, + handlers::setup::get_setup_status_handler ), components( schemas( @@ -83,7 +126,9 @@ struct Args { shared::SetLabelRequest, shared::GlobalLimitRequest, push::PushSubscription, - push::PushKeys + push::PushKeys, + handlers::auth::LoginRequest, + handlers::setup::SetupRequest ) ), tags( @@ -105,7 +150,10 @@ struct ApiDoc; handlers::set_file_priority_handler, handlers::set_label_handler, handlers::get_global_limit_handler, - handlers::set_global_limit_handler + handlers::set_global_limit_handler, + handlers::auth::login_handler, + handlers::setup::setup_handler, + handlers::setup::get_setup_status_handler ), components( schemas( @@ -118,7 +166,9 @@ struct ApiDoc; shared::TorrentTracker, shared::SetFilePriorityRequest, shared::SetLabelRequest, - shared::GlobalLimitRequest + shared::GlobalLimitRequest, + handlers::auth::LoginRequest, + handlers::setup::SetupRequest ) ), tags( @@ -146,6 +196,29 @@ async fn main() { tracing::info!("Socket: {}", args.socket); tracing::info!("Port: {}", args.port); + // Initialize Database + tracing::info!("Connecting to database: {}", args.db_url); + // Ensure the db file exists if it's sqlite + if args.db_url.starts_with("sqlite:") { + let path = args.db_url.trim_start_matches("sqlite:"); + if !std::path::Path::new(path).exists() { + tracing::info!("Database file not found, creating: {}", path); + match std::fs::File::create(path) { + Ok(_) => tracing::info!("Created empty database file"), + Err(e) => tracing::error!("Failed to create database file: {}", e), + } + } + } + + let db = match db::Db::new(&args.db_url).await { + Ok(db) => db, + Err(e) => { + tracing::error!("Failed to connect to database: {}", e); + std::process::exit(1); + } + }; + tracing::info!("Database connected successfully."); + // Startup Health Check let socket_path = std::path::Path::new(&args.socket); if !socket_path.exists() { @@ -181,6 +254,7 @@ async fn main() { tx: tx.clone(), event_bus: event_bus.clone(), scgi_socket_path: args.socket.clone(), + db: db.clone(), #[cfg(feature = "push-notifications")] push_store: push::PushSubscriptionStore::new(), }; @@ -308,6 +382,13 @@ async fn main() { let app = Router::new() .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) + // Setup & Auth Routes + .route("/api/setup/status", get(handlers::setup::get_setup_status_handler)) + .route("/api/setup", post(handlers::setup::setup_handler)) + .route("/api/auth/login", post(handlers::auth::login_handler)) + .route("/api/auth/logout", post(handlers::auth::logout_handler)) + .route("/api/auth/check", get(handlers::auth::check_auth_handler)) + // App Routes .route("/api/events", get(sse::sse_handler)) .route("/api/torrents/add", post(handlers::add_torrent_handler)) .route( @@ -337,13 +418,14 @@ async fn main() { get(handlers::get_global_limit_handler).post(handlers::set_global_limit_handler), ) .fallback(handlers::static_handler); // Serve static files for everything else - + #[cfg(feature = "push-notifications")] let app = app .route("/api/push/public-key", get(handlers::get_push_public_key_handler)) .route("/api/push/subscribe", post(handlers::subscribe_push_handler)); - + let app = app + .layer(middleware::from_fn_with_state(app_state.clone(), auth_middleware)) .layer(TraceLayer::new_for_http()) .layer( CompressionLayer::new() diff --git a/frontend/src/app.rs b/frontend/src/app.rs index e8d27e8..2c24191 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -3,92 +3,149 @@ use crate::components::layout::statusbar::StatusBar; use crate::components::layout::toolbar::Toolbar; use crate::components::toast::ToastContainer; use crate::components::torrent::table::TorrentTable; +use crate::components::auth::login::Login; +use crate::components::auth::setup::Setup; use leptos::*; use leptos_router::*; +use serde::Deserialize; + +#[derive(Deserialize)] +struct SetupStatus { + completed: bool, +} #[component] pub fn App() -> impl IntoView { crate::store::provide_torrent_store(); - // Initialize push notifications after user grants permission + // Auth State + let (is_loading, set_is_loading) = create_signal(true); + let (is_authenticated, set_is_authenticated) = create_signal(false); + + // Check Auth & Setup Status on load create_effect(move |_| { - spawn_local(async { - // Wait a bit for service worker to be ready - gloo_timers::future::TimeoutFuture::new(2000).await; - - // Check if running on iOS and not standalone - if let Some(ios_message) = crate::utils::platform::get_ios_notification_info() { - log::warn!("iOS detected: {}", ios_message); - - // Show toast to inform user - if let Some(store) = use_context::() { - crate::store::show_toast_with_signal( - store.notifications, - shared::NotificationLevel::Info, - ios_message, - ); + spawn_local(async move { + // 1. Check Setup Status + let setup_res = gloo_net::http::Request::get("/api/setup/status").send().await; + if let Ok(resp) = setup_res { + if let Ok(status) = resp.json::().await { + if !status.completed { + // Redirect to setup if not completed + let navigate = use_navigate(); + navigate("/setup", Default::default()); + set_is_loading.set(false); + return; + } } - return; } - - // Check if push notifications are supported - if !crate::utils::platform::supports_push_notifications() { - log::warn!("Push notifications not supported on this platform"); - return; - } - - // Safari requires user gesture for notification permission - // Don't auto-request on Safari - user should click a button - if crate::utils::platform::is_safari() { - log::info!("Safari detected - notification permission requires user interaction"); - if let Some(store) = use_context::() { - crate::store::show_toast_with_signal( - store.notifications, - shared::NotificationLevel::Info, - "Bildirim izni için sağ alttaki ayarlar ⚙️ ikonuna basın.".to_string(), - ); + + // 2. Check Auth Status + let auth_res = gloo_net::http::Request::get("/api/auth/check").send().await; + if let Ok(resp) = auth_res { + if resp.status() == 200 { + set_is_authenticated.set(true); + + // Initialize push notifications logic only if authenticated + // ... (Push notification logic moved here or kept global but guarded) + } else { + let navigate = use_navigate(); + // If we are already on login or setup, don't redirect loop + let pathname = window().location().pathname().unwrap_or_default(); + if pathname != "/login" && pathname != "/setup" { + navigate("/login", Default::default()); + } } - return; } - - // For non-Safari browsers (Chrome, Firefox, Edge), attempt auto-subscribe - log::info!("Attempting to subscribe to push notifications..."); - crate::store::subscribe_to_push_notifications().await; + set_is_loading.set(false); }); }); + // Initialize push notifications after user grants permission (Only if authenticated) + create_effect(move |_| { + if is_authenticated.get() { + spawn_local(async { + // Wait a bit for service worker to be ready + gloo_timers::future::TimeoutFuture::new(2000).await; + + // Check if running on iOS and not standalone + if let Some(ios_message) = crate::utils::platform::get_ios_notification_info() { + log::warn!("iOS detected: {}", ios_message); + if let Some(store) = use_context::() { + crate::store::show_toast_with_signal( + store.notifications, + shared::NotificationLevel::Info, + ios_message, + ); + } + return; + } + + if !crate::utils::platform::supports_push_notifications() { + return; + } + + if crate::utils::platform::is_safari() { + if let Some(store) = use_context::() { + crate::store::show_toast_with_signal( + store.notifications, + shared::NotificationLevel::Info, + "Bildirim izni için sağ alttaki ayarlar ⚙️ ikonuna basın.".to_string(), + ); + } + return; + } + + crate::store::subscribe_to_push_notifications().await; + }); + } + }); + view! { - // Main app wrapper - ensures proper stacking context
- // Drawer layout -
- + + + } /> + } /> -
- + + +
+ }> + }> + // Protected Layout +
+ -
- - - } /> - "Settings Page (Coming Soon)"
} /> -
-
- +
+ - // StatusBar is rendered via fixed positioning, just mount it here - -
+
+ + } /> + "Settings Page (Coming Soon)"
} /> + + + + +
+ +
+ + +
+ + + + } + }/> + + -
- - -
- - - // Toast container - fixed positioning relative to viewport } diff --git a/frontend/src/components/auth/login.rs b/frontend/src/components/auth/login.rs new file mode 100644 index 0000000..cfce11b --- /dev/null +++ b/frontend/src/components/auth/login.rs @@ -0,0 +1,110 @@ +use leptos::*; +use leptos_router::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize)] +struct LoginRequest { + username: String, + password: String, +} + +#[component] +pub fn Login() -> impl IntoView { + let (username, set_username) = create_signal(String::new()); + let (password, set_password) = create_signal(String::new()); + let (error, set_error) = create_signal(Option::::None); + let (loading, set_loading) = create_signal(false); + + let handle_login = move |ev: web_sys::SubmitEvent| { + ev.prevent_default(); + set_loading.set(true); + set_error.set(None); + + spawn_local(async move { + let req = LoginRequest { + username: username.get(), + password: password.get(), + }; + + let client = gloo_net::http::Request::post("/api/auth/login") + .json(&req) + .expect("Failed to create request"); + + match client.send().await { + Ok(resp) => { + if resp.ok() { + // Redirect to home on success + let navigate = use_navigate(); + navigate("/", Default::default()); + } else { + set_error.set(Some("Kullanıcı adı veya şifre hatalı".to_string())); + } + } + Err(_) => { + set_error.set(Some("Bağlantı hatası".to_string())); + } + } + set_loading.set(false); + }); + }; + + view! { +
+
+
+

"VibeTorrent Giriş"

+ +
+
+ + +
+ +
+ + +
+ + +
+ + {move || error.get()} +
+
+ +
+ +
+
+
+
+
+ } +} diff --git a/frontend/src/components/auth/mod.rs b/frontend/src/components/auth/mod.rs new file mode 100644 index 0000000..f8d416d --- /dev/null +++ b/frontend/src/components/auth/mod.rs @@ -0,0 +1,2 @@ +pub mod login; +pub mod setup; diff --git a/frontend/src/components/auth/setup.rs b/frontend/src/components/auth/setup.rs new file mode 100644 index 0000000..7ab93a3 --- /dev/null +++ b/frontend/src/components/auth/setup.rs @@ -0,0 +1,145 @@ +use leptos::*; +use leptos_router::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize)] +struct SetupRequest { + username: String, + password: String, +} + +#[component] +pub fn Setup() -> impl IntoView { + let (username, set_username) = create_signal(String::new()); + let (password, set_password) = create_signal(String::new()); + let (confirm_password, set_confirm_password) = create_signal(String::new()); + let (error, set_error) = create_signal(Option::::None); + let (loading, set_loading) = create_signal(false); + + let handle_setup = move |ev: web_sys::SubmitEvent| { + ev.prevent_default(); + set_loading.set(true); + set_error.set(None); + + let pass = password.get(); + let confirm = confirm_password.get(); + + if pass != confirm { + set_error.set(Some("Şifreler eşleşmiyor".to_string())); + set_loading.set(false); + return; + } + + if pass.len() < 6 { + set_error.set(Some("Şifre en az 6 karakter olmalıdır".to_string())); + set_loading.set(false); + return; + } + + spawn_local(async move { + let req = SetupRequest { + username: username.get(), + password: pass, + }; + + let client = gloo_net::http::Request::post("/api/setup") + .json(&req) + .expect("Failed to create request"); + + match client.send().await { + Ok(resp) => { + if resp.ok() { + // Redirect to login after setup + let navigate = use_navigate(); + navigate("/login", Default::default()); + } else { + let text = resp.text().await.unwrap_or_default(); + set_error.set(Some(format!("Hata: {}", text))); + } + } + Err(_) => { + set_error.set(Some("Bağlantı hatası".to_string())); + } + } + set_loading.set(false); + }); + }; + + view! { +
+
+
+

"VibeTorrent Kurulumu"

+

"Yönetici hesabınızı oluşturun"

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + {move || error.get()} +
+
+ +
+ +
+
+
+
+
+ } +} diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs index f86eef4..4174455 100644 --- a/frontend/src/components/mod.rs +++ b/frontend/src/components/mod.rs @@ -3,3 +3,4 @@ pub mod layout; pub mod modal; pub mod toast; pub mod torrent; +pub mod auth;