26 Commits

Author SHA1 Message Date
xfy
5743c7a581 feat(upload): 上传图片按内容 SHA-256 去重,重复上传复用已登记素材
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
assets 新增 content_hash 列(唯一索引,迁移 016;存量行保持 NULL
不参与去重)。上传在安全性校验后、转码前对原始字节算 SHA-256:
命中即刷新 created_at/updated_at(重启 7 天清理保护窗)并返回
已有 URL,跳过 GIF/WebP 验证、转码与落盘。并发同内容上传由唯一
索引 + INSERT ... ON CONFLICT DO NOTHING 兜底,落败者删落盘文件
复用胜出者路径。命中响应带 reused: true。仅精确去重,视觉相似
检测(pHash)有意不做。
2026-07-27 13:25:26 +08:00
xfy
fa40f4bd95 fix(assets): uuid 参数序列化失败导致上传 500
tokio-postgres 将 $1::uuid 的参数推断为 uuid 类型,String 无法
序列化(缺类型桥接),INSERT assets 报 error serializing parameter 0,
补偿删文件后返回 500。list/delete/purge/rebuild 的 $1::uuid 与
ANY($1::uuid[]) 全是同一颗未引爆的雷。

正路:tokio-postgres 启用 with-uuid-1,SQL 侧去掉全部 ::uuid cast,
Rust 侧全程 Uuid 类型,仅在 serde DTO 边界(WASM 共享模型)转 String;
外部传入的 id 在边界 parse_str,非法 id 走业务错误而非 500。
2026-07-24 17:04:35 +08:00
xfy
0c811efd4d feat(assets): 素材注册表数据层
- migration 015:assets 元数据注册表 + asset_refs 引用关联表
- upload_image 落盘后登记 assets(失败补偿删文件);尺寸校验复用
  upload_dimensions 一次解析同时返回 (w,h)
- sync_asset_refs 镜像 sync_tags:文章 create/update 事务内
  解析 content_html + cover_image 的 /uploads/ 路径重建引用
- Asset/AssetDto/AssetFilter/AssetSort 共享模型,id 以 String 承载
  (SQL 侧 ::uuid cast,避免 uuid crate 进 WASM 构建)
2026-07-24 16:07:07 +08:00
xfy
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
xfy
604febde98 perf(upload): 消除 data.to_vec() 多次全文件深拷贝
data 是引用计数的 Bytes,clone 廉价(原子 +1)。
- validate 路径:data.to_vec() → data.clone(),省一次全文件拷贝
- 转码闭包:original_data = data.to_vec() → data.clone(),
  仅在「保留原格式」回退分支才 to_vec() 出落盘所需 Vec<u8>
- 路径拼接:用 chrono DelayedFormat 内联,省 year/month/day 三个中间 String

5MB 图片上传:转码成功路径从 10MB 堆分配降到 0(仅 Bytes 引用计数);
回退路径从 10MB 降到 5MB。
2026-07-15 11:28:21 +08:00
xfy
6837b4cfa2 refactor(image): 合并维度读取函数,共享 image_reader_limits
提取 read_webp_dimensions / read_image_dimensions 两个核心函数,
read_dimensions_by_mime 与 read_dimensions_from_bytes 各自只负责
格式映射(MIME→format / 扩展名→format),消除 zenwebp 调用与
into_dimensions 逻辑的双份实现。

附带收紧:read_dimensions_from_bytes 原用 with_guessed_format() 猜测
格式,现改为按扩展名显式匹配,避免猜测错误。

image_reader_limits() 改为 pub(crate),upload.rs 的内联 limits 构造
(4 行逐字重复)改为调用共享函数。
2026-07-14 15:13:21 +08:00
xfy
804bcfb7b7 refactor(upload): 抽取 upload_error() 消除 15 处 JSON 错误响应样板
upload.rs 中 14 处 return Err((StatusCode::X, Json(json!({"success":
false, "error": MSG})))) + 1 处 map_err 同款 tuple,全部替换为
upload_error(status, msg) 单行调用。helper 接受 impl Serialize,
兼容 &str / String / 字面量等调用形式。
2026-07-14 14:53:18 +08:00
xfy
373498870a style: apply cargo fmt to workspace
Some checks failed
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
xfy
ff2694c5a3 feat(image): make size/pixel limits configurable, double defaults
Some checks failed
CI / check (push) Failing after 6m23s
CI / build (push) Has been skipped
将 MAX_IMAGE_DIMENSION / MAX_IMAGE_PIXELS 从编译期 const 改为运行时
LazyLock(启动读环境变量),默认值 x2。

