refactor: move DB to shared crate, convert push endpoints to server functions, remove dead REST handlers
All checks were successful
Build MIPS Binary / build (push) Successful in 5m17s
All checks were successful
Build MIPS Binary / build (push) Successful in 5m17s
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -330,7 +330,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shared",
|
"shared",
|
||||||
"sqlx",
|
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -3651,12 +3650,14 @@ dependencies = [
|
|||||||
name = "shared"
|
name = "shared"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"leptos",
|
"leptos",
|
||||||
"leptos_axum",
|
"leptos_axum",
|
||||||
"leptos_router",
|
"leptos_router",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sqlx",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"utoipa",
|
"utoipa",
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ 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 }
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
|
||||||
bcrypt = "0.17.0"
|
bcrypt = "0.17.0"
|
||||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|||||||
@@ -49,21 +49,3 @@ pub async fn handle_timeout_error(err: BoxError) -> (StatusCode, &'static str) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "push-notifications")]
|
|
||||||
pub async fn get_push_public_key_handler(
|
|
||||||
axum::extract::State(state): axum::extract::State<crate::AppState>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
let public_key = state.push_store.get_public_key();
|
|
||||||
(StatusCode::OK, axum::extract::Json(serde_json::json!({ "publicKey": public_key }))).into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "push-notifications")]
|
|
||||||
pub async fn subscribe_push_handler(
|
|
||||||
axum::extract::State(state): axum::extract::State<crate::AppState>,
|
|
||||||
axum::extract::Json(subscription): axum::extract::Json<crate::push::PushSubscription>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
tracing::info!("Received push subscription: {:?}", subscription);
|
|
||||||
state.push_store.add_subscription(subscription).await;
|
|
||||||
(StatusCode::OK, "Subscription saved").into_response()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
mod db;
|
|
||||||
mod diff;
|
mod diff;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
#[cfg(feature = "push-notifications")]
|
#[cfg(feature = "push-notifications")]
|
||||||
@@ -42,7 +41,7 @@ pub struct AppState {
|
|||||||
pub tx: Arc<watch::Sender<Vec<Torrent>>>,
|
pub tx: Arc<watch::Sender<Vec<Torrent>>>,
|
||||||
pub event_bus: broadcast::Sender<AppEvent>,
|
pub event_bus: broadcast::Sender<AppEvent>,
|
||||||
pub scgi_socket_path: String,
|
pub scgi_socket_path: String,
|
||||||
pub db: db::Db,
|
pub db: shared::db::Db,
|
||||||
#[cfg(feature = "push-notifications")]
|
#[cfg(feature = "push-notifications")]
|
||||||
pub push_store: push::PushSubscriptionStore,
|
pub push_store: push::PushSubscriptionStore,
|
||||||
pub notify_poll: Arc<tokio::sync::Notify>,
|
pub notify_poll: Arc<tokio::sync::Notify>,
|
||||||
@@ -103,46 +102,6 @@ struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "swagger")]
|
#[cfg(feature = "swagger")]
|
||||||
#[cfg(feature = "push-notifications")]
|
|
||||||
#[derive(OpenApi)]
|
|
||||||
#[openapi(
|
|
||||||
paths(
|
|
||||||
handlers::get_push_public_key_handler,
|
|
||||||
handlers::subscribe_push_handler,
|
|
||||||
handlers::auth::login_handler,
|
|
||||||
handlers::auth::logout_handler,
|
|
||||||
handlers::auth::check_auth_handler,
|
|
||||||
handlers::setup::setup_handler,
|
|
||||||
handlers::setup::get_setup_status_handler
|
|
||||||
),
|
|
||||||
components(
|
|
||||||
schemas(
|
|
||||||
shared::AddTorrentRequest,
|
|
||||||
shared::TorrentActionRequest,
|
|
||||||
shared::Torrent,
|
|
||||||
shared::TorrentStatus,
|
|
||||||
shared::TorrentFile,
|
|
||||||
shared::TorrentPeer,
|
|
||||||
shared::TorrentTracker,
|
|
||||||
shared::SetFilePriorityRequest,
|
|
||||||
shared::SetLabelRequest,
|
|
||||||
shared::GlobalLimitRequest,
|
|
||||||
push::PushSubscription,
|
|
||||||
push::PushKeys,
|
|
||||||
handlers::auth::LoginRequest,
|
|
||||||
handlers::setup::SetupRequest,
|
|
||||||
handlers::setup::SetupStatusResponse,
|
|
||||||
handlers::auth::UserResponse
|
|
||||||
)
|
|
||||||
),
|
|
||||||
tags(
|
|
||||||
(name = "vibetorrent", description = "VibeTorrent API")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
struct ApiDoc;
|
|
||||||
|
|
||||||
#[cfg(feature = "swagger")]
|
|
||||||
#[cfg(not(feature = "push-notifications"))]
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(
|
#[openapi(
|
||||||
paths(
|
paths(
|
||||||
@@ -206,7 +165,7 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let db: db::Db = match db::Db::new(&args.db_url).await {
|
let db: shared::db::Db = match shared::db::Db::new(&args.db_url).await {
|
||||||
Ok(db) => db,
|
Ok(db) => db,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Failed to connect to database: {}", e);
|
tracing::error!("Failed to connect to database: {}", e);
|
||||||
@@ -470,6 +429,7 @@ async fn main() {
|
|||||||
|
|
||||||
// Setup & Auth Routes (cookie-based, stay as REST)
|
// Setup & Auth Routes (cookie-based, stay as REST)
|
||||||
let scgi_path_for_ctx = args.socket.clone();
|
let scgi_path_for_ctx = args.socket.clone();
|
||||||
|
let db_for_ctx = db.clone();
|
||||||
let app = app
|
let app = app
|
||||||
.route("/api/setup/status", get(handlers::setup::get_setup_status_handler))
|
.route("/api/setup/status", get(handlers::setup::get_setup_status_handler))
|
||||||
.route("/api/setup", post(handlers::setup::setup_handler))
|
.route("/api/setup", post(handlers::setup::setup_handler))
|
||||||
@@ -484,12 +444,18 @@ async fn main() {
|
|||||||
.route("/api/events", get(sse::sse_handler))
|
.route("/api/events", get(sse::sse_handler))
|
||||||
.route("/api/server_fns/{*fn_name}", post({
|
.route("/api/server_fns/{*fn_name}", post({
|
||||||
let scgi_path = scgi_path_for_ctx.clone();
|
let scgi_path = scgi_path_for_ctx.clone();
|
||||||
|
let db = db_for_ctx.clone();
|
||||||
move |req: Request<Body>| {
|
move |req: Request<Body>| {
|
||||||
|
let scgi_path = scgi_path.clone();
|
||||||
|
let db = db.clone();
|
||||||
leptos_axum::handle_server_fns_with_context(
|
leptos_axum::handle_server_fns_with_context(
|
||||||
move || {
|
move || {
|
||||||
leptos::context::provide_context(shared::ServerContext {
|
leptos::context::provide_context(shared::ServerContext {
|
||||||
scgi_socket_path: scgi_path.clone(),
|
scgi_socket_path: scgi_path.clone(),
|
||||||
});
|
});
|
||||||
|
leptos::context::provide_context(shared::DbContext {
|
||||||
|
db: db.clone(),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
)
|
)
|
||||||
@@ -497,11 +463,6 @@ async fn main() {
|
|||||||
}))
|
}))
|
||||||
.fallback(handlers::static_handler);
|
.fallback(handlers::static_handler);
|
||||||
|
|
||||||
#[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
|
let app = app
|
||||||
.layer(middleware::from_fn_with_state(app_state.clone(), auth_middleware))
|
.layer(middleware::from_fn_with_state(app_state.clone(), auth_middleware))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use web_push::{
|
|||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
use crate::db::Db;
|
use shared::db::Db;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct PushSubscription {
|
pub struct PushSubscription {
|
||||||
|
|||||||
@@ -142,25 +142,21 @@ pub mod settings {
|
|||||||
|
|
||||||
pub mod push {
|
pub mod push {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::store::PushSubscriptionData;
|
|
||||||
|
|
||||||
pub async fn get_public_key() -> Result<String, ApiError> {
|
pub async fn get_public_key() -> Result<String, ApiError> {
|
||||||
let resp = Request::get(&format!("{}/push/public-key", base_url()))
|
shared::server_fns::push::get_public_key()
|
||||||
.send()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ApiError::Network)?;
|
.map_err(|e| ApiError::ServerFn(e.to_string()))
|
||||||
let key = resp.text().await.map_err(|_| ApiError::Network)?;
|
|
||||||
Ok(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn subscribe(req: &PushSubscriptionData) -> Result<(), ApiError> {
|
pub async fn subscribe(endpoint: &str, p256dh: &str, auth: &str) -> Result<(), ApiError> {
|
||||||
Request::post(&format!("{}/push/subscribe", base_url()))
|
shared::server_fns::push::subscribe_push(
|
||||||
.json(req)
|
endpoint.to_string(),
|
||||||
.map_err(|_| ApiError::Network)?
|
p256dh.to_string(),
|
||||||
.send()
|
auth.to_string(),
|
||||||
.await
|
)
|
||||||
.map_err(|_| ApiError::Network)?;
|
.await
|
||||||
Ok(())
|
.map_err(|e| ApiError::ServerFn(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,18 +55,6 @@ pub fn get_action_messages(action: &str) -> (&'static str, &'static str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct PushSubscriptionData {
|
|
||||||
pub endpoint: String,
|
|
||||||
pub keys: PushKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct PushKeys {
|
|
||||||
pub p256dh: String,
|
|
||||||
pub auth: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum FilterStatus {
|
pub enum FilterStatus {
|
||||||
All, Downloading, Seeding, Completed, Paused, Inactive, Active, Error,
|
All, Downloading, Seeding, Completed, Paused, Inactive, Active, Error,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ ssr = [
|
|||||||
"dep:thiserror",
|
"dep:thiserror",
|
||||||
"dep:quick-xml",
|
"dep:quick-xml",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
|
"dep:sqlx",
|
||||||
|
"dep:anyhow",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
]
|
]
|
||||||
@@ -30,3 +32,7 @@ tokio = { version = "1", features = ["full"], optional = true }
|
|||||||
bytes = { version = "1", optional = true }
|
bytes = { version = "1", optional = true }
|
||||||
thiserror = { version = "2", optional = true }
|
thiserror = { version = "2", optional = true }
|
||||||
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
||||||
|
|
||||||
|
# Database
|
||||||
|
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"], optional = true }
|
||||||
|
anyhow = { version = "1.0", optional = true }
|
||||||
@@ -7,6 +7,9 @@ pub mod scgi;
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod xmlrpc;
|
pub mod xmlrpc;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod db;
|
||||||
|
|
||||||
pub mod server_fns;
|
pub mod server_fns;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -14,6 +17,12 @@ pub struct ServerContext {
|
|||||||
pub scgi_socket_path: String,
|
pub scgi_socket_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DbContext {
|
||||||
|
pub db: db::Db,
|
||||||
|
}
|
||||||
|
|
||||||
#[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,2 +1,3 @@
|
|||||||
pub mod torrent;
|
pub mod torrent;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod push;
|
||||||
|
|||||||
22
shared/src/server_fns/push.rs
Normal file
22
shared/src/server_fns/push.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[server(GetPushPublicKey, "/api/server_fns")]
|
||||||
|
pub async fn get_public_key() -> Result<String, ServerFnError> {
|
||||||
|
let key = std::env::var("VAPID_PUBLIC_KEY")
|
||||||
|
.map_err(|_| ServerFnError::new("VAPID_PUBLIC_KEY not configured"))?;
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(SubscribePush, "/api/server_fns")]
|
||||||
|
pub async fn subscribe_push(
|
||||||
|
endpoint: String,
|
||||||
|
p256dh: String,
|
||||||
|
auth: String,
|
||||||
|
) -> Result<(), ServerFnError> {
|
||||||
|
let db_ctx = expect_context::<crate::DbContext>();
|
||||||
|
db_ctx
|
||||||
|
.db
|
||||||
|
.save_push_subscription(&endpoint, &p256dh, &auth)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ServerFnError::new(format!("Failed to save subscription: {}", e)))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user