diff --git a/AGENTS.md b/AGENTS.md index d806c58..191530b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,6 +42,8 @@ RUST_LOG=info Run migrations before first dev server start: ```bash +./migrate.sh # preferred: auto-creates DB, runs all migrations in order +# or manually: psql $DATABASE_URL -f migrations/001_init.sql ``` diff --git a/src/api/auth.rs b/src/api/auth.rs index 9cd3110..c09eee8 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -38,19 +38,16 @@ fn validate_password(password: &str) -> Result<(), String> { #[cfg(feature = "server")] fn parse_session_token(cookie_header: &str) -> Option<&str> { - cookie_header - .split(';') - .map(|s| s.trim()) - .find_map(|pair| { - let mut parts = pair.splitn(2, '='); - let name = parts.next()?.trim(); - let value = parts.next()?.trim(); - if name == "session" { - Some(value) - } else { - None - } - }) + cookie_header.split(';').map(|s| s.trim()).find_map(|pair| { + let mut parts = pair.splitn(2, '='); + let name = parts.next()?.trim(); + let value = parts.next()?.trim(); + if name == "session" { + Some(value) + } else { + None + } + }) } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -88,13 +85,10 @@ pub async fn register( }); } - let client = DB_POOL - .get() - .await - .map_err(|e| { - tracing::error!("Register DB connection failed: {:?}", e); - ServerFnError::new(format!("数据库连接失败: {}", e)) - })?; + let client = DB_POOL.get().await.map_err(|e| { + tracing::error!("Register DB connection failed: {:?}", e); + ServerFnError::new(format!("数据库连接失败: {}", e)) + })?; let admin_count: i64 = client .query_one("SELECT COUNT(*) FROM users WHERE role = 'admin'", &[]) @@ -113,11 +107,10 @@ pub async fn register( }); } - let password_hash = password::hash_password(&password) - .map_err(|e| { - tracing::error!("Register password hash failed: {:?}", e); - ServerFnError::new(format!("密码哈希失败: {}", e)) - })?; + let password_hash = password::hash_password(&password).map_err(|e| { + tracing::error!("Register password hash failed: {:?}", e); + ServerFnError::new(format!("密码哈希失败: {}", e)) + })?; let result = client .query_one( @@ -148,17 +141,11 @@ pub async fn register( } #[server(Login, "/api")] -pub async fn login( - username: String, - password: String, -) -> Result { - let client = DB_POOL - .get() - .await - .map_err(|e| { - tracing::error!("Login DB connection failed: {:?}", e); - ServerFnError::new(format!("数据库连接失败: {}", e)) - })?; +pub async fn login(username: String, password: String) -> Result { + let client = DB_POOL.get().await.map_err(|e| { + tracing::error!("Login DB connection failed: {:?}", e); + ServerFnError::new(format!("数据库连接失败: {}", e)) + })?; let row = match client .query_opt( @@ -182,11 +169,10 @@ pub async fn login( }; let password_hash: String = row.get("password_hash"); - let valid = password::verify_password(&password, &password_hash) - .map_err(|e| { - tracing::error!("Login password verify failed: {:?}", e); - ServerFnError::new(format!("密码验证失败: {}", e)) - })?; + let valid = password::verify_password(&password, &password_hash).map_err(|e| { + tracing::error!("Login password verify failed: {:?}", e); + ServerFnError::new(format!("密码验证失败: {}", e)) + })?; if !valid { return Ok(AuthResponse { @@ -242,21 +228,16 @@ pub async fn logout() -> Result { None }; - let client = DB_POOL - .get() - .await - .map_err(|e| { - tracing::error!("Logout DB connection failed: {:?}", e); - ServerFnError::new(format!("数据库连接失败: {}", e)) - })?; + let client = DB_POOL.get().await.map_err(|e| { + tracing::error!("Logout DB connection failed: {:?}", e); + ServerFnError::new(format!("数据库连接失败: {}", e)) + })?; // 清除 cookie if let Some(ctx) = dioxus::fullstack::FullstackContext::current() { ctx.add_response_header( SET_COOKIE, - HeaderValue::from_static( - "session=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax", - ), + HeaderValue::from_static("session=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax"), ); } @@ -301,13 +282,10 @@ pub async fn get_current_user() -> Result { return Ok(CurrentUserResponse { user: None }); }; - let client = DB_POOL - .get() - .await - .map_err(|e| { - tracing::error!("GetCurrentUser DB connection failed: {:?}", e); - ServerFnError::new(format!("数据库连接失败: {}", e)) - })?; + let client = DB_POOL.get().await.map_err(|e| { + tracing::error!("GetCurrentUser DB connection failed: {:?}", e); + ServerFnError::new(format!("数据库连接失败: {}", e)) + })?; let row = client .query_opt( diff --git a/src/api/mod.rs b/src/api/mod.rs index 0713700..949724c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,2 +1,2 @@ pub mod auth; -pub mod posts; \ No newline at end of file +pub mod posts; diff --git a/src/main.rs b/src/main.rs index 4e28dbd..c727347 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,9 +40,13 @@ fn main() { .serve_dioxus_application(config, router::AppRouter) .layer( TraceLayer::new_for_http() - .make_span_with(tower_http::trace::DefaultMakeSpan::new().level(Level::INFO)) + .make_span_with( + tower_http::trace::DefaultMakeSpan::new().level(Level::INFO), + ) .on_request(tower_http::trace::DefaultOnRequest::new().level(Level::INFO)) - .on_response(tower_http::trace::DefaultOnResponse::new().level(Level::INFO)), + .on_response( + tower_http::trace::DefaultOnResponse::new().level(Level::INFO), + ), ); Ok(router) diff --git a/src/models/mod.rs b/src/models/mod.rs index 99546ae..6f1b986 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,2 +1,2 @@ pub mod post; -pub mod user; \ No newline at end of file +pub mod user; diff --git a/src/models/post.rs b/src/models/post.rs index 6d37c09..2b27b12 100644 --- a/src/models/post.rs +++ b/src/models/post.rs @@ -42,6 +42,14 @@ pub struct Post { pub tags: Vec, } +impl Post { + pub fn formatted_date(&self) -> String { + self.published_at + .map(|d| d.format("%Y-%m-%d").to_string()) + .unwrap_or_else(|| self.created_at.format("%Y-%m-%d").to_string()) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Tag { pub id: i32, diff --git a/src/pages/admin/mod.rs b/src/pages/admin/mod.rs index 7fb0366..85ce9f2 100644 --- a/src/pages/admin/mod.rs +++ b/src/pages/admin/mod.rs @@ -4,4 +4,4 @@ pub mod write; pub use dashboard::Admin; pub use posts::Posts; -pub use write::Write; \ No newline at end of file +pub use write::Write; diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index ec0b80d..9a2f567 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -38,7 +38,7 @@ pub fn Posts() -> Element { thead { tr { class: "border-b border-gray-200 dark:border-[#333] text-left text-gray-500 dark:text-[#9b9c9d]", th { class: "px-4 py-3 font-medium", "标题" } - th { class: "px-4 py-3 font-medium w-24", "状态" } + th { class: "px-4 py-3 font-medium w-24 text-center", "状态" } th { class: "px-4 py-3 font-medium w-32", "日期" } th { class: "px-4 py-3 font-medium w-24 text-right", "操作" } } @@ -107,9 +107,15 @@ fn PostRow(post: Post, deleting: bool, on_delete: EventHandler) -> Element .unwrap_or_else(|| post.created_at.format("%Y-%m-%d").to_string()); let (status_label, status_class) = if post.status == PostStatus::Published { - ("已发布", "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300") + ( + "已发布", + "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300", + ) } else { - ("草稿", "bg-gray-100 dark:bg-[#333] text-gray-600 dark:text-[#9b9c9d]") + ( + "草稿", + "bg-gray-100 dark:bg-[#333] text-gray-600 dark:text-[#9b9c9d]", + ) }; rsx! { @@ -125,7 +131,7 @@ fn PostRow(post: Post, deleting: bool, on_delete: EventHandler) -> Element "{post.title}" } } - td { class: "px-4 py-3", + td { class: "px-4 py-3 text-center", span { class: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {status_class}", "{status_label}" } diff --git a/src/pages/admin/write.rs b/src/pages/admin/write.rs index bf4684f..94c1d49 100644 --- a/src/pages/admin/write.rs +++ b/src/pages/admin/write.rs @@ -120,7 +120,16 @@ pub fn Write() -> Element { error.set(None); spawn(async move { - match create_post(title().trim().to_string(), slug_opt, summary_opt, md, status(), tags_list).await { + match create_post( + title().trim().to_string(), + slug_opt, + summary_opt, + md, + status(), + tags_list, + ) + .await + { Ok(CreatePostResponse { success: true, .. }) => { saving.set(false); success.set(true); @@ -131,7 +140,11 @@ pub fn Write() -> Element { } let _ = dioxus::router::navigator().push("/admin"); } - Ok(CreatePostResponse { success: false, message, .. }) => { + Ok(CreatePostResponse { + success: false, + message, + .. + }) => { saving.set(false); error.set(Some(message)); } diff --git a/src/pages/register.rs b/src/pages/register.rs index 70d9b37..73008c7 100644 --- a/src/pages/register.rs +++ b/src/pages/register.rs @@ -33,7 +33,11 @@ pub fn Register() -> Element { Ok(AuthResponse { success: true, .. }) => { success.set(true); } - Ok(AuthResponse { success: false, message, .. }) => { + Ok(AuthResponse { + success: false, + message, + .. + }) => { error.set(Some(message)); } Err(e) => {