xfy 294d60afab
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
style: format rust code
2026-06-12 17:14:31 +08:00

186 lines
6.3 KiB
Rust

use dioxus::prelude::*;
#[cfg(feature = "server")]
use super::helpers::{get_current_admin_user, row_to_post_list};
use super::types::PostListResponse;
#[cfg(feature = "server")]
use crate::api::error::AppError;
use crate::db::pool::get_conn;
#[server(ListPublishedPosts, "/api")]
pub async fn list_published_posts(
page: i32,
per_page: i32,
) -> Result<PostListResponse, ServerFnError> {
#[cfg(feature = "server")]
{
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,
});
}
let client = get_conn().await.map_err(AppError::db_conn)?;
// Get total count from cache or query
let total = if let Some(cached_total) = crate::cache::get_total_published_posts().await {
cached_total
} else {
let count_row = client
.query_one(
"SELECT COUNT(*) FROM posts WHERE status = 'published' AND deleted_at IS NULL",
&[],
)
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
crate::cache::set_total_published_posts(total).await;
total
};
let offset = ((page - 1).max(0) as i64) * (per_page as i64);
let limit = per_page as i64;
let rows = client
.query(
"SELECT
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html,
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
FROM posts p
LEFT JOIN post_tags pt ON p.id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.id
WHERE p.status = 'published' AND p.deleted_at IS NULL
GROUP BY p.id
ORDER BY p.published_at DESC
LIMIT $1 OFFSET $2",
&[&limit, &offset],
)
.await
.map_err(AppError::query)?;
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post_list(&client, row).await);
}
crate::cache::set_post_list(&cache_key, posts.clone(), total).await;
Ok(PostListResponse { posts, total })
}
#[cfg(not(feature = "server"))]
{
Ok(PostListResponse {
posts: Vec::new(),
total: 0,
})
}
}
#[server(ListPosts, "/api")]
pub async fn list_posts(page: i32, per_page: i32) -> Result<PostListResponse, ServerFnError> {
let _user = get_current_admin_user().await?;
#[cfg(feature = "server")]
{
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", &[])
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
let offset = ((page - 1).max(0) as i64) * (per_page as i64);
let limit = per_page as i64;
let rows = client
.query(
"SELECT
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html,
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
FROM posts p
LEFT JOIN post_tags pt ON p.id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.id
WHERE p.deleted_at IS NULL
GROUP BY p.id
ORDER BY p.created_at DESC
LIMIT $1 OFFSET $2",
&[&limit, &offset],
)
.await
.map_err(AppError::query)?;
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post_list(&client, row).await);
}
Ok(PostListResponse { posts, total })
}
#[cfg(not(feature = "server"))]
{
Ok(PostListResponse {
posts: Vec::new(),
total: 0,
})
}
}
#[server(GetPostsByTag, "/api")]
pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, ServerFnError> {
#[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,
});
}
let client = get_conn().await.map_err(AppError::db_conn)?;
let rows = client
.query(
"SELECT
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html,
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
COALESCE(array_agg(t2.name) FILTER (WHERE t2.name IS NOT NULL), '{}') as tags
FROM posts p
JOIN post_tags pt ON p.id = pt.post_id
JOIN tags t ON pt.tag_id = t.id
LEFT JOIN post_tags pt2 ON p.id = pt2.post_id
LEFT JOIN tags t2 ON pt2.tag_id = t2.id
WHERE t.name = $1 AND p.status = 'published' AND p.deleted_at IS NULL
GROUP BY p.id
ORDER BY p.published_at DESC",
&[&tag_name],
)
.await
.map_err(AppError::query)?;
let mut posts = Vec::new();
for row in &rows {
posts.push(row_to_post_list(&client, row).await);
}
// NOTE: total = posts.len() is correct because get_posts_by_tag
// currently fetches ALL matching posts (no LIMIT/OFFSET).
// If pagination is added later, switch to a proper COUNT(*) query.
let total = posts.len() as i64;
crate::cache::set_posts_by_tag(&tag_name, posts.clone(), total).await;
Ok(PostListResponse { posts, total })
}
#[cfg(not(feature = "server"))]
{
Ok(PostListResponse {
posts: Vec::new(),
total: 0,
})
}
}