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)
75 lines
2.7 KiB
TOML
75 lines
2.7 KiB
TOML
[package]
|
|
name = "yggdrasil"
|
|
version = "0.2.0"
|
|
edition = "2021"
|
|
|
|
[dependencies]
|
|
dioxus = { version = "0.7.9", features = ["fullstack", "router"] }
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
tokio = { version = "1.52", features = ["rt-multi-thread", "macros", "fs", "time", "sync"], optional = true }
|
|
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"], optional = true }
|
|
deadpool-postgres = { version = "0.14", optional = true }
|
|
argon2 = "0.5"
|
|
uuid = { version = "1", features = ["v4", "js"] }
|
|
chrono = { version = "0.4", features = ["serde"] }
|
|
regex = "1.12"
|
|
pulldown-cmark = "0.13"
|
|
dotenvy = { version = "0.15", optional = true }
|
|
tracing = { version = "0.1", optional = true }
|
|
tracing-subscriber = { version = "0.3", optional = true }
|
|
tower-http = { version = "0.6", features = ["trace"], optional = true }
|
|
rand = { version = "0.8", features = ["getrandom"] }
|
|
getrandom = { version = "0.2", features = ["js"] }
|
|
http = "1"
|
|
axum = { version = "0.8", optional = true, features = ["multipart"] }
|
|
serde_json = "1.0"
|
|
sha2 = "0.10"
|
|
hex = "0.4"
|
|
ammonia = { version = "4", optional = true }
|
|
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "default-fancy", "html", "parsing", "dump-load", "yaml-load"], optional = true }
|
|
# NOTE: WebP decoder is intentionally excluded from the image crate.
|
|
# We use zenwebp for both encoding and decoding to ensure consistency.
|
|
# Do NOT add "webp" to the features list without updating src/webp.rs.
|
|
image = { version = "0.25", optional = true, default-features = false, features = ["jpeg", "png", "gif"] }
|
|
zenwebp = { version = "0.3", optional = true }
|
|
moka = { version = "0.12", features = ["future"], optional = true }
|
|
governor = { version = "0.8", optional = true }
|
|
md-5 = { version = "0.10", optional = true }
|
|
|
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
web-sys = { version = "0.3", features = ["Document", "Window", "Storage", "Element", "DomTokenList", "MediaQueryList", "HtmlImageElement", "MouseEvent", "KeyboardEvent", "Node", "EventTarget", "Navigator"] }
|
|
wasm-bindgen = "0.2"
|
|
wasm-bindgen-futures = "0.4"
|
|
js-sys = "0.3"
|
|
|
|
[dev-dependencies]
|
|
tokio = { version = "1.52", features = ["rt-multi-thread", "macros", "fs", "time", "sync"] }
|
|
|
|
[profile.release]
|
|
debug = false
|
|
opt-level = 3
|
|
lto = "thin"
|
|
codegen-units = 1
|
|
|
|
[features]
|
|
default = ["web", "server"]
|
|
web = ["dioxus/web"]
|
|
server = [
|
|
"dioxus/server",
|
|
"dep:tokio",
|
|
"dep:tokio-postgres",
|
|
"dep:deadpool-postgres",
|
|
"dep:dotenvy",
|
|
"dep:tracing",
|
|
"dep:tracing-subscriber",
|
|
"dep:tower-http",
|
|
"dep:ammonia",
|
|
"dep:syntect",
|
|
"dep:axum",
|
|
"dep:image",
|
|
"dep:zenwebp",
|
|
"dep:moka",
|
|
"dep:governor",
|
|
"dep:md-5",
|
|
]
|