145 Commits

Author SHA1 Message Date
xfy
942ac853fe refactor: tighten module-level allow attributes 2026-06-12 17:26:46 +08:00
xfy
294d60afab style: format rust code
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
2026-06-12 17:14:31 +08:00
xfy
71ac08c373 feat(rate_limit): derive real client IP from X-Forwarded-For with TRUSTED_PROXY_COUNT 2026-06-12 17:13:17 +08:00
xfy
4ae7b38131 feat(auth): add Secure flag to session cookie via COOKIE_SECURE env 2026-06-12 17:02:22 +08:00
xfy
ee6aaf179c feat(posts): add batch limit and error handling to rebuild_content_html 2026-06-12 11:44:53 +08:00
xfy
a10bf8737c refactor(sanitizer): extract shared sanitizer module and migrate from ammonia to lol_html 2026-06-12 11:15:42 +08:00
xfy
f6589121da perf(markdown): eliminate duplicate clean_html call in generate_toc_html
clean_html() triggers expensive ammonia sanitization with aho_corasick.
Previously called twice for the same text (aria-label + display text).
Now calls once and reuses the result.
2026-06-12 10:36:06 +08:00
xfy
2e9d123396 feat(posts): add rebuild_content_html server function
New admin-only server function that re-renders content_html and
toc_html for posts from their content_md:
- rebuild_all=false: only processes posts where content_html IS NULL
- rebuild_all=true: processes all non-deleted posts
- Processes rows one at a time to avoid table locks
- Invalidates post list and stats caches after rebuild
2026-06-12 10:35:59 +08:00
xfy
a975e45cc9 feat(posts): store toc_html in DB on create and update
- create_post: include toc_html in INSERT alongside content_html
- update_post: include toc_html in UPDATE alongside content_html
- Both write toc_html as NULL when empty, Some(..) otherwise
2026-06-12 10:35:51 +08:00
xfy
acd85d18ce perf(posts): read pre-rendered content_html/toc_html from DB instead of re-rendering
Previously row_to_post_full() ignored the stored content_html and
re-rendered markdown with render_markdown_enhanced() on every read,
triggering expensive ammonia sanitization (aho_corasick accounted for
87% CPU in flame graph).

Now reads content_html and toc_html directly from the database row.
Falls back to render_markdown_enhanced() only for legacy posts where
content_html is NULL.
2026-06-12 10:35:39 +08:00
xfy
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
xfy
569eec5bf8 fix(admin): 评论管理页多项修复
- 修复默认头像无法显示:AdminComment 添加 avatar_url 字段
- 修复状态标签换行:移除 w-20 固定宽度,添加 whitespace-nowrap
- 操作按钮添加 cursor-pointer
- 已处于该状态的评论隐藏对应操作按钮
- 修复操作后页面不自动刷新:将 restart() 移到异步块内
2026-06-11 16:10:42 +08:00
xfy
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
xfy
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
xfy
59d5b9222a refactor(comments): remove privacy consent checkbox and consented param 2026-06-11 13:22:57 +08:00
xfy
358dff152d fix(error): use Debug formatting in AppError logs for full error chain visibility 2026-06-11 13:19:18 +08:00
xfy
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
xfy
ccd564312b feat: hash session tokens and enforce configurable session limit per user 2026-06-11 09:45:56 +08:00
xfy
265eb15887 feat(admin): add pagination to posts management page
- list_posts API now accepts page/per_page params with LIMIT/OFFSET
- Add /admin/posts/:page route with PostsPage component
- Pagination component with prev/next navigation (20 posts/page)
- Update dashboard to use new list_posts(1, 5) signature
2026-06-10 18:03:36 +08:00
xfy
5e2487a067 fix: gate helper imports with #[cfg(feature = "server")] in posts API
Server-only helper functions (get_current_admin_user, row_to_post_list,
row_to_post_full, sync_tags, clean_tags) are defined behind
#[cfg(feature = "server")] in helpers.rs, but their imports in consumer
files were not gated, causing WASM build failures.
2026-06-10 14:49:54 +08:00
xfy
311ddbe204 perf(cache): cache COUNT(*) result separately to avoid redundant queries
- Add TotalPublishedPosts cache key for reusing total count across pages
- list_published_posts now checks total cache before running COUNT(*)
- Add note to get_posts_by_tag about total = posts.len() assumption
- Remove unused invalidate_total_published_posts helper
2026-06-10 14:44:53 +08:00
xfy
44eba24121 feat: return total count from post list server functions 2026-06-10 14:14:04 +08:00
xfy
72c1efa566 feat: add total field to PostListResponse 2026-06-10 13:57:06 +08:00
xfy
b1385efd5e refactor(api): split posts.rs into module directory
- Move posts.rs (891 lines) to posts/ with 11 submodules
- Extract shared tag sync logic into helpers::sync_tags
- Extract tag cleaning into helpers::clean_tags
- All external import paths unchanged via mod.rs re-exports
- Zero behavioral changes, pure structural refactor
2026-06-10 13:45:21 +08:00
xfy
2574e1e315 fix(api): gate AppError helper methods behind server feature
tracing is an optional dependency unavailable in WASM builds.
The constructors are only called from server-side code anyway.
2026-06-10 13:21:37 +08:00
xfy
bf401e4916 fix(api): correct AppError variant semantics for transaction/query errors
- client.transaction() failures: db_conn -> tx (logs correct category)
- auth session INSERT: db_conn -> query
- delete_post UPDATE: tx -> query (no transaction context)
2026-06-10 13:19:57 +08:00
xfy
a3fa602df2 refactor(api): unify error handling with AppError enum
Replace scattered ServerFnError::new("...") inline calls and utils.rs
helper functions with a single AppError enum that provides:
- Automatic tracing::error logging for system errors
- Safe generic messages to frontend (no SQL/connection detail leaks)
- Typed error variants (Unauthorized, Forbidden, NotFound, etc.)
- 5 unit tests for message passthrough and detail hiding

