perf(api): split row_to_post into list and full variants

List pages (home, tags, search, admin) now use row_to_post_list which
reads pre-rendered content_html from DB instead of re-rendering markdown
on every request. Only get_post_by_slug uses row_to_post_full which
includes TOC, anchors, word count, and prev/next navigation.
This commit is contained in:
xfy 2026-06-03 10:29:46 +08:00
parent 19e5a0be41
commit 8146a8a779

View File

@ -569,13 +569,44 @@ async fn get_post_tags(client: &tokio_postgres::Client, post_id: i32) -> Vec<Str
// ============================================================================
#[cfg(feature = "server")]
async fn row_to_post(client: &tokio_postgres::Client, row: &tokio_postgres::Row) -> Post {
async fn row_to_post_list(client: &tokio_postgres::Client, row: &tokio_postgres::Row) -> Post {
let id: i32 = row.get("id");
let role_str: String = row.get("status");
let status = PostStatus::from_str(&role_str).unwrap_or(PostStatus::Draft);
let tags = get_post_tags(client, id).await;
let content_md: String = row.get("content_md");
let word_count = count_words(&content_md);
Post {
id,
author_id: row.get("author_id"),
title: row.get("title"),
slug: row.get("slug"),
summary: row.get("summary"),
content_md,
content_html: row.get("content_html"),
status,
published_at: row.get("published_at"),
created_at: row.get("created_at"),
updated_at: row.get("updated_at"),
tags,
cover_image: row.get("cover_image"),
reading_time: (word_count / 200).max(1),
word_count,
toc_html: None,
prev_post: None,
next_post: None,
}
}
#[cfg(feature = "server")]
async fn row_to_post_full(client: &tokio_postgres::Client, row: &tokio_postgres::Row) -> Post {
let id: i32 = row.get("id");
let role_str: String = row.get("status");
let status = PostStatus::from_str(&role_str).unwrap_or(PostStatus::Draft);
let tags = get_post_tags(client, id).await;
// Get prev/next post info if present in the row
let prev_post = if let Ok(prev_title) = row.try_get::<_, String>("prev_title") {
if let Ok(prev_slug) = row.try_get::<_, String>("prev_slug") {
Some(crate::models::post::PostNav {
@ -602,12 +633,8 @@ async fn row_to_post(client: &tokio_postgres::Client, row: &tokio_postgres::Row)
None
};
// Calculate word count and reading time from content
let content_md: String = row.get("content_md");
let word_count = count_words(&content_md);
let reading_time = (word_count / 200).max(1);
// Generate TOC HTML from content
let rendered = render_markdown_enhanced(&content_md);
Post {
@ -616,7 +643,7 @@ async fn row_to_post(client: &tokio_postgres::Client, row: &tokio_postgres::Row)
title: row.get("title"),
slug: row.get("slug"),
summary: row.get("summary"),
content_md: row.get("content_md"),
content_md,
content_html: Some(rendered.html),
status,
published_at: row.get("published_at"),
@ -624,7 +651,7 @@ async fn row_to_post(client: &tokio_postgres::Client, row: &tokio_postgres::Row)
updated_at: row.get("updated_at"),
tags,
cover_image: row.get("cover_image"),
reading_time,
reading_time: (word_count / 200).max(1),
word_count,
toc_html: if rendered.toc_html.is_empty() {
None
@ -1073,7 +1100,7 @@ pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, Server
})?;
let post = match row {
Some(row) => Some(row_to_post(&client, &row).await),
Some(row) => Some(row_to_post_full(&client, &row).await),
None => None,
};
@ -1109,7 +1136,7 @@ pub async fn list_published_posts(
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post(&client, row).await);
posts.push(row_to_post_list(&client, row).await);
}
Ok(PostListResponse { posts })
@ -1140,7 +1167,7 @@ pub async fn list_posts() -> Result<PostListResponse, ServerFnError> {
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post(&client, row).await);
posts.push(row_to_post_list(&client, row).await);
}
Ok(PostListResponse { posts })
@ -1243,7 +1270,7 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post(&client, row).await);
posts.push(row_to_post_list(&client, row).await);
}
Ok(PostListResponse { posts })
@ -1317,8 +1344,8 @@ pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnErr
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post(&client, row).await);
posts.push(row_to_post_list(&client, row).await);
}
Ok(PostListResponse { posts })
}
}