modernize: migrate to Leptos 0.8 and Server Functions architecture, break backend->shared loop
Some checks failed
Build MIPS Binary / build (push) Failing after 1m27s
Some checks failed
Build MIPS Binary / build (push) Failing after 1m27s
This commit is contained in:
1261
Cargo.lock
generated
1261
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,15 +4,15 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["push-notifications", "swagger"]
|
default = ["swagger"] # push-notifications kaldırıldı
|
||||||
push-notifications = ["web-push", "openssl"]
|
push-notifications = ["web-push", "openssl"]
|
||||||
swagger = ["utoipa-swagger-ui"]
|
swagger = ["utoipa-swagger-ui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8", features = ["macros", "ws"] }
|
axum = { version = "0.8", features = ["macros", "ws"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util", "timeout"] }
|
tower = { version = "0.5", features = ["util", "timeout"] }
|
||||||
tower-http = { version = "0.5", features = ["fs", "trace", "cors", "compression-full"] }
|
tower-http = { version = "0.6", features = ["fs", "trace", "cors", "compression-full"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
@@ -21,16 +21,15 @@ tokio-stream = "0.1"
|
|||||||
bytes = "1"
|
bytes = "1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
quick-xml = { version = "0.31", features = ["serde", "serialize"] }
|
quick-xml = { version = "0.31", features = ["serde", "serialize"] }
|
||||||
# We might need `tokio-util` for codecs if we implement SCGI manually
|
|
||||||
tokio-util = { version = "0.7", features = ["codec", "io"] }
|
tokio-util = { version = "0.7", features = ["codec", "io"] }
|
||||||
clap = { version = "4.4", features = ["derive", "env"] }
|
clap = { version = "4.4", features = ["derive", "env"] }
|
||||||
rust-embed = "8.2"
|
rust-embed = "8.2"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
shared = { path = "../shared" }
|
shared = { path = "../shared", features = ["ssr"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"], optional = true }
|
utoipa-swagger-ui = { version = "9.0", features = ["axum"], optional = true }
|
||||||
web-push = { version = "0.10", default-features = false, features = ["hyper-client"], optional = true }
|
web-push = { version = "0.10", default-features = false, features = ["hyper-client"], optional = true }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
||||||
@@ -42,3 +41,7 @@ anyhow = "1.0.101"
|
|||||||
time = { version = "0.3.47", features = ["serde", "formatting", "parsing"] }
|
time = { version = "0.3.47", features = ["serde", "formatting", "parsing"] }
|
||||||
tower_governor = "0.8.0"
|
tower_governor = "0.8.0"
|
||||||
governor = "0.10.4"
|
governor = "0.10.4"
|
||||||
|
|
||||||
|
# Leptos
|
||||||
|
leptos = { version = "0.8.15", features = ["nightly"] }
|
||||||
|
leptos_axum = { version = "0.8.7" }
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::{
|
use shared::{
|
||||||
xmlrpc::{self, RpcParam},
|
xmlrpc::{self, RpcParam},
|
||||||
AppState,
|
AddTorrentRequest, GlobalLimitRequest, SetFilePriorityRequest, SetLabelRequest, TorrentActionRequest,
|
||||||
|
TorrentFile, TorrentPeer, TorrentTracker,
|
||||||
};
|
};
|
||||||
|
use crate::AppState;
|
||||||
#[cfg(feature = "push-notifications")]
|
#[cfg(feature = "push-notifications")]
|
||||||
use crate::push;
|
use crate::push;
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -11,10 +13,6 @@ use axum::{
|
|||||||
BoxError,
|
BoxError,
|
||||||
};
|
};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use shared::{
|
|
||||||
AddTorrentRequest, GlobalLimitRequest, SetFilePriorityRequest, SetLabelRequest, TorrentActionRequest,
|
|
||||||
TorrentFile, TorrentPeer, TorrentTracker,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ mod handlers;
|
|||||||
#[cfg(feature = "push-notifications")]
|
#[cfg(feature = "push-notifications")]
|
||||||
mod push;
|
mod push;
|
||||||
mod rate_limit;
|
mod rate_limit;
|
||||||
mod scgi;
|
|
||||||
mod sse;
|
mod sse;
|
||||||
mod xmlrpc;
|
|
||||||
|
use shared::{scgi, xmlrpc};
|
||||||
|
|
||||||
use axum::error_handling::HandleErrorLayer;
|
use axum::error_handling::HandleErrorLayer;
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -59,6 +59,7 @@ async fn auth_middleware(
|
|||||||
if path.starts_with("/api/auth/login")
|
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/auth/check") // Used by frontend to decide where to go
|
||||||
|| path.starts_with("/api/setup")
|
|| path.starts_with("/api/setup")
|
||||||
|
|| path.starts_with("/api/server_fns")
|
||||||
|| path.starts_with("/swagger-ui")
|
|| path.starts_with("/swagger-ui")
|
||||||
|| path.starts_with("/api-docs")
|
|| path.starts_with("/api-docs")
|
||||||
|| !path.starts_with("/api/") // Allow static files (frontend)
|
|| !path.starts_with("/api/") // Allow static files (frontend)
|
||||||
@@ -528,6 +529,7 @@ async fn main() {
|
|||||||
"/api/settings/global-limits",
|
"/api/settings/global-limits",
|
||||||
get(handlers::get_global_limit_handler).post(handlers::set_global_limit_handler),
|
get(handlers::get_global_limit_handler).post(handlers::set_global_limit_handler),
|
||||||
)
|
)
|
||||||
|
.route("/api/server_fns/{*fn_name}", post(leptos_axum::handle_server_fns))
|
||||||
.fallback(handlers::static_handler); // Serve static files for everything else
|
.fallback(handlers::static_handler); // Serve static files for everything else
|
||||||
|
|
||||||
#[cfg(feature = "push-notifications")]
|
#[cfg(feature = "push-notifications")]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::xmlrpc::{
|
use shared::xmlrpc::{
|
||||||
parse_i64_response, parse_multicall_response, RpcParam, RtorrentClient, XmlRpcError,
|
parse_i64_response, parse_multicall_response, RpcParam, RtorrentClient, XmlRpcError,
|
||||||
};
|
};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ edition = "2021"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
leptos = { version = "0.6", features = ["csr"] }
|
leptos = { version = "0.8.7", features = ["csr", "nightly"] }
|
||||||
leptos_router = { version = "0.6", features = ["csr"] }
|
leptos_router = { version = "0.8.7", features = ["nightly"] }
|
||||||
|
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
console_log = "1"
|
console_log = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
gloo-net = "0.5"
|
gloo-net = "0.6"
|
||||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
@@ -23,11 +23,11 @@ uuid = { version = "1", features = ["v4", "js"] }
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
chrono = { version = "0.4", features = ["serde", "wasm-bindgen"] }
|
chrono = { version = "0.4", features = ["serde", "wasm-bindgen"] }
|
||||||
web-sys = { version = "0.3", features = ["HtmlDivElement", "HtmlUListElement", "HtmlLiElement", "HtmlAnchorElement", "MouseEvent", "Event", "Window", "Document", "Element", "DomTokenList", "CssStyleDeclaration", "Storage", "TouchEvent", "TouchList", "Touch", "Navigator", "Notification", "NotificationOptions", "NotificationPermission", "ServiceWorkerContainer", "ServiceWorkerRegistration", "PushManager", "PushSubscription", "PushSubscriptionOptions", "PushSubscriptionOptionsInit", "HtmlDetailsElement"] }
|
web-sys = { version = "0.3", features = ["HtmlDivElement", "HtmlUListElement", "HtmlLiElement", "HtmlAnchorElement", "MouseEvent", "Event", "Window", "Document", "Element", "DomTokenList", "CssStyleDeclaration", "Storage", "TouchEvent", "TouchList", "Touch", "Navigator", "Notification", "NotificationOptions", "NotificationPermission", "ServiceWorkerContainer", "ServiceWorkerRegistration", "PushManager", "PushSubscription", "PushSubscriptionOptions", "PushSubscriptionOptionsInit", "HtmlDetailsElement"] }
|
||||||
shared = { path = "../shared" }
|
shared = { path = "../shared", features = ["hydrate"] }
|
||||||
tailwind_fuse = "0.3.2"
|
tailwind_fuse = "0.3.2"
|
||||||
js-sys = "0.3.85"
|
js-sys = "0.3.85"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
serde-wasm-bindgen = "0.6.5"
|
serde-wasm-bindgen = "0.6.5"
|
||||||
leptos-use = "0.13"
|
leptos-use = "0.15"
|
||||||
codee = "0.2"
|
codee = "0.2"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
@@ -1,8 +1,32 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "shared"
|
name = "shared"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
ssr = [
|
||||||
|
"dep:tokio",
|
||||||
|
"dep:bytes",
|
||||||
|
"dep:thiserror",
|
||||||
|
"dep:quick-xml",
|
||||||
|
"dep:leptos_axum",
|
||||||
|
"leptos/ssr",
|
||||||
|
"leptos_router/ssr",
|
||||||
|
]
|
||||||
|
hydrate = ["leptos/hydrate"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
|
|
||||||
|
# Leptos 0.8.7
|
||||||
|
leptos = { version = "0.8.7", features = ["nightly"] }
|
||||||
|
leptos_router = { version = "0.8.7", features = ["nightly"] }
|
||||||
|
leptos_axum = { version = "0.8.7", optional = true }
|
||||||
|
|
||||||
|
# SSR Dependencies (XML-RPC & SCGI)
|
||||||
|
tokio = { version = "1", features = ["full"], optional = true }
|
||||||
|
bytes = { version = "1", optional = true }
|
||||||
|
thiserror = { version = "2", optional = true }
|
||||||
|
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod scgi;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod xmlrpc;
|
||||||
|
|
||||||
|
pub mod server_fns;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
|
||||||
pub struct Torrent {
|
pub struct Torrent {
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "ssr")]
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -94,15 +96,33 @@ pub async fn send_request(socket_path: &str, request: ScgiRequest) -> Result<Byt
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| ScgiError::Timeout)??;
|
.map_err(|_| ScgiError::Timeout)??;
|
||||||
|
|
||||||
let double_newline = b"\r\n\r\n";
|
|
||||||
let mut response_vec = response;
|
let mut response_vec = response;
|
||||||
if let Some(pos) = response_vec
|
|
||||||
.windows(double_newline.len())
|
// Improved header stripping: find the first occurrence of "<?xml" OR double newline
|
||||||
.position(|window| window == double_newline)
|
let patterns = [
|
||||||
{
|
&b"\r\n\r\n"[..],
|
||||||
Ok(Bytes::from(
|
&b"\n\n"[..],
|
||||||
response_vec.split_off(pos + double_newline.len()),
|
&b"<?xml"[..] // If headers are missing or weird, find start of XML
|
||||||
))
|
];
|
||||||
|
|
||||||
|
let mut found_pos = None;
|
||||||
|
for (i, pattern) in patterns.iter().enumerate() {
|
||||||
|
if let Some(pos) = response_vec
|
||||||
|
.windows(pattern.len())
|
||||||
|
.position(|window| window == *pattern)
|
||||||
|
{
|
||||||
|
// For XML pattern, we keep it. For newlines, we skip them.
|
||||||
|
if i == 2 {
|
||||||
|
found_pos = Some(pos);
|
||||||
|
} else {
|
||||||
|
found_pos = Some(pos + pattern.len());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = found_pos {
|
||||||
|
Ok(Bytes::from(response_vec.split_off(pos)))
|
||||||
} else {
|
} else {
|
||||||
Ok(Bytes::from(response_vec))
|
Ok(Bytes::from(response_vec))
|
||||||
}
|
}
|
||||||
26
shared/src/server_fns/mod.rs
Normal file
26
shared/src/server_fns/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use leptos::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
use crate::xmlrpc::{self, RtorrentClient};
|
||||||
|
|
||||||
|
#[server(GetVersion, "/api/server_fns")]
|
||||||
|
pub async fn get_version() -> Result<String, ServerFnError> {
|
||||||
|
let socket_path = std::env::var("RTORRENT_SOCKET").unwrap_or_else(|_| "/tmp/rtorrent.sock".to_string());
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
{
|
||||||
|
let client = RtorrentClient::new(&socket_path);
|
||||||
|
match client.call("system.client_version", &[]).await {
|
||||||
|
Ok(xml) => {
|
||||||
|
let version = xmlrpc::parse_string_response(&xml).unwrap_or(xml);
|
||||||
|
Ok(version)
|
||||||
|
},
|
||||||
|
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
{
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "ssr")]
|
||||||
|
|
||||||
use crate::scgi::{send_request, ScgiError, ScgiRequest};
|
use crate::scgi::{send_request, ScgiError, ScgiRequest};
|
||||||
use quick_xml::de::from_str;
|
use quick_xml::de::from_str;
|
||||||
use quick_xml::se::to_string;
|
use quick_xml::se::to_string;
|
||||||
Reference in New Issue
Block a user