xfy 449a545886
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
security: fix critical issues from repository review
P0 blockers:
- Fix migration numbering conflict and duplicate indexes
- Change comments.post_id FK to ON DELETE CASCADE
- Restrict public post detail endpoint to published posts only
- Fix rate-limiting IP extraction and fallback to ConnectInfo
- Harden HTML sanitizer: deny unknown URL schemes, restrict data URIs
- Remove session token from login response body
- Enforce image pixel/dimension limits on upload and serving

P1 high-risk:
- Validate uploads by magic bytes and decode GIF/WebP
- Add pagination/rate-limiting to search, tag posts, and comments
- Make first-admin registration and slug uniqueness check atomic
- HTML-escape comment author fields
- Improve HTML minify cache key and skip admin/error responses
- Add mobile navigation menu

P2 accessibility/quality:
- Associate form labels with inputs
- Key PostDetail article by slug to re-init scripts on navigation
- Improve image viewer keyboard accessibility
- Make theme toggle SSR-friendly and add aria-label
- Invalidate slug 404 cache on create and pending count on new comment
- Deduplicate tags case-insensitively

P3 cleanup:
- Remove unused tower-http dependency, expand make clean
- Configure DB pool timeouts and verified recycling
- Run background cleanup tasks immediately on startup
- Use SHA-256 for stable disk cache keys
- Log DB errors with Display instead of Debug
- Update README migration instructions

All tests pass (321), clippy clean, dx check clean.
2026-06-17 10:34:14 +08:00

88 lines
3.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 前端评论读取接口:已审核评论列表与评论计数。
//!
//! 结果按文章 id 缓存Dioxus server function 注册在 `/api` 路径下。
//! 仅在 `feature = "server"` 启用的服务端构建中查询数据库。
use crate::api::comments::types::*;
use dioxus::prelude::*;
/// 获取指定文章的已审核评论列表。
///
/// 优先命中缓存;按 id 升序返回,便于前端构建嵌套树。
#[server(GetComments, "/api")]
pub async fn get_comments(post_id: i32) -> Result<CommentTreeResponse, ServerFnError> {
#[cfg(feature = "server")]
{
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;
return Ok(CommentTreeResponse {
comments: cached,
count,
});
}
let client = get_conn().await.map_err(AppError::db_conn)?;
let rows = client
.query(
"SELECT id, parent_id, depth, author_name, author_email, author_url, content_html, created_at \
FROM comments \
WHERE post_id = $1 AND status = 'approved' AND deleted_at IS NULL \
AND EXISTS (SELECT 1 FROM posts p WHERE p.id = $1 AND p.status = 'published' AND p.deleted_at IS NULL) \
ORDER BY id ASC \
LIMIT 200",
&[&post_id],
)
.await
.map_err(AppError::query)?;
let comments: Vec<_> = rows.iter().map(row_to_public_comment).collect();
let count = comments.len() as i64;
cache::set_comments_by_post(post_id, comments.clone()).await;
Ok(CommentTreeResponse { comments, count })
}
#[cfg(not(feature = "server"))]
unreachable!()
}
/// 获取指定文章的已审核评论数量。
///
/// 优先命中缓存;未命中时执行 COUNT 查询并写入缓存。
#[server(GetCommentCount, "/api")]
pub async fn get_comment_count(post_id: i32) -> Result<CommentCountResponse, ServerFnError> {
#[cfg(feature = "server")]
{
use crate::api::error::AppError;
use crate::cache;
use crate::db::pool::get_conn;
if let Some(cached) = cache::get_comment_count(post_id).await {
return Ok(CommentCountResponse { count: cached });
}
let client = get_conn().await.map_err(AppError::db_conn)?;
let count: i64 = client
.query_one(
"SELECT COUNT(*) FROM comments WHERE post_id = $1 AND status = 'approved' AND deleted_at IS NULL",
&[&post_id],
)
.await
.map_err(AppError::query)?
.get(0);
cache::set_comment_count(post_id, count).await;
Ok(CommentCountResponse { count })
}
#[cfg(not(feature = "server"))]
unreachable!()
}