Removes src/api/utils.rs entirely; all error creation now goes through
src/api/error.rs.
2026-06-10 13:16:06 +08:00
xfy
4c534eeb4c fix(posts): invalidate tag and old slug caches on create/update 2026-06-09 17:15:29 +08:00
xfy
91caf3f8bd feat(posts): invalidate caches on write operations 2026-06-09 17:09:17 +08:00
xfy
887aacb5f1 fix(posts): check cache before auth, skip caching None slugs 2026-06-09 17:06:53 +08:00
xfy
4c44b62e3d feat(posts): add caching to read operations 2026-06-09 17:01:12 +08:00
xfy
db3379364f fix: limit WebP decode buffer size to prevent malicious allocations 2026-06-09 15:48:34 +08:00
xfy
7c6bab8019 fix: preserve f32 precision for WebP quality 2026-06-09 15:43:16 +08:00
xfy
2e2be7b16d feat: add WebP encoding support with zenwebp
- Replace image crate's WebP with zenwebp for better quality/speed
- Add webp.rs module with configurable quality and method
- Update .env.example with WEBP_QUALITY and WEBP_METHOD
- Add WebP decode support in image serving pipeline
- Add detailed timing logs for WebP conversion
2026-06-09 15:30:06 +08:00
xfy
5ecd8a9b86 feat: add disk-level cache for processed image variants 2026-06-09 13:45:49 +08:00
xfy
0319c927c6 chore: format code 2026-06-09 13:43:10 +08:00
xfy
b26094835f feat: convert uploaded images to WebP for storage savings 2026-06-09 13:39:36 +08:00
xfy
281da208f5 fix: remove stray space in upload filename format string 2026-06-09 13:37:11 +08:00
xfy
42b39266a6 fix: preserve data URI in img src during markdown rendering
ammonia's default url_schemes whitelist excludes 'data', causing
data:image/* URLs to be stripped from img src attributes. Add 'data'
to allowed schemes. Also skip data: URIs in JS thumbnail logic.
2026-06-09 10:06:34 +08:00
xfy
3974856f3d fix: resolve image/upload 500 errors and refactor rate limiting
- Separate /uploads and /api/upload routes from Dioxus app to avoid
  IncrementalRenderer intercepting non-page requests
- Remove broken SmartIpKeyExtractor-based general_limit() that failed
  under Dioxus dev server proxy (Unable To Extract Key)
- Move rate limiting into handlers using governor::RateLimiter directly
- Add IMAGE_LIMITER for /uploads/* serving
- Make all rate limits configurable via environment variables
- Add rate limit config to .env.example with sensible defaults
2026-06-09 09:48:51 +08:00
xfy
4f368e6fb8 test: add 122 unit tests across 12 modules
Cover utils/text, api/slug, auth/password, auth/session,
models/post, models/user, api/auth validation, api/markdown,
api/image, highlight, api/rate_limit. Add make test target.
2026-06-09 09:25:44 +08:00
959d813630 feat: pg_trgm full-text search for posts (#2)
* feat(db): add pg_trgm search index on posts

* feat(api): use pg_trgm similarity search for posts

* fix(api): use ILIKE + word_similarity instead of % operator for search
2026-06-08 18:26:35 +08:00
xfy
e74b9f3c39 feat: apply rate limiting to Register, Login, and upload endpoints
- Remove unused tags.rs and related re-exports
- Convert strict_limit/upload_limit from Layer to manual check functions
- Add IP-based rate limiting checks to Register, Login, and upload_image
- Keep general_limit as global middleware for all other routes
2026-06-08 17:30:26 +08:00
xfy
76f1b8dd53 feat: add rate limiting configurations 2026-06-08 16:57:48 +08:00
xfy
771b155a88 refactor: split posts.rs and fix N+1 with JOIN+array_agg 2026-06-08 16:53:31 +08:00
xfy
32131377c3 refactor: integrate tags module into posts.rs and mod.rs 2026-06-08 16:45:20 +08:00
xfy
441060e7c2 refactor: extract tag operations into api/tags.rs 2026-06-08 16:44:38 +08:00
xfy
6e4e72b232 refactor: extract markdown rendering into api/markdown.rs 2026-06-08 16:42:55 +08:00
xfy
4c88d5e2bb refactor: extract slug utilities into api/slug.rs 2026-06-08 16:40:44 +08:00
xfy
e5d37aa034 refactor(api): extract error helper functions, DRY up db/query/tx error handling 2026-06-08 16:17:03 +08:00