diff --git a/Cargo.lock b/Cargo.lock index fa58223..03818c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1834,6 +1834,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror 1.0.69", +] + [[package]] name = "futf" version = "0.1.5" @@ -1915,6 +1925,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.32" @@ -2058,6 +2074,29 @@ dependencies = [ "web-sys", ] +[[package]] +name = "governor" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be93b4ec2e4710b04d9264c0c7350cdd62a8c20e5e4ac732552ebb8f0debe8eb" +dependencies = [ + "cfg-if", + "dashmap", + "futures-sink", + "futures-timer", + "futures-util", + "getrandom 0.3.4", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.9.4", + "smallvec", + "spinning_top", + "web-time", +] + [[package]] name = "h2" version = "0.4.14" @@ -3028,6 +3067,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "no_std_io2" version = "0.9.4" @@ -3046,6 +3091,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -3544,6 +3601,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -3761,6 +3833,15 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -4245,6 +4326,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4731,6 +4821,22 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tower_governor" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e6672c7510df74859726427edea641674dad1aeeb30057b87335b1ba23b843" +dependencies = [ + "axum", + "forwarded-header-value", + "governor", + "http", + "pin-project", + "thiserror 2.0.18", + "tower", + "tracing", +] + [[package]] name = "tracing" version = "0.1.44" @@ -5236,6 +5342,22 @@ dependencies = [ "web-sys", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -5245,6 +5367,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -5701,6 +5829,7 @@ dependencies = [ "tokio", "tokio-postgres", "tower-http", + "tower_governor", "tracing", "tracing-subscriber", "uuid", diff --git a/src/api/mod.rs b/src/api/mod.rs index 46e77d3..bdfe6f4 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,6 @@ pub mod auth; pub mod image; pub mod posts; +pub mod tags; pub mod upload; pub mod utils; diff --git a/src/api/posts.rs b/src/api/posts.rs index fb01551..c848baf 100644 --- a/src/api/posts.rs +++ b/src/api/posts.rs @@ -2,6 +2,8 @@ use dioxus::prelude::*; +#[cfg(feature = "server")] +use crate::api::tags::get_post_tags; #[cfg(feature = "server")] use crate::api::utils::{db_conn_error, query_error, tx_error}; #[cfg(feature = "server")] @@ -386,92 +388,6 @@ fn slugify_heading(text: &str) -> String { slug } -// ============================================================================ -// Tag helpers -// ============================================================================ - -#[cfg(feature = "server")] -#[allow(dead_code)] -async fn set_post_tags( - client: &tokio_postgres::Client, - post_id: i32, - tags: &[String], -) -> Result<(), ServerFnError> { - // Remove existing tags - client - .execute("DELETE FROM post_tags WHERE post_id = $1", &[&post_id]) - .await - .map_err(|e| { - tracing::error!("delete tag links failed: {:?}", e); - ServerFnError::new(format!("删除标签关联失败: {}", e)) - })?; - - for tag_name in tags { - let tag_name = tag_name.trim(); - if tag_name.is_empty() { - continue; - } - - // Insert or get tag - let tag_id: i32 = { - let row = client - .query_opt( - "INSERT INTO tags (name) VALUES ($1) ON CONFLICT (name) DO NOTHING RETURNING id", - &[&tag_name], - ) - .await - .map_err(|e| { - tracing::error!("create tag failed: {:?}", e); - ServerFnError::new(format!("创建标签失败: {}", e)) - })?; - - match row { - Some(r) => r.get(0), - None => { - // Tag already exists, fetch its id - let row = client - .query_opt("SELECT id FROM tags WHERE name = $1", &[&tag_name]) - .await - .map_err(|e| { - tracing::error!("query tag failed: {:?}", e); - ServerFnError::new(format!("查询标签失败: {}", e)) - })?; - row.map(|r| r.get(0)) - .ok_or_else(|| ServerFnError::new(format!("标签不存在: {}", tag_name)))? - } - } - }; - - client - .execute( - "INSERT INTO post_tags (post_id, tag_id) VALUES ($1, $2)", - &[&post_id, &tag_id], - ) - .await - .map_err(|e| { - tracing::error!("link tag failed: {:?}", e); - ServerFnError::new(format!("关联标签失败: {}", e)) - })?; - } - - Ok(()) -} - -#[cfg(feature = "server")] -async fn get_post_tags(client: &tokio_postgres::Client, post_id: i32) -> Vec { - let rows = client - .query( - "SELECT t.name FROM tags t JOIN post_tags pt ON t.id = pt.tag_id WHERE pt.post_id = $1 ORDER BY t.name", - &[&post_id], - ) - .await; - - match rows { - Ok(rows) => rows.iter().map(|r| r.get(0)).collect(), - Err(_) => vec![], - } -} - // ============================================================================ // Row to Post conversion // ============================================================================