chore: code cleanup - formatting, EOF newlines, model helper, and UI tweaks
This commit is contained in:
parent
1950646bef
commit
9c5b09a278
@ -42,6 +42,8 @@ RUST_LOG=info
|
|||||||
Run migrations before first dev server start:
|
Run migrations before first dev server start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
./migrate.sh # preferred: auto-creates DB, runs all migrations in order
|
||||||
|
# or manually:
|
||||||
psql $DATABASE_URL -f migrations/001_init.sql
|
psql $DATABASE_URL -f migrations/001_init.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -38,19 +38,16 @@ fn validate_password(password: &str) -> Result<(), String> {
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn parse_session_token(cookie_header: &str) -> Option<&str> {
|
fn parse_session_token(cookie_header: &str) -> Option<&str> {
|
||||||
cookie_header
|
cookie_header.split(';').map(|s| s.trim()).find_map(|pair| {
|
||||||
.split(';')
|
let mut parts = pair.splitn(2, '=');
|
||||||
.map(|s| s.trim())
|
let name = parts.next()?.trim();
|
||||||
.find_map(|pair| {
|
let value = parts.next()?.trim();
|
||||||
let mut parts = pair.splitn(2, '=');
|
if name == "session" {
|
||||||
let name = parts.next()?.trim();
|
Some(value)
|
||||||
let value = parts.next()?.trim();
|
} else {
|
||||||
if name == "session" {
|
None
|
||||||
Some(value)
|
}
|
||||||
} else {
|
})
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@ -88,13 +85,10 @@ pub async fn register(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = DB_POOL
|
let client = DB_POOL.get().await.map_err(|e| {
|
||||||
.get()
|
tracing::error!("Register DB connection failed: {:?}", e);
|
||||||
.await
|
ServerFnError::new(format!("数据库连接失败: {}", e))
|
||||||
.map_err(|e| {
|
})?;
|
||||||
tracing::error!("Register DB connection failed: {:?}", e);
|
|
||||||
ServerFnError::new(format!("数据库连接失败: {}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let admin_count: i64 = client
|
let admin_count: i64 = client
|
||||||
.query_one("SELECT COUNT(*) FROM users WHERE role = 'admin'", &[])
|
.query_one("SELECT COUNT(*) FROM users WHERE role = 'admin'", &[])
|
||||||
@ -113,11 +107,10 @@ pub async fn register(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let password_hash = password::hash_password(&password)
|
let password_hash = password::hash_password(&password).map_err(|e| {
|
||||||
.map_err(|e| {
|
tracing::error!("Register password hash failed: {:?}", e);
|
||||||
tracing::error!("Register password hash failed: {:?}", e);
|
ServerFnError::new(format!("密码哈希失败: {}", e))
|
||||||
ServerFnError::new(format!("密码哈希失败: {}", e))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let result = client
|
let result = client
|
||||||
.query_one(
|
.query_one(
|
||||||
@ -148,17 +141,11 @@ pub async fn register(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[server(Login, "/api")]
|
#[server(Login, "/api")]
|
||||||
pub async fn login(
|
pub async fn login(username: String, password: String) -> Result<AuthResponse, ServerFnError> {
|
||||||
username: String,
|
let client = DB_POOL.get().await.map_err(|e| {
|
||||||
password: String,
|
tracing::error!("Login DB connection failed: {:?}", e);
|
||||||
) -> Result<AuthResponse, ServerFnError> {
|
ServerFnError::new(format!("数据库连接失败: {}", e))
|
||||||
let client = DB_POOL
|
})?;
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
tracing::error!("Login DB connection failed: {:?}", e);
|
|
||||||
ServerFnError::new(format!("数据库连接失败: {}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let row = match client
|
let row = match client
|
||||||
.query_opt(
|
.query_opt(
|
||||||
@ -182,11 +169,10 @@ pub async fn login(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let password_hash: String = row.get("password_hash");
|
let password_hash: String = row.get("password_hash");
|
||||||
let valid = password::verify_password(&password, &password_hash)
|
let valid = password::verify_password(&password, &password_hash).map_err(|e| {
|
||||||
.map_err(|e| {
|
tracing::error!("Login password verify failed: {:?}", e);
|
||||||
tracing::error!("Login password verify failed: {:?}", e);
|
ServerFnError::new(format!("密码验证失败: {}", e))
|
||||||
ServerFnError::new(format!("密码验证失败: {}", e))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
return Ok(AuthResponse {
|
return Ok(AuthResponse {
|
||||||
@ -242,21 +228,16 @@ pub async fn logout() -> Result<AuthResponse, ServerFnError> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = DB_POOL
|
let client = DB_POOL.get().await.map_err(|e| {
|
||||||
.get()
|
tracing::error!("Logout DB connection failed: {:?}", e);
|
||||||
.await
|
ServerFnError::new(format!("数据库连接失败: {}", e))
|
||||||
.map_err(|e| {
|
})?;
|
||||||
tracing::error!("Logout DB connection failed: {:?}", e);
|
|
||||||
ServerFnError::new(format!("数据库连接失败: {}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// 清除 cookie
|
// 清除 cookie
|
||||||
if let Some(ctx) = dioxus::fullstack::FullstackContext::current() {
|
if let Some(ctx) = dioxus::fullstack::FullstackContext::current() {
|
||||||
ctx.add_response_header(
|
ctx.add_response_header(
|
||||||
SET_COOKIE,
|
SET_COOKIE,
|
||||||
HeaderValue::from_static(
|
HeaderValue::from_static("session=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax"),
|
||||||
"session=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,13 +282,10 @@ pub async fn get_current_user() -> Result<CurrentUserResponse, ServerFnError> {
|
|||||||
return Ok(CurrentUserResponse { user: None });
|
return Ok(CurrentUserResponse { user: None });
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = DB_POOL
|
let client = DB_POOL.get().await.map_err(|e| {
|
||||||
.get()
|
tracing::error!("GetCurrentUser DB connection failed: {:?}", e);
|
||||||
.await
|
ServerFnError::new(format!("数据库连接失败: {}", e))
|
||||||
.map_err(|e| {
|
})?;
|
||||||
tracing::error!("GetCurrentUser DB connection failed: {:?}", e);
|
|
||||||
ServerFnError::new(format!("数据库连接失败: {}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let row = client
|
let row = client
|
||||||
.query_opt(
|
.query_opt(
|
||||||
|
|||||||
@ -40,9 +40,13 @@ fn main() {
|
|||||||
.serve_dioxus_application(config, router::AppRouter)
|
.serve_dioxus_application(config, router::AppRouter)
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http()
|
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_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)
|
Ok(router)
|
||||||
|
|||||||
@ -42,6 +42,14 @@ pub struct Post {
|
|||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ pub fn Posts() -> Element {
|
|||||||
thead {
|
thead {
|
||||||
tr { class: "border-b border-gray-200 dark:border-[#333] text-left text-gray-500 dark:text-[#9b9c9d]",
|
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", "标题" }
|
||||||
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-32", "日期" }
|
||||||
th { class: "px-4 py-3 font-medium w-24 text-right", "操作" }
|
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<i32>) -> Element
|
|||||||
.unwrap_or_else(|| post.created_at.format("%Y-%m-%d").to_string());
|
.unwrap_or_else(|| post.created_at.format("%Y-%m-%d").to_string());
|
||||||
|
|
||||||
let (status_label, status_class) = if post.status == PostStatus::Published {
|
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 {
|
} 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! {
|
rsx! {
|
||||||
@ -125,7 +131,7 @@ fn PostRow(post: Post, deleting: bool, on_delete: EventHandler<i32>) -> Element
|
|||||||
"{post.title}"
|
"{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}",
|
span { class: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {status_class}",
|
||||||
"{status_label}"
|
"{status_label}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,7 +120,16 @@ pub fn Write() -> Element {
|
|||||||
error.set(None);
|
error.set(None);
|
||||||
|
|
||||||
spawn(async move {
|
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, .. }) => {
|
Ok(CreatePostResponse { success: true, .. }) => {
|
||||||
saving.set(false);
|
saving.set(false);
|
||||||
success.set(true);
|
success.set(true);
|
||||||
@ -131,7 +140,11 @@ pub fn Write() -> Element {
|
|||||||
}
|
}
|
||||||
let _ = dioxus::router::navigator().push("/admin");
|
let _ = dioxus::router::navigator().push("/admin");
|
||||||
}
|
}
|
||||||
Ok(CreatePostResponse { success: false, message, .. }) => {
|
Ok(CreatePostResponse {
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
saving.set(false);
|
saving.set(false);
|
||||||
error.set(Some(message));
|
error.set(Some(message));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,11 @@ pub fn Register() -> Element {
|
|||||||
Ok(AuthResponse { success: true, .. }) => {
|
Ok(AuthResponse { success: true, .. }) => {
|
||||||
success.set(true);
|
success.set(true);
|
||||||
}
|
}
|
||||||
Ok(AuthResponse { success: false, message, .. }) => {
|
Ok(AuthResponse {
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
error.set(Some(message));
|
error.set(Some(message));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user