From 294d60afabc753552bd24bace3695f1b7aa138df Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 12 Jun 2026 17:14:31 +0800 Subject: [PATCH] style: format rust code --- src/api/auth.rs | 12 +- src/api/comments/check.rs | 7 +- src/api/comments/create.rs | 27 ++-- src/api/comments/helpers.rs | 3 +- src/api/comments/list.rs | 22 ++- src/api/comments/markdown.rs | 20 ++- src/api/comments/mod.rs | 31 +++-- src/api/comments/read.rs | 16 +-- src/api/comments/types.rs | 2 +- src/api/comments/update.rs | 18 +-- src/api/error.rs | 5 +- src/api/image.rs | 118 +++++++++++++---- src/api/markdown.rs | 10 +- src/api/posts/list.rs | 38 ++++-- src/api/posts/mod.rs | 33 +++-- src/api/posts/search.rs | 10 +- src/api/posts/tags.rs | 4 +- src/api/posts/update.rs | 3 +- src/api/rate_limit.rs | 18 +-- src/api/sanitizer.rs | 125 +++++++++++++++--- src/api/upload.rs | 8 +- src/bin/generate_highlight_css.rs | 3 +- src/cache.rs | 65 ++++++--- src/components/admin_layout.rs | 3 +- src/components/admin_skeleton.rs | 2 - src/components/comments/form.rs | 2 +- src/components/comments/item.rs | 4 +- src/components/comments/list.rs | 20 ++- src/components/comments/mod.rs | 10 +- src/components/comments/pending_item.rs | 3 +- src/components/forms.rs | 16 ++- src/components/image_viewer.rs | 15 ++- src/components/post/post_header.rs | 2 +- src/components/skeletons/archive_skeleton.rs | 2 +- src/components/skeletons/comment_skeleton.rs | 2 +- src/components/skeletons/delayed_skeleton.rs | 2 +- src/components/skeletons/mod.rs | 2 +- .../skeletons/post_detail_skeleton.rs | 2 +- src/components/write_skeleton.rs | 2 +- src/db/pool.rs | 17 ++- src/highlight.rs | 2 +- src/hooks/delayed_loading.rs | 2 +- src/hooks/mod.rs | 2 +- src/main.rs | 13 +- src/models/post.rs | 14 +- src/models/user.rs | 5 +- src/pages/admin/comments.rs | 47 +++++-- src/pages/admin/posts.rs | 5 +- src/pages/admin/write.rs | 30 +++-- src/pages/archives.rs | 2 +- src/pages/tags.rs | 2 +- src/router.rs | 4 +- src/utils/mod.rs | 2 +- src/utils/text.rs | 39 +++--- src/webp.rs | 5 +- 55 files changed, 582 insertions(+), 296 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index d2e3c9f..e41e78f 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -4,11 +4,11 @@ use dioxus::prelude::*; #[cfg(feature = "server")] use http::header::{HeaderValue, SET_COOKIE}; -use crate::auth::{password, session}; -#[cfg(feature = "server")] -use crate::auth::session::get_session_from_ctx; #[cfg(feature = "server")] use crate::api::error::AppError; +#[cfg(feature = "server")] +use crate::auth::session::get_session_from_ctx; +use crate::auth::{password, session}; use crate::db::pool::get_conn; use crate::models::user::{PublicUser, User, UserRole}; @@ -106,7 +106,8 @@ pub async fn register( }); } - let password_hash = password::hash_password(&password).map_err(|_| AppError::Internal("密码处理失败"))?; + let password_hash = + password::hash_password(&password).map_err(|_| AppError::Internal("密码处理失败"))?; let result = client .query_one( @@ -176,7 +177,8 @@ pub async fn login(username: String, password: String) -> Result) -> Result, ServerFnError> { #[cfg(feature = "server")] { - use crate::db::pool::get_conn; use crate::api::error::AppError; + use crate::db::pool::get_conn; if ids.is_empty() { return Ok(vec![]); @@ -35,7 +35,10 @@ pub async fn check_pending_status(ids: Vec) -> Result = ids .into_iter() .map(|id| { - let status = found.get(&id).cloned().unwrap_or_else(|| "gone".to_string()); + let status = found + .get(&id) + .cloned() + .unwrap_or_else(|| "gone".to_string()); PendingStatusItem { id, status } }) .collect(); diff --git a/src/api/comments/create.rs b/src/api/comments/create.rs index 71fd837..fa31cb0 100644 --- a/src/api/comments/create.rs +++ b/src/api/comments/create.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::api::comments::types::*; +use dioxus::prelude::*; #[server(CreateComment, "/api")] pub async fn create_comment( @@ -12,13 +12,13 @@ pub async fn create_comment( ) -> Result { #[cfg(feature = "server")] { + use crate::api::comments::helpers::{ + compute_content_hash, validate_comment_content, validate_comment_email, + validate_comment_name, validate_comment_url, + }; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::comments::helpers::{ - validate_comment_name, validate_comment_email, validate_comment_url, - validate_comment_content, compute_content_hash, - }; if let Some(ctx) = dioxus::fullstack::FullstackContext::current() { let parts = ctx.parts_mut(); @@ -177,12 +177,7 @@ pub async fn create_comment( } } - let content_hash = compute_content_hash( - post_id, - parent_id, - &author_name, - &content_md, - ); + let content_hash = compute_content_hash(post_id, parent_id, &author_name, &content_md); let dup: Option = client .query_opt( @@ -215,7 +210,8 @@ pub async fn create_comment( let user_agent = if let Some(ctx) = dioxus::fullstack::FullstackContext::current() { let parts = ctx.parts_mut(); - parts.headers + parts + .headers .get("user-agent") .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()) @@ -236,7 +232,10 @@ pub async fn create_comment( &depth, &author_name.trim(), &author_email.trim(), - &author_url.as_ref().map(|u| u.trim()).filter(|u| !u.is_empty()), + &author_url + .as_ref() + .map(|u| u.trim()) + .filter(|u| !u.is_empty()), &content_md, &content_html, &content_hash, diff --git a/src/api/comments/helpers.rs b/src/api/comments/helpers.rs index 03b3fbc..c98f213 100644 --- a/src/api/comments/helpers.rs +++ b/src/api/comments/helpers.rs @@ -89,8 +89,7 @@ pub fn validate_comment_name(name: &str) -> Result<(), String> { #[allow(dead_code)] pub fn validate_comment_email(email: &str) -> Result<(), String> { - let re = - regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); + let re = regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); if !re.is_match(email.trim()) { return Err("邮箱格式不正确".to_string()); } diff --git a/src/api/comments/list.rs b/src/api/comments/list.rs index 9be6337..5f06e57 100644 --- a/src/api/comments/list.rs +++ b/src/api/comments/list.rs @@ -1,16 +1,14 @@ -use dioxus::prelude::*; use crate::api::comments::types::*; +use dioxus::prelude::*; #[server(GetPendingComments, "/api")] -pub async fn get_pending_comments( - page: i32, -) -> Result { +pub async fn get_pending_comments(page: i32) -> Result { #[cfg(feature = "server")] { - use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::comments::helpers::row_to_admin_comment; use crate::api::auth::get_current_admin_user; + use crate::api::comments::helpers::row_to_admin_comment; + use crate::api::error::AppError; + use crate::db::pool::get_conn; let _admin = get_current_admin_user().await?; @@ -54,10 +52,10 @@ pub async fn get_pending_comments( pub async fn get_pending_count() -> Result { #[cfg(feature = "server")] { + use crate::api::auth::get_current_admin_user; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::auth::get_current_admin_user; let _admin = get_current_admin_user().await?; @@ -91,10 +89,10 @@ pub async fn get_all_comments( ) -> Result { #[cfg(feature = "server")] { - use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::comments::helpers::row_to_admin_comment; use crate::api::auth::get_current_admin_user; + use crate::api::comments::helpers::row_to_admin_comment; + use crate::api::error::AppError; + use crate::db::pool::get_conn; let _admin = get_current_admin_user().await?; diff --git a/src/api/comments/markdown.rs b/src/api/comments/markdown.rs index d3388f5..d7be19b 100644 --- a/src/api/comments/markdown.rs +++ b/src/api/comments/markdown.rs @@ -36,9 +36,7 @@ pub fn render_comment_markdown(md: &str) -> String { Event::Start(Tag::CodeBlock(kind)) => { in_codeblock = true; code_lang = match kind { - CodeBlockKind::Fenced(lang) if !lang.is_empty() => { - Some(lang.to_string()) - } + CodeBlockKind::Fenced(lang) if !lang.is_empty() => Some(lang.to_string()), _ => None, }; code_buffer.clear(); @@ -83,10 +81,19 @@ mod tests { #[test] fn render_comment_heading_all_levels() { for md in &[ - "# H1", "## H2", "### H3", "#### H4", "##### H5", "###### H6", + "# H1", + "## H2", + "### H3", + "#### H4", + "##### H5", + "###### H6", ] { let result = render_comment_markdown(md); - assert!(result.contains(""), "heading not converted for: {}", md); + assert!( + result.contains(""), + "heading not converted for: {}", + md + ); } } @@ -162,7 +169,8 @@ mod tests { #[test] fn clean_comment_html_removes_details_summary() { - let result = clean_comment_html("
Click

