84f6223c68
feat(markdown): 评论支持数学公式渲染
...
- comments/markdown.rs: 开启 ENABLE_MATH,InlineMath/DisplayMath 事件
渲染成 katex HTML 注入事件流(评论不用块级 <p> 包裹,保持紧凑)
- sanitizer.rs: 评论路径 span 白名单加 style(KaTeX 内联定位 style 需保留,
与文章正文 sanitizer.rs:382 对齐)
- 新增 3 个评论公式测试(内联/块级/style 保留)
双 target 编译通过,24 个评论测试 + 36 个 sanitizer 测试全过。
2026-07-16 14:12:20 +08:00
5a88531543
refactor(api): 收敛 mod.rs 逐行 allow 为文件级 #![allow]
...
api/posts/mod.rs(7处) 与 api/comments/mod.rs(3处) 在 pub use 行上方逐行加 #[allow(unused_imports)],而文件顶部已有 #![allow(clippy::unused_unit, deprecated)]。这些 allow 经核实真实必要(消费侧 server fn 多仅在 WASM 路径,SSR 编译时 unused),故收敛为 #![allow(..., unused_imports)] 等价替代,删掉 10 处逐行 attribute。clippy 严格模式 -D warnings 通过。
2026-07-15 18:16:49 +08:00
65a7b1226f
style: 全项目格式规范化(cargo fmt)+ Docker daemon 断连容错
...
- 全项目 .rs 统一 cargo fmt 排版(换行/缩进/参数分行/导入排序)
- infra/docker: DOCKER_CLIENT 连接失败不 panic,降级返回 None
(博客不依赖 Docker,panic=abort 下会导致整个进程崩溃)
- infra/mod: 去除多余空行
- database/mod: 模块声明按字母序重新排序
2026-07-15 17:22:06 +08:00
fade4e180f
refactor: 删除死代码 CommentActions 组件
...
components/comments/actions.rs (CommentActions) 零引用——评论管理
操作(通过/垃圾/删除)已在 pages/admin/comments.rs 用 BTN_SOLID_*
常量实现,actions.rs 是遗留的重复实现。
原计划将它的内联按钮样式提取为 BTN_PILL_* 常量统一,但确认零引用后
直接删除整个文件更干净。trash_comment re-export 补 allow(unused_imports)
(wasm-only import 在 server 构建触发的已知模式)。
2026-07-14 15:09:41 +08:00
22750ef89e
refactor(api): 为 Response 类型添加构造器,消除 51 处样板
...
CreatePostResponse 新增 err()/ok()/ok_msg() 构造器,消除 posts/
下 34 处 { success, message, post_id, slug } 字面量构造。
CommentResponse 新增 error()/ok()/created() 构造器,消除 comments/
下 17 处 { success, message, error_code, comment_id, ... } 字面量构造。
构造器只在 server function body 内调用,WASM 端因 cfg gate 剥离了
调用点,故加 #[cfg_attr(not(feature="server"), allow(dead_code))]。
行为完全不变(505 测试全过),代码量净减约 300 行样板。
2026-07-14 14:36:56 +08:00
16caa7dd9e
fix(comments): 统一 escape_html,修复代码块单引号未转义
...
comments/markdown.rs 的本地 html_escape 只转义 4 个字符(漏单引号),
与规范实现 utils::html::escape_html(5 字符,'→')不一致。
删除本地副本,改用 crate::utils::html::escape_html。评论代码块里的
单引号现在会被正确转义,与其他上下文的转义行为统一。
2026-07-14 14:29:53 +08:00
b39afbb616
refactor: 删除死代码 (delayed_loading.rs / ui.rs EmptyState / 未用 re-export)
...
- 删除 src/hooks/delayed_loading.rs:未在 hooks/mod.rs 注册,已被
DelayedSkeleton 组件取代(mod.rs 注释已说明)
- 删除 src/components/ui.rs 的 EmptyState:零调用者,所有实际用法
都走功能更全的 components/empty_state.rs::EmptyState
- 删除 api/posts/mod.rs 末尾的 render_markdown_enhanced / slug 工具
re-export:调用方均使用完整路径 crate::api::{markdown,slug}::*,
这两个跨模块短路径无引用
- 删除 api/comments/mod.rs 的 render_comment_markdown re-export:
调用方用完整路径 crate::api::comments::markdown::render_comment_markdown
保留 auth.rs::invalidate_user_sessions:有完整文档说明的预留基础设施
(角色变更/封禁场景),非遗留垃圾。
2026-07-14 14:27:31 +08:00
dcdd72a8c2
refactor(utils): 拆分 comment_storage 的时间/HTML 工具函数
...
消除 server 端对 hooks 模块的跨层引用:
- escape_html(+ 两份重复实现:comment_storage 用 '、helpers 用 ')
统一到 utils/html.rs,单引号采用 HTML5 标准 '。
- now_millis / relative_label_from_millis / format_relative_time_iso 及其
单元测试迁到 utils/time.rs,与既有 sleep_ms 同构(wasm32/非 wasm 双版本)。
调用方迁移:
- api/comments/helpers.rs:删掉本地 escape_html,format_relative_time 改引
utils::time;其 escape_html 测试随之删除(utils/html.rs 已覆盖)。
- api/comments/create.rs:两处 helpers::escape_html 改 utils::html。
- api/markdown.rs:escape_heading_text 改 utils::html。
- components/comments/pending_item.rs:format_relative_time_iso 改从
utils::time 引入;render_pending_content 留在 comment_storage(语义紧贴
待审核评论)。
- hooks/comment_storage.rs:is_expired 的 now_millis 改引 utils::time。
comment_storage.rs 现在只剩结构体与 localStorage 读写,文件名即将自洽。
2026-07-02 16:15:23 +08:00
373498870a
style: apply cargo fmt to workspace
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
d3082d5c64
refactor(comment): share relative-time bucketing between server and client
...
提取相对时间分档逻辑,服务端与前端共享同一份实现
- comment_storage.rs 新增 relative_label_from_millis / format_relative_time_iso:
按 delta 毫秒分档(刚刚/N 分钟前/N 小时前/N 天前/超过 30 天显示日期),
返回 (相对文本, 绝对日期 YYYY-MM-DD);解析失败兜底为空串/刚刚,不 panic
- helpers.rs 的 format_relative_time 重构为调用共享函数,消除服务端预渲染
与前端实时计算两份分档逻辑的口径漂移风险
- 补充 8 个单元测试覆盖各分档边界与异常输入
测试: cargo test relative (18 passed)
2026-06-22 14:18:36 +08:00
10ef52bede
fix(comment): harden comment form with server honeypot, a11y labels, and reply layout
...
CI / check (push) Failing after 5m20s
CI / build (push) Has been skipped
四项评论区输入框修复:
1. 蜜罐服务端二次校验
- helpers.rs 新增 validate_comment_honeypot 校验器 + 单元测试
- create_comment 加 honeypot 参数,在限流后做服务端校验
- 即便机器人禁用 JS 绕过前端拦截,服务端仍返回 spam_detected 错误
- 与 rate limit 共同构成纵深防御
2. label/input 关联 (可访问性)
- 四个字段补 id + r#for 属性,用 id_suffix(顶层 root/回复用 parent_id)保证页面唯一
- textarea 原本无 label,补上"内容 *"label
3. 提交按钮右对齐 + 宽度自适应
- 新常量 COMMENT_SUBMIT_CLASS 去掉 w-full、px-4 改为 px-6
- 外层包 flex justify-end,按钮跟随文字宽度
- login/register 的全宽按钮保持不变(模态卡片场景)
4. 深层回复表单回到内容区左边缘
- CommentForm 新增 parent_indent prop,回复时用负 margin 抵消父评论缩进
- 避免 depth 越深表单被越挤越右
验证: cargo test (376 passed) / cargo clippy --all-targets (无警告) / dx check (无问题)
2026-06-22 13:45:28 +08:00
c7d8a5e67f
fix(comments): use advisory lock to fully eliminate concurrent duplicate submissions
...
Review 发现:仅靠普通 SELECT+事务在 Read Committed 下无法阻止并发重复(两个
事务都看不到对方未提交的 INSERT)。改用 pg_advisory_xact_lock 以 content_hash
派生的 key 加事务级排他锁,使相同内容的并发请求排队,第二个查重必然命中第一个。
- 查重前加 pg_advisory_xact_lock(hashtext 前16位)
- Markdown 渲染/IP/UA 提取等纯计算移出事务,缩短关键排他锁窗口
- 注释准确描述:从「缩小窗口」改为「彻底消除并发重复」
2026-06-18 14:15:19 +08:00
8f288c60da
fix(comments): rate-limit check_pending_status to prevent status enumeration
...
该接口供访客轮询自己刚提交评论的审核状态,故不加 admin 鉴权(会破坏合法
轮询);改为加 strict 限流(对 unknown IP 降级宽松桶),阻止批量枚举评论
状态(L3)。复用 check_strict_limit,与其它敏感接口一致。
2026-06-18 13:43:57 +08:00
82ab190e0d
fix(comments): make duplicate-check atomic with transaction; index content_hash
...
查重 SELECT 与 INSERT 包进同一事务,串行化并发请求缩小重复窗口(M4);
重复时 rollback 空事务。content_hash 加索引(非唯一,避免误杀不同作者
发相同短内容的合法场景)加速 5 分钟窗口查重,原先全表扫。
2026-06-18 13:31:37 +08:00
9986d1ce4e
perf(auth): compile email regex once via LazyLock
...
auth.rs 与 comments/helpers.rs 原先每次校验都 Regex::new 重新编译。
改为 LazyLock 全局静态,正则只编译一次。
2026-06-18 11:25:15 +08:00
07cb0a5b0d
refactor(comments): stop invalidating removed comment count cache in updates
2026-06-17 11:45:49 +08:00
a7fb8405c3
refactor(comments): stop invalidating removed comment count cache
2026-06-17 11:43:16 +08:00
a4954a6c1b
refactor(comments): remove unused CommentCountResponse and PendingCommentsResponse
2026-06-17 11:39:12 +08:00
fc9fce1f4d
refactor(comments): remove dead re-exports from comments module
2026-06-17 11:30:14 +08:00
472d8e91fa
refactor(comments): remove unused get_comment_count server function
2026-06-17 11:26:50 +08:00
0545412cf3
refactor(comments): remove unused get_pending_comments server function
2026-06-17 11:23:00 +08:00
449a545886
security: fix critical issues from repository review
...
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
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
2dcd300930
build: allow dead_code on CommentCountResponse for WASM builds
2026-06-16 17:12:35 +08:00
cafbddb861
refactor: replace server-only dead_code allows with cfg(feature = "server")
...
Replace #[allow(dead_code)] on server-only helpers with #[cfg(feature = "server")]
to make the server/WASM split explicit and avoid compiling unused server logic
into the WASM frontend.
- Gate password/session/auth/comment helpers and model parsers with server feature
- Gate related imports and tests accordingly
- Remove genuinely unused CreatePostRequest and CreateCommentRequest
- Keep #[allow(dead_code)] for true dead code (stub methods, unused public APIs)
- Use #[cfg(any(target_arch = "wasm32", test))] for THEME_KEY
- Update AGENTS.md note
2026-06-16 16:45:08 +08:00
c60e4e08d7
fix(build): 用 cfg gate 消除 WASM 构建的 27 个假阳性 warning
...
CI / check (push) Failing after 5m17s
CI / build (push) Has been skipped
Dioxus fullstack 在 WASM 构建中会剥离 #[server] 函数体,导致仅在
函数体内使用的导入/函数/常量被误报为 unused / dead_code。
- 仅在服务端使用的导入加 #[cfg(feature="server")]:get_conn、
PostStatus/PostStats/Tag、password/session、User/UserRole、
slug.rs 的 dioxus::prelude(保留 ServerFnError 给 server-gated
的 ensure_unique_slug)
- 仅在服务端使用的项加 cfg gate:clamp_pagination 及其常量、
rebuild 常量、format_relative_time、compute_content_hash、
CommentStatus::from_str
- CommentCountResponse/PendingCommentsResponse 出现在 #[server]
签名中(WASM 保留),无法 gate,改用 #[allow(dead_code)],
与现有 CreateCommentRequest 处理一致
make build 零 warning,cargo test 299 passed。
2026-06-16 11:29:18 +08:00
f7288cc390
style: 格式化测试代码以符合 clippy 宽度限制
2026-06-15 10:40:00 +08:00
9921f8eebf
docs(comments): 补充中文注释
2026-06-12 18:42:19 +08:00
4fe26f7eb3
test: improve unit test coverage and assertions
...
CI / check (push) Failing after 15m51s
CI / build (push) Has been skipped
- Add tests for sanitizer, mime_to_ext, clean_tags, Theme::toggle
- Tighten assertions in highlight, markdown, post status classes
- Add boundary and XSS cases for comments, slug, rate_limit, webp
- Update Cargo.lock for serial_test dev-dependency
2026-06-12 18:13:51 +08:00
942ac853fe
refactor: tighten module-level allow attributes
2026-06-12 17:26:46 +08:00
294d60afab
style: format rust code
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
2026-06-12 17:14:31 +08:00
a10bf8737c
refactor(sanitizer): extract shared sanitizer module and migrate from ammonia to lol_html
2026-06-12 11:15:42 +08:00
bd9e87128d
perf(ssr): optimize request throughput by 32%
...
- Cache ammonia::Builder with LazyLock (was rebuilt per request)
- Enable tracing release_max_level_info to strip tracing overhead at compile time
- Remove TraceLayer and tower-http trace feature from production
- Increase DB pool size 10→20 (configurable via DB_POOL_SIZE)
- Increase SSR cache TTL 300s→3600s (configurable via SSR_CACHE_SECS)
Benchmark: 7,444 → 9,840 req/s, P99 latency 27.6ms → 11.1ms
2026-06-12 09:36:53 +08:00
569eec5bf8
fix(admin): 评论管理页多项修复
...
- 修复默认头像无法显示:AdminComment 添加 avatar_url 字段
- 修复状态标签换行:移除 w-20 固定宽度,添加 whitespace-nowrap
- 操作按钮添加 cursor-pointer
- 已处于该状态的评论隐藏对应操作按钮
- 修复操作后页面不自动刷新:将 restart() 移到异步块内
2026-06-11 16:10:42 +08:00
7b3d894e96
feat(api): add CheckPendingStatus server function
...
Accepts Vec<i64> of comment IDs, returns their current status.
IDs not found in DB return status 'gone'. Empty vec returns early.
Used by client to prune localStorage pending comments that are
no longer pending (approved/spam/trash/deleted).
2026-06-11 14:18:49 +08:00
d63cee58c2
feat(api): return comment_id, avatar_url, depth from create_comment
...
- Add comment_id/avatar_url/depth Option fields to CommentResponse with serde default
- Extract RETURNING id from INSERT in create.rs
- Compute gravatar_url server-side (md5 not available in WASM)
- Return computed depth for correct client-side pending comment indentation
- All error paths return None for all new fields
2026-06-11 14:13:14 +08:00
59d5b9222a
refactor(comments): remove privacy consent checkbox and consented param
2026-06-11 13:22:57 +08:00
04737300e6
feat(comments): add complete comment system with guest commenting, moderation, and admin UI
...
Implements a fully self-built comment system for the blog:
Data layer:
- comments table with BIGSERIAL PK, parent_id self-reference (ON DELETE SET NULL),
depth tracking (max 20), status workflow (pending/approved/spam/trash),
content hashing for dedup, GDPR consent tracking, IP/UA storage with auto-purge
- 5 partial indexes optimized for read patterns
- updated_at auto-trigger
API (9 Dioxus server functions):
- Public: get_comments, get_comment_count, create_comment
- Admin: get_pending_comments, get_pending_count, get_all_comments,
approve_comment (with ancestor auto-approval), spam_comment, trash_comment,
batch_update_comment_status
Security:
- Function-level rate limiting (1/sec, burst 5) via FullstackContext IP extraction
- Input validation (name, email, URL scheme, content length, consent)
- Parent chain validation (must be approved, same post)
- Strict comment Markdown renderer (headings→strong, no img/id/data URIs, nofollow links)
- Honeypot anti-spam field
- 5-minute dedup window via SHA-256 content hash
Frontend:
- CommentSection with SuspenseBoundary isolation
- Flat-list rendering with depth-based CSS indentation (responsive)
- Gravatar via cravatar.cn (server-computed, email never exposed)
- Inline reply forms (one-at-a-time via Signal)
- Admin action buttons (approve/spam/delete) visible per-comment
- CommentForm with privacy consent, Markdown hint, loading states
Admin:
- /admin/comments page with status tabs, batch operations, pagination
- Pending count badge on admin dashboard
Infrastructure:
- Shared get_current_admin_user moved from posts/helpers to auth module
- COMMENT_LIMITER rate limiter tier
- Moka caches (60s TTL for comments, 10s for pending count)
- IP/UA purge background task (daily, 90-day retention)
2026-06-11 12:34:26 +08:00