- 默认值:单边 4096→8192,总像素 25M→50M
- clamp 策略:只设下限防危险小值(DIMENSION≥512,PIXELS≥1M),
  超下限 clamp + warn;无上限,完全信任运维
- 内存影响:默认 50M 像素对应 ~200MB/图解码缓冲(max_alloc)
- 模式复用 WEBP_CONFIG:env→parse→(val,clamped)→clamp→warn→info
- 所有引用点解引用(*MAX_...),含 upload.rs/webp.rs 跨文件
- check_upload_dimensions 超限文案改为动态读取上限,不再硬编码数值
- 测试改用 *MAX_IMAGE_DIMENSION+1,数值无关

.env.example 与 AGENTS.md 同步更新环境变量说明。
2026-06-24 13:54:36 +08:00
xfy
0777fd469a fix(upload): unify image size limit across all formats
上传入口此前对超大图片行为不一致:JPEG/PNG 因 decode limits 失败后
静默存原图,WebP 报"损坏",GIF 完全不查。统一为 header 阶段硬拒绝。

- image.rs: 新增 check_upload_dimensions,按 MIME 只读 header 拿尺寸
  (WebP 走 zenwebp header,JPEG/PNG/GIF 走 image into_dimensions),
  超 MAX_IMAGE_DIMENSION/MAX_IMAGE_PIXELS 返回友好提示
- upload.rs: magic bytes 校验后插入统一尺寸校验,三种格式同路径
- upload.rs: JPEG/PNG 转码 fallback 的 Err(_) 改为 Err(e) 带原因日志
  (超限已在 header 阶段拦截,此处仅剩真损坏兜底)

读取侧 serve_image 不动;webp::decode 内的像素校验保留(读取侧仍需)。
2026-06-24 13:25:23 +08:00
xfy
67212a52b3 fix(upload): make ConnectInfo optional to prevent 500 in release builds
Some checks failed
CI / check (push) Failing after 26m31s
CI / build (push) Has been skipped
upload_image declared ConnectInfo<SocketAddr> as a required Axum extractor,
but dioxus::server::serve() owns the listener and cannot call
into_make_service_with_connect_info::<SocketAddr>(), so the request extension
is absent on manually-merged routes. The extractor failed and Axum returned
500. dev (dx serve) passed by luck; release builds failed consistently.

Match serve_image's graceful-degradation pattern: take
Option<Extension<ConnectInfo<SocketAddr>>>, fall back to the 'unknown'
rate-limit bucket when missing. Production should deploy behind a reverse
proxy with TRUSTED_PROXY_COUNT so rate limiting keys on the real client IP.

Also correct the misleading comment in main.rs: axum 0.8 does have
ConnectInfoLayer/into_make_service_with_connect_info; the real blocker is
that Dioxus owns the listener.
2026-06-18 17:07:07 +08:00
xfy
753525fb41 perf(upload): offload GIF/WebP raw image validation to spawn_blocking
GIF 走 image::load_from_memory 会完整解码动画帧,在 async 上下文阻塞
worker。移到 spawn_blocking,JoinError 兜底任务失败。
2026-06-18 11:19:34 +08:00
xfy
449a545886 security: fix critical issues from repository review
Some checks failed
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
xfy
26b012c40c docs(api, auth): 补充中文注释 2026-06-12 18:27:24 +08:00
xfy
4fe26f7eb3 test: improve unit test coverage and assertions
Some checks failed
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
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
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
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
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
717266db1e fix: resolve conditional compilation and dead code warnings 2026-06-08 16:11:24 +08:00
xfy
371ebcf8f9 fix(upload): gate axum imports behind #[cfg(feature = "server")]
Wrap all axum imports and constants with #[cfg(feature = "server")]
to prevent WASM compilation failures. Provide a no-op stub for
non-server builds.

Fixes: error[E0433]: cannot find module or crate  in scope
2026-06-05 15:08:19 +08:00
xfy
93020a8e14 fix(upload): remove space in filename format and unused UploadResponse struct 2026-06-05 15:00:29 +08:00
xfy
1bae3446e6 feat(api): add image upload handler with admin auth 2026-06-05 14:56:29 +08:00