Content

"); + let result = + clean_comment_html("
Click

Content

"); assert!(!result.contains("details")); assert!(!result.contains("summary")); } diff --git a/src/api/comments/mod.rs b/src/api/comments/mod.rs index 42730fc..d3f5805 100644 --- a/src/api/comments/mod.rs +++ b/src/api/comments/mod.rs @@ -1,20 +1,25 @@ -#![allow(clippy::unused_unit, deprecated, unused_imports, clippy::too_many_arguments)] +#![allow( + clippy::unused_unit, + deprecated, + unused_imports, + clippy::too_many_arguments +)] -mod types; -mod helpers; -mod markdown; -mod create; -mod read; -mod update; -mod list; mod check; +mod create; +mod helpers; +mod list; +mod markdown; +mod read; +mod types; +mod update; -pub use types::*; -pub use create::create_comment; -pub use read::{get_comments, get_comment_count}; -pub use update::{approve_comment, spam_comment, trash_comment, batch_update_comment_status}; -pub use list::{get_pending_comments, get_pending_count, get_all_comments}; pub use check::check_pending_status; +pub use create::create_comment; +pub use list::{get_all_comments, get_pending_comments, get_pending_count}; +pub use read::{get_comment_count, get_comments}; +pub use types::*; +pub use update::{approve_comment, batch_update_comment_status, spam_comment, trash_comment}; #[cfg(feature = "server")] pub use markdown::render_comment_markdown; diff --git a/src/api/comments/read.rs b/src/api/comments/read.rs index baefeb4..3970d98 100644 --- a/src/api/comments/read.rs +++ b/src/api/comments/read.rs @@ -1,16 +1,14 @@ -use dioxus::prelude::*; use crate::api::comments::types::*; +use dioxus::prelude::*; #[server(GetComments, "/api")] -pub async fn get_comments( - post_id: i32, -) -> Result { +pub async fn get_comments(post_id: i32) -> Result { #[cfg(feature = "server")] { - use crate::cache; - use crate::db::pool::get_conn; use crate::api::comments::helpers::row_to_public_comment; use crate::api::error::AppError; + use crate::cache; + use crate::db::pool::get_conn; if let Some(cached) = cache::get_comments_by_post(post_id).await { let count = cached.len() as i64; @@ -45,14 +43,12 @@ pub async fn get_comments( } #[server(GetCommentCount, "/api")] -pub async fn get_comment_count( - post_id: i32, -) -> Result { +pub async fn get_comment_count(post_id: i32) -> Result { #[cfg(feature = "server")] { + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; if let Some(cached) = cache::get_comment_count(post_id).await { return Ok(CommentCountResponse { count: cached }); diff --git a/src/api/comments/types.rs b/src/api/comments/types.rs index 8a11831..638450f 100644 --- a/src/api/comments/types.rs +++ b/src/api/comments/types.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use crate::models::comment::{AdminComment, PublicComment}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(dead_code)] diff --git a/src/api/comments/update.rs b/src/api/comments/update.rs index 6837579..d6f6dcd 100644 --- a/src/api/comments/update.rs +++ b/src/api/comments/update.rs @@ -1,14 +1,14 @@ -use dioxus::prelude::*; use crate::api::comments::types::*; +use dioxus::prelude::*; #[server(ApproveComment, "/api")] pub async fn approve_comment(id: i64) -> Result { #[cfg(feature = "server")] { + use crate::api::auth::get_current_admin_user; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::auth::get_current_admin_user; let _admin = get_current_admin_user().await?; @@ -79,10 +79,10 @@ pub async fn approve_comment(id: i64) -> Result pub async fn spam_comment(id: i64) -> Result { #[cfg(feature = "server")] { + use crate::api::auth::get_current_admin_user; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::auth::get_current_admin_user; let _admin = get_current_admin_user().await?; @@ -132,10 +132,10 @@ pub async fn spam_comment(id: i64) -> Result { pub async fn trash_comment(id: i64) -> Result { #[cfg(feature = "server")] { + use crate::api::auth::get_current_admin_user; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::auth::get_current_admin_user; let _admin = get_current_admin_user().await?; @@ -185,10 +185,10 @@ pub async fn batch_update_comment_status( ) -> Result { #[cfg(feature = "server")] { + use crate::api::auth::get_current_admin_user; + use crate::api::error::AppError; use crate::cache; use crate::db::pool::get_conn; - use crate::api::error::AppError; - use crate::api::auth::get_current_admin_user; let _admin = get_current_admin_user().await?; diff --git a/src/api/error.rs b/src/api/error.rs index c8c6319..db3c32d 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -63,7 +63,10 @@ mod tests { !msg.contains("5432"), "should not leak internal details: {msg}" ); - assert!(msg.contains("服务暂时不可用"), "expected generic message: {msg}"); + assert!( + msg.contains("服务暂时不可用"), + "expected generic message: {msg}" + ); } #[test] diff --git a/src/api/image.rs b/src/api/image.rs index c32a7c6..9c63938 100644 --- a/src/api/image.rs +++ b/src/api/image.rs @@ -266,7 +266,10 @@ async fn write_disk_cache(cache_key: &str, cached: &CachedImage) { tracing::warn!("Failed to create cache dir: {:?}", e); return; } - let ct_str = cached.content_type.to_str().unwrap_or("application/octet-stream"); + let ct_str = cached + .content_type + .to_str() + .unwrap_or("application/octet-stream"); if let Err(e) = tokio::fs::write(format!("{}.dat", base), &cached.data).await { tracing::warn!("Failed to write disk cache data: {:?}", e); } @@ -319,10 +322,15 @@ pub async fn serve_image( } if let Some(cached) = read_disk_cache(&cache_key).await { - let _ = IMAGE_CACHE.insert(cache_key.clone(), CachedImage { - data: cached.data.clone(), - content_type: cached.content_type.clone(), - }).await; + let _ = IMAGE_CACHE + .insert( + cache_key.clone(), + CachedImage { + data: cached.data.clone(), + content_type: cached.content_type.clone(), + }, + ) + .await; return ( StatusCode::OK, [(header::CONTENT_TYPE, cached.content_type)], @@ -388,77 +396,113 @@ mod tests { #[test] fn image_params_validate_valid_width() { - let params = ImageParams { w: Some(100), ..Default::default() }; + let params = ImageParams { + w: Some(100), + ..Default::default() + }; assert!(params.validate().is_ok()); } #[test] fn image_params_validate_zero_width_rejected() { - let params = ImageParams { w: Some(0), ..Default::default() }; + let params = ImageParams { + w: Some(0), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_oversized_width_rejected() { - let params = ImageParams { w: Some(5000), ..Default::default() }; + let params = ImageParams { + w: Some(5000), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_valid_rotation() { for angle in [0, 90, 180, 270] { - let params = ImageParams { rotate: Some(angle), ..Default::default() }; + let params = ImageParams { + rotate: Some(angle), + ..Default::default() + }; assert!(params.validate().is_ok(), "angle {} should be valid", angle); } } #[test] fn image_params_validate_invalid_rotation_rejected() { - let params = ImageParams { rotate: Some(45), ..Default::default() }; + let params = ImageParams { + rotate: Some(45), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_valid_format() { for fmt in &["jpeg", "jpg", "png", "webp", "JPEG", "PNG"] { - let params = ImageParams { format: Some(fmt.to_string()), ..Default::default() }; + let params = ImageParams { + format: Some(fmt.to_string()), + ..Default::default() + }; assert!(params.validate().is_ok(), "format {} should be valid", fmt); } } #[test] fn image_params_validate_invalid_format_rejected() { - let params = ImageParams { format: Some("gif".to_string()), ..Default::default() }; + let params = ImageParams { + format: Some("gif".to_string()), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_valid_thumbnail() { - let params = ImageParams { thumb: Some("200x150".to_string()), ..Default::default() }; + let params = ImageParams { + thumb: Some("200x150".to_string()), + ..Default::default() + }; assert!(params.validate().is_ok()); } #[test] fn image_params_validate_invalid_thumbnail_rejected() { - let params = ImageParams { thumb: Some("200".to_string()), ..Default::default() }; + let params = ImageParams { + thumb: Some("200".to_string()), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_valid_quality() { - let params = ImageParams { quality: Some(85), ..Default::default() }; + let params = ImageParams { + quality: Some(85), + ..Default::default() + }; assert!(params.validate().is_ok()); } #[test] fn image_params_validate_zero_quality_rejected() { - let params = ImageParams { quality: Some(0), ..Default::default() }; + let params = ImageParams { + quality: Some(0), + ..Default::default() + }; assert!(params.validate().is_err()); } #[test] fn image_params_validate_over_100_quality_rejected() { - let params = ImageParams { quality: Some(101), ..Default::default() }; + let params = ImageParams { + quality: Some(101), + ..Default::default() + }; assert!(params.validate().is_err()); } @@ -486,9 +530,18 @@ mod tests { #[test] fn detect_format_jpeg() { - assert!(matches!(detect_format("photo.jpg"), image::ImageFormat::Jpeg)); - assert!(matches!(detect_format("photo.jpeg"), image::ImageFormat::Jpeg)); - assert!(matches!(detect_format("PHOTO.JPG"), image::ImageFormat::Jpeg)); + assert!(matches!( + detect_format("photo.jpg"), + image::ImageFormat::Jpeg + )); + assert!(matches!( + detect_format("photo.jpeg"), + image::ImageFormat::Jpeg + )); + assert!(matches!( + detect_format("PHOTO.JPG"), + image::ImageFormat::Jpeg + )); } #[test] @@ -498,18 +551,30 @@ mod tests { #[test] fn detect_format_webp() { - assert!(matches!(detect_format("anim.webp"), image::ImageFormat::WebP)); + assert!(matches!( + detect_format("anim.webp"), + image::ImageFormat::WebP + )); } #[test] fn detect_format_defaults_to_jpeg() { - assert!(matches!(detect_format("file.xyz"), image::ImageFormat::Jpeg)); + assert!(matches!( + detect_format("file.xyz"), + image::ImageFormat::Jpeg + )); } #[test] fn cache_key_differs_for_different_params() { - let p1 = ImageParams { w: Some(100), ..Default::default() }; - let p2 = ImageParams { w: Some(200), ..Default::default() }; + let p1 = ImageParams { + w: Some(100), + ..Default::default() + }; + let p2 = ImageParams { + w: Some(200), + ..Default::default() + }; assert_ne!(p1.cache_key("img.jpg"), p2.cache_key("img.jpg")); } @@ -521,7 +586,10 @@ mod tests { #[test] fn is_empty_false_when_any_set() { - let params = ImageParams { w: Some(100), ..Default::default() }; + let params = ImageParams { + w: Some(100), + ..Default::default() + }; assert!(!params.is_empty()); } diff --git a/src/api/markdown.rs b/src/api/markdown.rs index 0d62430..5f7ca34 100644 --- a/src/api/markdown.rs +++ b/src/api/markdown.rs @@ -204,9 +204,7 @@ fn generate_toc_html(headings: &[(u8, String, String)]) -> String { let clean_text = clean_html(text); html.push_str(&format!( "
  • {}", - id, - clean_text, - clean_text + id, clean_text, clean_text )); } @@ -367,7 +365,11 @@ mod tests { #[test] fn render_markdown_data_uri_image() { let result = render_markdown_enhanced("![alt](data:image/svg+xml,%3csvg%3e%3c/svg%3e)"); - assert!(result.html.contains("data:image/svg+xml"), "data URI should be preserved in img src, got: {}", result.html); + assert!( + result.html.contains("data:image/svg+xml"), + "data URI should be preserved in img src, got: {}", + result.html + ); assert!(result.html.contains("alt=\"alt\"")); } } diff --git a/src/api/posts/list.rs b/src/api/posts/list.rs index e4c9259..6549e47 100644 --- a/src/api/posts/list.rs +++ b/src/api/posts/list.rs @@ -16,7 +16,10 @@ pub async fn list_published_posts( { let cache_key = crate::cache::CacheKey::PublishedPosts { page, per_page }; if let Some((cached_posts, cached_total)) = crate::cache::get_post_list(&cache_key).await { - return Ok(PostListResponse { posts: cached_posts, total: cached_total }); + return Ok(PostListResponse { + posts: cached_posts, + total: cached_total, + }); } let client = get_conn().await.map_err(AppError::db_conn)?; @@ -68,15 +71,15 @@ pub async fn list_published_posts( #[cfg(not(feature = "server"))] { - Ok(PostListResponse { posts: Vec::new(), total: 0 }) + Ok(PostListResponse { + posts: Vec::new(), + total: 0, + }) } } #[server(ListPosts, "/api")] -pub async fn list_posts( - page: i32, - per_page: i32, -) -> Result { +pub async fn list_posts(page: i32, per_page: i32) -> Result { let _user = get_current_admin_user().await?; #[cfg(feature = "server")] @@ -84,10 +87,7 @@ pub async fn list_posts( let client = get_conn().await.map_err(AppError::db_conn)?; let count_row = client - .query_one( - "SELECT COUNT(*) FROM posts WHERE deleted_at IS NULL", - &[], - ) + .query_one("SELECT COUNT(*) FROM posts WHERE deleted_at IS NULL", &[]) .await .map_err(AppError::query)?; let total: i64 = count_row.get(0); @@ -122,7 +122,10 @@ pub async fn list_posts( #[cfg(not(feature = "server"))] { - Ok(PostListResponse { posts: Vec::new(), total: 0 }) + Ok(PostListResponse { + posts: Vec::new(), + total: 0, + }) } } @@ -130,8 +133,12 @@ pub async fn list_posts( pub async fn get_posts_by_tag(tag_name: String) -> Result { #[cfg(feature = "server")] { - if let Some((cached_posts, cached_total)) = crate::cache::get_posts_by_tag(&tag_name).await { - return Ok(PostListResponse { posts: cached_posts, total: cached_total }); + if let Some((cached_posts, cached_total)) = crate::cache::get_posts_by_tag(&tag_name).await + { + return Ok(PostListResponse { + posts: cached_posts, + total: cached_total, + }); } let client = get_conn().await.map_err(AppError::db_conn)?; @@ -170,6 +177,9 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result Result Result Result { diff --git a/src/api/posts/update.rs b/src/api/posts/update.rs index 7c2b0ea..1f9a1a8 100644 --- a/src/api/posts/update.rs +++ b/src/api/posts/update.rs @@ -65,7 +65,8 @@ pub async fn update_post( _ => crate::api::slug::slugify(&title), }; - let final_slug = crate::api::slug::ensure_unique_slug(&client, &base_slug, Some(post_id)).await?; + let final_slug = + crate::api::slug::ensure_unique_slug(&client, &base_slug, Some(post_id)).await?; let rendered = crate::api::markdown::render_markdown_enhanced(&content_md); let content_html = rendered.html; let toc_html = if rendered.toc_html.is_empty() { diff --git a/src/api/rate_limit.rs b/src/api/rate_limit.rs index 5afd42a..571217d 100644 --- a/src/api/rate_limit.rs +++ b/src/api/rate_limit.rs @@ -1,11 +1,11 @@ #[cfg(feature = "server")] -use std::sync::LazyLock; -#[cfg(feature = "server")] -use std::num::NonZeroU32; +use axum::http::StatusCode; #[cfg(feature = "server")] use governor::{DefaultKeyedRateLimiter, Quota, RateLimiter}; #[cfg(feature = "server")] -use axum::http::StatusCode; +use std::num::NonZeroU32; +#[cfg(feature = "server")] +use std::sync::LazyLock; #[cfg(feature = "server")] fn env_or(key: &str, default: u32) -> NonZeroU32 { @@ -90,10 +90,7 @@ fn ip_from_x_forwarded_for(value: &str, trusted_proxy_count: usize) -> Option String { +pub fn get_client_ip_with_trusted(headers: &http::HeaderMap, trusted_proxy_count: usize) -> String { if let Some(value) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) { if let Some(ip) = ip_from_x_forwarded_for(value, trusted_proxy_count) { return ip; @@ -204,7 +201,10 @@ mod tests { #[test] fn get_client_ip_ignores_empty_x_forwarded_for_entries() { let mut headers = HeaderMap::new(); - headers.insert("x-forwarded-for", " , 1.2.3.4 , 5.6.7.8 , ".parse().unwrap()); + headers.insert( + "x-forwarded-for", + " , 1.2.3.4 , 5.6.7.8 , ".parse().unwrap(), + ); assert_eq!(get_client_ip_with_trusted(&headers, 1), "1.2.3.4"); } } diff --git a/src/api/sanitizer.rs b/src/api/sanitizer.rs index fa4524d..c9d4f97 100644 --- a/src/api/sanitizer.rs +++ b/src/api/sanitizer.rs @@ -7,15 +7,81 @@ use std::collections::HashSet; fn default_allowed_tags() -> HashSet<&'static str> { let mut set = HashSet::new(); for tag in [ - "a", "abbr", "acronym", "area", "article", "aside", "b", "bdi", - "bdo", "blockquote", "br", "caption", "center", "cite", "code", - "col", "colgroup", "data", "dd", "del", "details", "dfn", "div", - "dl", "dt", "em", "figcaption", "figure", "footer", "h1", "h2", - "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "i", "img", - "ins", "kbd", "li", "map", "mark", "nav", "ol", "p", "pre", - "q", "rp", "rt", "rtc", "ruby", "s", "samp", "small", "span", - "strike", "strong", "sub", "summary", "sup", "table", "tbody", - "td", "th", "thead", "time", "tr", "tt", "u", "ul", "var", "wbr", + "a", + "abbr", + "acronym", + "area", + "article", + "aside", + "b", + "bdi", + "bdo", + "blockquote", + "br", + "caption", + "center", + "cite", + "code", + "col", + "colgroup", + "data", + "dd", + "del", + "details", + "dfn", + "div", + "dl", + "dt", + "em", + "figcaption", + "figure", + "footer", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "header", + "hgroup", + "hr", + "i", + "img", + "ins", + "kbd", + "li", + "map", + "mark", + "nav", + "ol", + "p", + "pre", + "q", + "rp", + "rt", + "rtc", + "ruby", + "s", + "samp", + "small", + "span", + "strike", + "strong", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "th", + "thead", + "time", + "tr", + "tt", + "u", + "ul", + "var", + "wbr", ] { set.insert(tag); } @@ -34,10 +100,31 @@ fn clean_content_tags() -> HashSet<&'static str> { fn default_allowed_schemes() -> HashSet<&'static str> { let mut set = HashSet::new(); for scheme in [ - "bitcoin", "ftp", "ftps", "geo", "http", "https", "im", "irc", - "ircs", "magnet", "mailto", "mms", "mx", "news", "nntp", - "openpgp4fpr", "sip", "sms", "smsto", "ssh", "tel", "url", - "webcal", "wtai", "xmpp", + "bitcoin", + "ftp", + "ftps", + "geo", + "http", + "https", + "im", + "irc", + "ircs", + "magnet", + "mailto", + "mms", + "mx", + "news", + "nntp", + "openpgp4fpr", + "sip", + "sms", + "smsto", + "ssh", + "tel", + "url", + "webcal", + "wtai", + "xmpp", ] { set.insert(scheme); } @@ -109,9 +196,17 @@ fn sanitize(input: &str, config: &SanitizerConfig) -> String { ("q", vec!["cite"]), ("table", vec!["align", "char", "charoff", "summary"]), ("tbody", vec!["align", "char", "charoff"]), - ("td", vec!["align", "char", "charoff", "colspan", "headers", "rowspan"]), + ( + "td", + vec!["align", "char", "charoff", "colspan", "headers", "rowspan"], + ), ("tfoot", vec!["align", "char", "charoff"]), - ("th", vec!["align", "char", "charoff", "colspan", "headers", "rowspan", "scope"]), + ( + "th", + vec![ + "align", "char", "charoff", "colspan", "headers", "rowspan", "scope", + ], + ), ("thead", vec!["align", "char", "charoff"]), ("tr", vec!["align", "char", "charoff"]), ]; diff --git a/src/api/upload.rs b/src/api/upload.rs index ed89307..97eefe3 100644 --- a/src/api/upload.rs +++ b/src/api/upload.rs @@ -261,7 +261,11 @@ mod tests { let uuid = "abc-123"; let ext = "jpg"; let file_name = format!("{}.{}.{}", now_str, uuid, ext); - assert!(!file_name.contains(' '), "filename should not contain spaces: got '{}'", file_name); + assert!( + !file_name.contains(' '), + "filename should not contain spaces: got '{}'", + file_name + ); } #[test] @@ -297,5 +301,3 @@ mod tests { assert!(loaded.is_ok()); } } - - diff --git a/src/bin/generate_highlight_css.rs b/src/bin/generate_highlight_css.rs index df9a9ab..3f5145d 100644 --- a/src/bin/generate_highlight_css.rs +++ b/src/bin/generate_highlight_css.rs @@ -36,8 +36,7 @@ fn main() { output.push_str(&mocha_rewritten); std::fs::create_dir_all("public").expect("Failed to create public/"); - std::fs::write("public/highlight.css", output) - .expect("Failed to write public/highlight.css"); + std::fs::write("public/highlight.css", output).expect("Failed to write public/highlight.css"); println!("Generated public/highlight.css"); } diff --git a/src/cache.rs b/src/cache.rs index 3a0f288..90d0701 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -49,8 +49,6 @@ pub enum CacheKey { PendingCommentCount, } - - // ============================================================================ // Cache Instances // ============================================================================ @@ -153,12 +151,17 @@ pub async fn set_post_list(key: &CacheKey, posts: Vec, total: i64) { #[cfg(feature = "server")] pub async fn get_total_published_posts() -> Option { - POST_LIST_CACHE.get(&CacheKey::TotalPublishedPosts).await.map(|(_, total)| total) + POST_LIST_CACHE + .get(&CacheKey::TotalPublishedPosts) + .await + .map(|(_, total)| total) } #[cfg(feature = "server")] pub async fn set_total_published_posts(total: i64) { - let _ = POST_LIST_CACHE.insert(CacheKey::TotalPublishedPosts, (vec![], total)).await; + let _ = POST_LIST_CACHE + .insert(CacheKey::TotalPublishedPosts, (vec![], total)) + .await; } #[cfg(feature = "server")] @@ -330,21 +333,33 @@ mod tests { #[test] fn cache_key_equality() { - let k1 = CacheKey::PublishedPosts { page: 1, per_page: 10 }; - let k2 = CacheKey::PublishedPosts { page: 1, per_page: 10 }; - let k3 = CacheKey::PublishedPosts { page: 2, per_page: 10 }; + let k1 = CacheKey::PublishedPosts { + page: 1, + per_page: 10, + }; + let k2 = CacheKey::PublishedPosts { + page: 1, + per_page: 10, + }; + let k3 = CacheKey::PublishedPosts { + page: 2, + per_page: 10, + }; assert_eq!(k1, k2); assert_ne!(k1, k3); } #[tokio::test] async fn post_list_cache_roundtrip() { - let key = CacheKey::PublishedPosts { page: 999, per_page: 99 }; + let key = CacheKey::PublishedPosts { + page: 999, + per_page: 99, + }; let posts = vec![]; - + set_post_list(&key, posts.clone(), 0).await; let cached = get_post_list(&key).await; - + assert!(cached.is_some()); let (cached_posts, cached_total) = cached.unwrap(); assert_eq!(cached_posts.len(), 0); @@ -353,11 +368,15 @@ mod tests { #[tokio::test] async fn tag_list_cache_roundtrip() { - let tags = vec![Tag { id: 1, name: "rust".to_string(), post_count: 5 }]; - + let tags = vec![Tag { + id: 1, + name: "rust".to_string(), + post_count: 5, + }]; + set_tag_list(tags.clone()).await; let cached = get_tag_list().await; - + assert!(cached.is_some()); assert_eq!(cached.unwrap()[0].name, "rust"); } @@ -384,21 +403,25 @@ mod tests { prev_post: None, next_post: None, }); - + set_post_by_slug("test", post.clone()).await; let cached = get_post_by_slug("test").await; - + assert!(cached.is_some()); assert_eq!(cached.unwrap().unwrap().title, "Test"); } #[tokio::test] async fn post_stats_cache_roundtrip() { - let stats = PostStats { total: 10, drafts: 3, published: 7 }; - + let stats = PostStats { + total: 10, + drafts: 3, + published: 7, + }; + set_post_stats(stats.clone()).await; let cached = get_post_stats().await; - + assert!(cached.is_some()); assert_eq!(cached.unwrap().total, 10); } @@ -425,13 +448,13 @@ mod tests { prev_post: None, next_post: None, }); - + set_post_by_slug("invalidation-test", post.clone()).await; let cached_before = get_post_by_slug("invalidation-test").await; assert!(cached_before.is_some()); - + invalidate_post_by_slug("invalidation-test").await; - + let cached_after = get_post_by_slug("invalidation-test").await; assert!(cached_after.is_none()); } diff --git a/src/components/admin_layout.rs b/src/components/admin_layout.rs index d1ea068..6d11584 100644 --- a/src/components/admin_layout.rs +++ b/src/components/admin_layout.rs @@ -73,7 +73,8 @@ pub fn AdminLayout() -> Element { } }; - let is_write_route = matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); + let is_write_route = + matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }); let main_class = if is_write_route { "flex-1 w-full max-w-5xl mx-auto px-6 flex flex-col overflow-hidden" } else { diff --git a/src/components/admin_skeleton.rs b/src/components/admin_skeleton.rs index 2910754..a3ebf84 100644 --- a/src/components/admin_skeleton.rs +++ b/src/components/admin_skeleton.rs @@ -38,5 +38,3 @@ pub fn AdminDashboardSkeleton() -> Element { } } } - - diff --git a/src/components/comments/form.rs b/src/components/comments/form.rs index ee1c534..d9093e6 100644 --- a/src/components/comments/form.rs +++ b/src/components/comments/form.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; use crate::api::comments::create_comment; use crate::components::comments::section::CommentContext; -use crate::components::forms::{INPUT_CLASS, BUTTON_PRIMARY_CLASS, AlertBox}; +use crate::components::forms::{AlertBox, BUTTON_PRIMARY_CLASS, INPUT_CLASS}; use crate::hooks::comment_storage::{self, PendingComment}; #[component] diff --git a/src/components/comments/item.rs b/src/components/comments/item.rs index 439710e..6032b6e 100644 --- a/src/components/comments/item.rs +++ b/src/components/comments/item.rs @@ -1,8 +1,8 @@ use dioxus::prelude::*; -use crate::models::comment::PublicComment; -use crate::components::comments::section::CommentContext; use crate::components::comments::form::CommentForm; +use crate::components::comments::section::CommentContext; +use crate::models::comment::PublicComment; #[component] pub fn CommentItem(comment: PublicComment, post_id: i32) -> Element { diff --git a/src/components/comments/list.rs b/src/components/comments/list.rs index ac6cd3a..3358dad 100644 --- a/src/components/comments/list.rs +++ b/src/components/comments/list.rs @@ -1,9 +1,9 @@ use dioxus::prelude::*; -use crate::models::comment::PublicComment; -use crate::hooks::comment_storage::PendingComment; use crate::components::comments::item::CommentItem; use crate::components::comments::pending_item::PendingCommentItem; +use crate::hooks::comment_storage::PendingComment; +use crate::models::comment::PublicComment; #[derive(Clone)] enum MergedComment { @@ -23,10 +23,13 @@ fn merge_and_treeify( .chain(pending.into_iter().map(MergedComment::Pending)) .collect(); - let all_ids: HashSet = all.iter().map(|c| match c { - MergedComment::Approved(c) => c.id, - MergedComment::Pending(c) => c.id, - }).collect(); + let all_ids: HashSet = all + .iter() + .map(|c| match c { + MergedComment::Approved(c) => c.id, + MergedComment::Pending(c) => c.id, + }) + .collect(); let mut children_map: HashMap, Vec> = HashMap::new(); for comment in all { @@ -38,7 +41,10 @@ fn merge_and_treeify( Some(pid) if !all_ids.contains(&pid) => None, _ => parent_id, }; - children_map.entry(effective_parent).or_default().push(comment); + children_map + .entry(effective_parent) + .or_default() + .push(comment); } for children in children_map.values_mut() { diff --git a/src/components/comments/mod.rs b/src/components/comments/mod.rs index d81e597..f2fd5e3 100644 --- a/src/components/comments/mod.rs +++ b/src/components/comments/mod.rs @@ -1,6 +1,6 @@ -pub mod section; -pub mod form; -pub mod list; -pub mod item; -pub mod pending_item; pub mod actions; +pub mod form; +pub mod item; +pub mod list; +pub mod pending_item; +pub mod section; diff --git a/src/components/comments/pending_item.rs b/src/components/comments/pending_item.rs index 0d17358..4b711e6 100644 --- a/src/components/comments/pending_item.rs +++ b/src/components/comments/pending_item.rs @@ -1,11 +1,10 @@ use dioxus::prelude::*; -use crate::hooks::comment_storage::{PendingComment, render_pending_content}; +use crate::hooks::comment_storage::{render_pending_content, PendingComment}; #[component] #[allow(unused_variables)] pub fn PendingCommentItem(comment: PendingComment, post_id: i32) -> Element { - let depth = if comment.parent_id.is_none() && comment.depth > 0 { 0 } else { diff --git a/src/components/forms.rs b/src/components/forms.rs index 89b201d..cc11c26 100644 --- a/src/components/forms.rs +++ b/src/components/forms.rs @@ -13,7 +13,11 @@ pub fn FormInput( oninput: EventHandler, onkeydown: Option>, ) -> Element { - let disabled_class = if disabled { "opacity-60 cursor-not-allowed" } else { "" }; + let disabled_class = if disabled { + "opacity-60 cursor-not-allowed" + } else { + "" + }; rsx! { input { class: "{INPUT_CLASS} {disabled_class}", @@ -43,8 +47,14 @@ pub fn FormLabel(label: &'static str) -> Element { #[component] pub fn AlertBox(message: String, variant: &'static str) -> Element { let (bg_class, text_class) = match variant { - "error" => ("bg-red-100 dark:bg-red-900/30", "text-red-700 dark:text-red-300"), - "success" => ("bg-green-100 dark:bg-green-900/30", "text-green-700 dark:text-green-300"), + "error" => ( + "bg-red-100 dark:bg-red-900/30", + "text-red-700 dark:text-red-300", + ), + "success" => ( + "bg-green-100 dark:bg-green-900/30", + "text-green-700 dark:text-green-300", + ), _ => ("bg-paper-code-bg", "text-paper-secondary"), }; rsx! { diff --git a/src/components/image_viewer.rs b/src/components/image_viewer.rs index 9915744..c7875e5 100644 --- a/src/components/image_viewer.rs +++ b/src/components/image_viewer.rs @@ -3,17 +3,18 @@ use dioxus::prelude::*; #[component] pub fn ImageViewer( src: String, - #[props(default = "?w=800".to_string())] - thumb_params: String, - #[props(default = "图片".to_string())] - alt: String, - #[props(default = false)] - lazy_load: bool, + #[props(default = "?w=800".to_string())] thumb_params: String, + #[props(default = "图片".to_string())] alt: String, + #[props(default = false)] lazy_load: bool, ) -> Element { let mut is_open = use_signal(|| false); let thumb_src = if src.contains('?') { - format!("{}&{}", src.split('?').next().unwrap_or(&src), thumb_params.trim_start_matches('?')) + format!( + "{}&{}", + src.split('?').next().unwrap_or(&src), + thumb_params.trim_start_matches('?') + ) } else { format!("{}{}", src, thumb_params) }; diff --git a/src/components/post/post_header.rs b/src/components/post/post_header.rs index d9f2997..47a2831 100644 --- a/src/components/post/post_header.rs +++ b/src/components/post/post_header.rs @@ -9,7 +9,7 @@ pub fn PostHeader(post: Post) -> Element { rsx! { header { class: "post-header", Breadcrumbs { title: post.title.clone() } - + h1 { class: "post-title", "{post.title}" if post.status == PostStatus::Draft { diff --git a/src/components/skeletons/archive_skeleton.rs b/src/components/skeletons/archive_skeleton.rs index 2b84d94..f50badd 100644 --- a/src/components/skeletons/archive_skeleton.rs +++ b/src/components/skeletons/archive_skeleton.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::components::skeletons::atoms::*; +use dioxus::prelude::*; /// 归档页骨架屏 /// 结构:统计行("共 N 篇文章") + 年份标题 + 月份标题 + 文章条目列表 diff --git a/src/components/skeletons/comment_skeleton.rs b/src/components/skeletons/comment_skeleton.rs index 0a8135d..38fab18 100644 --- a/src/components/skeletons/comment_skeleton.rs +++ b/src/components/skeletons/comment_skeleton.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::components::skeletons::atoms::*; +use dioxus::prelude::*; #[component] pub fn CommentListSkeleton() -> Element { diff --git a/src/components/skeletons/delayed_skeleton.rs b/src/components/skeletons/delayed_skeleton.rs index 003883e..5b23455 100644 --- a/src/components/skeletons/delayed_skeleton.rs +++ b/src/components/skeletons/delayed_skeleton.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::utils::time::sleep_ms; +use dioxus::prelude::*; /// 骨架屏 pulse 动画延迟(毫秒) /// 加载时间低于此值时骨架屏只显示静态灰色块,避免 pulse 动画一闪而过 diff --git a/src/components/skeletons/mod.rs b/src/components/skeletons/mod.rs index 7518ebf..2b73c58 100644 --- a/src/components/skeletons/mod.rs +++ b/src/components/skeletons/mod.rs @@ -1,5 +1,5 @@ -pub mod atoms; pub mod archive_skeleton; +pub mod atoms; pub mod comment_skeleton; pub mod delayed_skeleton; pub mod home_skeleton; diff --git a/src/components/skeletons/post_detail_skeleton.rs b/src/components/skeletons/post_detail_skeleton.rs index e57adca..3b13a6c 100644 --- a/src/components/skeletons/post_detail_skeleton.rs +++ b/src/components/skeletons/post_detail_skeleton.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::components::skeletons::atoms::*; +use dioxus::prelude::*; /// 文章详情页骨架屏 /// 结构:面包屑 + 标题(h1) + 摘要 + 元信息 + 封面图 + 正文(多段) + Footer diff --git a/src/components/write_skeleton.rs b/src/components/write_skeleton.rs index bfd5e3c..018fa63 100644 --- a/src/components/write_skeleton.rs +++ b/src/components/write_skeleton.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::components::skeletons::atoms::*; +use dioxus::prelude::*; #[component] pub fn WriteSkeleton() -> Element { diff --git a/src/db/pool.rs b/src/db/pool.rs index 8cf460a..5dd1026 100644 --- a/src/db/pool.rs +++ b/src/db/pool.rs @@ -16,10 +16,12 @@ pub static DB_POOL: LazyLock = LazyLock::new(|| { let mgr = Manager::from_config(pg_cfg, NoTls, mgr_cfg); Pool::builder(mgr) - .max_size(std::env::var("DB_POOL_SIZE") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(20)) + .max_size( + std::env::var("DB_POOL_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(20), + ) .build() .expect("Failed to create database connection pool") }); @@ -34,7 +36,12 @@ pub async fn get_conn() -> Result return Ok(conn), Err(e) => { if attempt < MAX_RETRIES { - tracing::warn!("DB connection attempt {} failed, retrying in {:?}: {:?}", attempt + 1, RETRY_DELAY, e); + tracing::warn!( + "DB connection attempt {} failed, retrying in {:?}: {:?}", + attempt + 1, + RETRY_DELAY, + e + ); tokio::time::sleep(RETRY_DELAY).await; } last_err = Some(e); diff --git a/src/highlight.rs b/src/highlight.rs index 326c552..f917b03 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -2,7 +2,7 @@ pub mod server { use std::sync::LazyLock; - use syntect::html::{ClassedHTMLGenerator, ClassStyle}; + use syntect::html::{ClassStyle, ClassedHTMLGenerator}; use syntect::parsing::SyntaxSet; use syntect::util::LinesWithEndings; diff --git a/src/hooks/delayed_loading.rs b/src/hooks/delayed_loading.rs index 7852b5e..b7f2922 100644 --- a/src/hooks/delayed_loading.rs +++ b/src/hooks/delayed_loading.rs @@ -1,5 +1,5 @@ -use dioxus::prelude::*; use crate::utils::time::sleep_ms; +use dioxus::prelude::*; /// 骨架屏最小显示延迟(毫秒)。加载时间低于此值时不会显示骨架屏,避免闪烁。 pub const MIN_SKELETON_DELAY_MS: u32 = 200; diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 247a499..44abc3f 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -1,2 +1,2 @@ -pub mod delayed_loading; pub mod comment_storage; +pub mod delayed_loading; diff --git a/src/main.rs b/src/main.rs index e206be9..08a61f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ mod api; -mod cache; mod auth; +mod cache; mod components; mod context; mod db; @@ -44,13 +44,14 @@ fn main() { }); let config = ServeConfig::builder().incremental( - dioxus::server::IncrementalRendererConfig::default() - .invalidate_after(std::time::Duration::from_secs( + dioxus::server::IncrementalRendererConfig::default().invalidate_after( + std::time::Duration::from_secs( std::env::var("SSR_CACHE_SECS") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(3600), - )), + ), + ), ); let api_routes = axum::Router::new().route( "/api/upload", @@ -66,9 +67,7 @@ fn main() { let dioxus_app = axum::Router::new().serve_dioxus_application(config, router::AppRouter); - let router = api_routes - .merge(static_routes) - .merge(dioxus_app); + let router = api_routes.merge(static_routes).merge(dioxus_app); Ok(router) }); diff --git a/src/models/post.rs b/src/models/post.rs index 09c5ca5..c450ad2 100644 --- a/src/models/post.rs +++ b/src/models/post.rs @@ -71,7 +71,9 @@ impl Post { pub fn status_badge_class(&self) -> &'static str { match self.status { - PostStatus::Published => "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300", + PostStatus::Published => { + "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300" + } PostStatus::Draft => "bg-gray-100 dark:bg-[#333] text-gray-600 dark:text-[#9b9c9d]", } } @@ -128,7 +130,10 @@ mod tests { #[test] fn post_status_from_str() { assert_eq!(PostStatus::from_str("draft"), Some(PostStatus::Draft)); - assert_eq!(PostStatus::from_str("published"), Some(PostStatus::Published)); + assert_eq!( + PostStatus::from_str("published"), + Some(PostStatus::Published) + ); assert_eq!(PostStatus::from_str("unknown"), None); assert_eq!(PostStatus::from_str(""), None); } @@ -186,6 +191,9 @@ mod tests { #[test] fn post_status_serde_roundtrip() { let json = serde_json::to_string(&PostStatus::Draft).unwrap(); - assert_eq!(serde_json::from_str::(&json).unwrap(), PostStatus::Draft); + assert_eq!( + serde_json::from_str::(&json).unwrap(), + PostStatus::Draft + ); } } diff --git a/src/models/user.rs b/src/models/user.rs index ddd6cd5..288698f 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -95,6 +95,9 @@ mod tests { #[test] fn user_role_serde_roundtrip() { let json = serde_json::to_string(&UserRole::Admin).unwrap(); - assert_eq!(serde_json::from_str::(&json).unwrap(), UserRole::Admin); + assert_eq!( + serde_json::from_str::(&json).unwrap(), + UserRole::Admin + ); } } diff --git a/src/pages/admin/comments.rs b/src/pages/admin/comments.rs index f78fd46..57fcc21 100644 --- a/src/pages/admin/comments.rs +++ b/src/pages/admin/comments.rs @@ -3,13 +3,11 @@ use std::collections::HashSet; use dioxus::prelude::*; use dioxus::router::components::Link; -use crate::api::comments::{ - approve_comment, batch_update_comment_status, spam_comment, -}; -#[cfg(target_arch = "wasm32")] -use crate::api::comments::{get_all_comments, AllCommentsResponse}; #[cfg(target_arch = "wasm32")] use crate::api::comments::trash_comment; +use crate::api::comments::{approve_comment, batch_update_comment_status, spam_comment}; +#[cfg(target_arch = "wasm32")] +use crate::api::comments::{get_all_comments, AllCommentsResponse}; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::models::comment::{AdminComment, CommentStatus}; use crate::router::Route; @@ -54,7 +52,11 @@ pub fn AdminCommentsPage(page: i32) -> Element { #[allow(unused_variables)] let filter_status = move || { let f = active_filter(); - if f.is_empty() { None } else { Some(f) } + if f.is_empty() { + None + } else { + Some(f) + } }; // 客户端(CSR)加载数据 @@ -313,14 +315,29 @@ fn CommentRow( on_trash: EventHandler, ) -> Element { let (badge_class, status_label) = match &comment.status { - CommentStatus::Pending => ("bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400", "待审核"), - CommentStatus::Approved => ("bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", "已通过"), - CommentStatus::Spam => ("bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", "垃圾"), - CommentStatus::Trash => ("bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400", "已删除"), + CommentStatus::Pending => ( + "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400", + "待审核", + ), + CommentStatus::Approved => ( + "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", + "已通过", + ), + CommentStatus::Spam => ( + "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", + "垃圾", + ), + CommentStatus::Trash => ( + "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400", + "已删除", + ), }; let date_str = comment.created_at.format("%Y-%m-%d").to_string(); let preview = if comment.content_md.len() > 100 { - format!("{}...", &comment.content_md[..comment.content_md.ceil_char_boundary(100)]) + format!( + "{}...", + &comment.content_md[..comment.content_md.ceil_char_boundary(100)] + ) } else { comment.content_md.clone() }; @@ -411,9 +428,13 @@ fn CommentsPagination(current_page: i32, total: i64) -> Element { let prev_route = if current_page - 1 <= 1 { Route::AdminComments {} } else { - Route::AdminCommentsPage { page: current_page - 1 } + Route::AdminCommentsPage { + page: current_page - 1, + } + }; + let next_route = Route::AdminCommentsPage { + page: current_page + 1, }; - let next_route = Route::AdminCommentsPage { page: current_page + 1 }; rsx! { nav { class: "flex mt-6 justify-between", diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index 050c5ad..28de746 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -37,7 +37,10 @@ pub fn PostsPage(page: i32) -> Element { let p = current_page; spawn(async move { match list_posts(p, POSTS_PER_PAGE).await { - Ok(PostListResponse { posts: list, total: t }) => { + Ok(PostListResponse { + posts: list, + total: t, + }) => { posts.set(list); total.set(t); } diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index 23fa66a..87d263a 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -4,7 +4,9 @@ use dioxus::prelude::*; use wasm_bindgen::JsCast; #[cfg(target_arch = "wasm32")] -use crate::api::posts::{create_post, get_post_by_id, update_post, CreatePostResponse, SinglePostResponse}; +use crate::api::posts::{ + create_post, get_post_by_id, update_post, CreatePostResponse, SinglePostResponse, +}; use crate::components::write_skeleton::WriteSkeleton; use crate::models::post::Post; use crate::router::Route; @@ -63,21 +65,21 @@ fn write_editor(post_id: Option) -> Element { if is_edit { #[cfg(target_arch = "wasm32")] if let Some(id) = post_id { - spawn(async move { - match get_post_by_id(id).await { - Ok(SinglePostResponse { post: Some(post) }) => { - edit_post.set(Some(post)); + spawn(async move { + match get_post_by_id(id).await { + Ok(SinglePostResponse { post: Some(post) }) => { + edit_post.set(Some(post)); + } + Ok(SinglePostResponse { post: None }) => { + load_error.set(Some("文章不存在".to_string())); + } + Err(e) => { + load_error.set(Some(format!("加载失败: {}", e))); + } } - Ok(SinglePostResponse { post: None }) => { - load_error.set(Some("文章不存在".to_string())); - } - Err(e) => { - load_error.set(Some(format!("加载失败: {}", e))); - } - } - }); + }); + } } - } }); #[cfg(target_arch = "wasm32")] diff --git a/src/pages/archives.rs b/src/pages/archives.rs index 618354c..c7f4b53 100644 --- a/src/pages/archives.rs +++ b/src/pages/archives.rs @@ -2,8 +2,8 @@ use dioxus::prelude::*; use dioxus::router::components::Link; use crate::api::posts::{list_published_posts, PostListResponse}; -use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::archive_skeleton::ArchiveSkeleton; +use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::models::post::Post; use crate::router::Route; diff --git a/src/pages/tags.rs b/src/pages/tags.rs index 803f25b..d581fce 100644 --- a/src/pages/tags.rs +++ b/src/pages/tags.rs @@ -4,7 +4,7 @@ use dioxus::router::components::Link; use crate::api::posts::{get_posts_by_tag, list_tags, PostListResponse, TagListResponse}; use crate::components::post_card::PostCard; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; -use crate::components::skeletons::tags_skeleton::{TagsSkeleton, TagDetailSkeleton}; +use crate::components::skeletons::tags_skeleton::{TagDetailSkeleton, TagsSkeleton}; use crate::router::Route; #[component] diff --git a/src/router.rs b/src/router.rs index f3e1815..2ff8fe6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -5,7 +5,9 @@ use crate::components::admin_layout::AdminLayout; use crate::components::frontend_layout::FrontendLayout; use crate::context::UserContext; use crate::pages::about::About; -use crate::pages::admin::{Admin, AdminComments, AdminCommentsPage, Posts, PostsPage, Write, WriteEdit}; +use crate::pages::admin::{ + Admin, AdminComments, AdminCommentsPage, Posts, PostsPage, Write, WriteEdit, +}; use crate::pages::archives::Archives; use crate::pages::home::{Home, HomePage}; use crate::pages::login::Login; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e381a9e..d89e655 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,3 @@ -pub mod time; #[cfg(feature = "server")] pub mod text; +pub mod time; diff --git a/src/utils/text.rs b/src/utils/text.rs index 1067758..243df5b 100644 --- a/src/utils/text.rs +++ b/src/utils/text.rs @@ -1,28 +1,21 @@ use std::sync::LazyLock; -static CODE_BLOCK_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"```[\s\S]*?```").unwrap() -}); +static CODE_BLOCK_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"```[\s\S]*?```").unwrap()); -static INLINE_CODE_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"`[^`]*`").unwrap() -}); +static INLINE_CODE_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"`[^`]*`").unwrap()); -static LINK_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").unwrap() -}); +static LINK_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").unwrap()); -static HEADING_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"^#{1,6}\s*").unwrap() -}); +static HEADING_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"^#{1,6}\s*").unwrap()); -static IMAGE_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").unwrap() -}); +static IMAGE_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").unwrap()); -static WHITESPACE_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"\s+").unwrap() -}); +static WHITESPACE_RE: LazyLock = LazyLock::new(|| regex::Regex::new(r"\s+").unwrap()); pub fn strip_markdown(md: &str) -> String { let mut plain = CODE_BLOCK_RE.replace_all(md, "").to_string(); @@ -88,7 +81,10 @@ mod tests { #[test] fn strip_markdown_keeps_link_text() { - assert_eq!(strip_markdown("[click me](https://example.com)"), "click me"); + assert_eq!( + strip_markdown("[click me](https://example.com)"), + "click me" + ); } #[test] @@ -98,7 +94,10 @@ mod tests { #[test] fn strip_markdown_removes_bold_and_italic() { - assert_eq!(strip_markdown("**bold** *italic* __bold__ _italic_"), "bold italic bold italic"); + assert_eq!( + strip_markdown("**bold** *italic* __bold__ _italic_"), + "bold italic bold italic" + ); } #[test] diff --git a/src/webp.rs b/src/webp.rs index a315abb..ca6d192 100644 --- a/src/webp.rs +++ b/src/webp.rs @@ -178,7 +178,10 @@ mod tests { // Note: LazyLock initializes once, so we can't easily test // different env values in the same process. // Test that the default config is reasonable - let config = WebpConfig { quality: 85.0, method: 2 }; + let config = WebpConfig { + quality: 85.0, + method: 2, + }; assert!(config.quality >= 0.0 && config.quality <= 100.0); assert!(config.method <= 6); }