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:
@@ -11,6 +11,8 @@ ssr = [
|
||||
"dep:thiserror",
|
||||
"dep:quick-xml",
|
||||
"dep:leptos_axum",
|
||||
"dep:sqlx",
|
||||
"dep:anyhow",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
@@ -29,4 +31,8 @@ leptos_axum = { version = "0.8.7", optional = true }
|
||||
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 }
|
||||
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 }
|
||||
16
shared/migrations/001_init.sql
Normal file
16
shared/migrations/001_init.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- 001_init.sql
|
||||
-- Initial schema for users and sessions
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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)
|
||||
);
|
||||
13
shared/migrations/002_push_subscriptions.sql
Normal file
13
shared/migrations/002_push_subscriptions.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- 002_push_subscriptions.sql
|
||||
-- Push notification subscriptions storage
|
||||
|
||||
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
endpoint TEXT NOT NULL UNIQUE,
|
||||
p256dh TEXT NOT NULL,
|
||||
auth TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Index for faster lookups by endpoint
|
||||
CREATE INDEX IF NOT EXISTS idx_push_subscriptions_endpoint ON push_subscriptions(endpoint);
|
||||
148
shared/src/db.rs
Normal file
148
shared/src/db.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite, Row, sqlite::SqliteConnectOptions};
|
||||
use std::time::Duration;
|
||||
use anyhow::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Db {
|
||||
pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub async fn new(db_url: &str) -> Result<Self> {
|
||||
let options = SqliteConnectOptions::from_str(db_url)?
|
||||
.create_if_missing(true)
|
||||
.busy_timeout(Duration::from_secs(10)) // Bekleme süresini 10 saniyeye çıkardık
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(sqlx::sqlite::SqliteSynchronous::Normal);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.acquire_timeout(Duration::from_secs(10))
|
||||
.connect_with(options)
|
||||
.await?;
|
||||
|
||||
let db = Self { pool };
|
||||
db.run_migrations().await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
async fn run_migrations(&self) -> Result<()> {
|
||||
sqlx::migrate!("./migrations").run(&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<Option<(i64, String)>> {
|
||||
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 get_username_by_id(&self, id: i64) -> Result<Option<String>> {
|
||||
let row = sqlx::query("SELECT username FROM users WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(row.map(|r| r.get(0)))
|
||||
}
|
||||
|
||||
pub async fn has_users(&self) -> Result<bool> {
|
||||
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<Option<i64>> {
|
||||
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(())
|
||||
}
|
||||
|
||||
pub async fn update_password(&self, user_id: i64, password_hash: &str) -> Result<()> {
|
||||
sqlx::query("UPDATE users SET password_hash = ? WHERE id = ?")
|
||||
.bind(password_hash)
|
||||
.bind(user_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_sessions_for_user(&self, user_id: i64) -> Result<()> {
|
||||
sqlx::query("DELETE FROM sessions WHERE user_id = ?")
|
||||
.bind(user_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Push Subscription Operations ---
|
||||
|
||||
pub async fn save_push_subscription(&self, endpoint: &str, p256dh: &str, auth: &str) -> Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO push_subscriptions (endpoint, p256dh, auth) VALUES (?, ?, ?)
|
||||
ON CONFLICT(endpoint) DO UPDATE SET p256dh = EXCLUDED.p256dh, auth = EXCLUDED.auth"
|
||||
)
|
||||
.bind(endpoint)
|
||||
.bind(p256dh)
|
||||
.bind(auth)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_push_subscription(&self, endpoint: &str) -> Result<()> {
|
||||
sqlx::query("DELETE FROM push_subscriptions WHERE endpoint = ?")
|
||||
.bind(endpoint)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_all_push_subscriptions(&self) -> Result<Vec<(String, String, String)>> {
|
||||
let rows = sqlx::query_as::<_, (String, String, String)>(
|
||||
"SELECT endpoint, p256dh, auth FROM push_subscriptions"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
Ok(rows)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ pub mod scgi;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod xmlrpc;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod db;
|
||||
|
||||
pub mod server_fns;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -14,6 +17,12 @@ pub struct ServerContext {
|
||||
pub scgi_socket_path: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[derive(Clone)]
|
||||
pub struct DbContext {
|
||||
pub db: db::Db,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
|
||||
pub struct Torrent {
|
||||
pub hash: String,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod torrent;
|
||||
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