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
This commit is contained in:
parent
b7afd12538
commit
cafbddb861
@ -71,7 +71,7 @@ Dioxus 0.7 fullstack project with **two independent gates** — the most common
|
||||
|
||||
**Stub pattern**: `src/db/mod.rs` provides a `DummyPool` when `server` feature is disabled — do not remove.
|
||||
|
||||
**Dead code allowances**: `src/auth/password.rs` and `src/auth/session.rs` carry `#[allow(dead_code)]` because the compiler sees them as unused in WASM builds where server function bodies are stripped.
|
||||
**Server-only helpers**: `src/auth/password.rs`, `src/auth/session.rs`, `src/api/auth.rs`, `src/api/comments/helpers.rs`, and several model helper methods are gated with `#[cfg(feature = "server")]` because they are only called from server function bodies, which are stripped in WASM builds.
|
||||
|
||||
## Dual API Architecture
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ use crate::db::pool::get_conn;
|
||||
use crate::models::user::{User, UserRole};
|
||||
use crate::models::user::PublicUser;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
fn validate_username(username: &str) -> Result<(), String> {
|
||||
if username.len() < 3 || username.len() > 50 {
|
||||
return Err("用户名长度必须在 3-50 字符之间".to_string());
|
||||
@ -34,7 +34,7 @@ fn validate_username(username: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
fn validate_email(email: &str) -> Result<(), String> {
|
||||
let re = regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
|
||||
if !re.is_match(email) {
|
||||
@ -43,7 +43,7 @@ fn validate_email(email: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
fn validate_password(password: &str) -> Result<(), String> {
|
||||
if password.len() < 8 {
|
||||
return Err("密码长度至少 8 位".to_string());
|
||||
@ -387,7 +387,7 @@ pub async fn get_current_admin_user() -> Result<User, AppError> {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "server"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
//! 评论模块的辅助函数:数据转换、校验、哈希与头像生成。
|
||||
//!
|
||||
//! 大部分工具函数仅在 `feature = "server"` 启用的服务端构建中使用;
|
||||
//! 校验函数同时在前端构建中保留签名,避免编译器提示未使用。
|
||||
//! 所有工具函数仅在 `feature = "server"` 启用的服务端构建中使用。
|
||||
|
||||
#![allow(clippy::unused_unit, deprecated)]
|
||||
|
||||
@ -87,7 +86,7 @@ pub fn format_relative_time(dt: chrono::DateTime<chrono::Utc>) -> String {
|
||||
}
|
||||
|
||||
/// 校验评论作者昵称:非空且不超过 50 字符。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn validate_comment_name(name: &str) -> Result<(), String> {
|
||||
let trimmed = name.trim();
|
||||
if trimmed.is_empty() {
|
||||
@ -100,7 +99,7 @@ pub fn validate_comment_name(name: &str) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// 校验评论作者邮箱格式。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn validate_comment_email(email: &str) -> Result<(), String> {
|
||||
let re = regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
|
||||
if !re.is_match(email.trim()) {
|
||||
@ -110,7 +109,7 @@ pub fn validate_comment_email(email: &str) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// 校验评论作者网址:为空时允许,非空时必须以 http:// 或 https:// 开头且不超过 200 字符。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn validate_comment_url(url: &str) -> Result<(), String> {
|
||||
let trimmed = url.trim();
|
||||
if trimmed.is_empty() {
|
||||
@ -127,7 +126,7 @@ pub fn validate_comment_url(url: &str) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// 校验评论内容:非空且不超过 10000 字符。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn validate_comment_content(content: &str) -> Result<(), String> {
|
||||
let trimmed = content.trim();
|
||||
if trimmed.is_empty() {
|
||||
|
||||
@ -3,24 +3,6 @@
|
||||
use crate::models::comment::{AdminComment, PublicComment};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 创建评论请求体(客户端使用)。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct CreateCommentRequest {
|
||||
/// 目标文章 id。
|
||||
pub post_id: i32,
|
||||
/// 父评论 id,顶层评论为 None。
|
||||
pub parent_id: Option<i64>,
|
||||
/// 评论者昵称。
|
||||
pub author_name: String,
|
||||
/// 评论者邮箱。
|
||||
pub author_email: String,
|
||||
/// 评论者个人网址。
|
||||
pub author_url: Option<String>,
|
||||
/// 评论 Markdown 原文。
|
||||
pub content_md: String,
|
||||
}
|
||||
|
||||
/// 创建/审核/删除评论的统一响应结构。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommentResponse {
|
||||
@ -52,13 +34,15 @@ pub struct CommentTreeResponse {
|
||||
|
||||
/// 评论计数响应。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct CommentCountResponse {
|
||||
/// 评论数量。
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
/// 待审核评论列表响应。
|
||||
///
|
||||
/// 当前前端未直接调用 `get_pending_comments`,此类型仅在服务端函数体中构造;
|
||||
/// 保留 `#[allow(dead_code)]` 以避免 WASM 构建中因函数体被剥离而产生的未使用警告。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct PendingCommentsResponse {
|
||||
|
||||
@ -2,26 +2,6 @@
|
||||
|
||||
use crate::models::post::{Post, PostStats, Tag};
|
||||
|
||||
/// 创建/更新文章请求体(客户端使用)。
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct CreatePostRequest {
|
||||
/// 文章标题。
|
||||
pub title: String,
|
||||
/// 自定义 slug,为空时由标题自动生成。
|
||||
pub slug: Option<String>,
|
||||
/// 文章摘要,为空时自动从正文提取。
|
||||
pub summary: Option<String>,
|
||||
/// Markdown 格式正文。
|
||||
pub content_md: String,
|
||||
/// 文章状态(如 draft / published)。
|
||||
pub status: String,
|
||||
/// 标签列表。
|
||||
pub tags: Vec<String>,
|
||||
/// 封面图 URL。
|
||||
pub cover_image: Option<String>,
|
||||
}
|
||||
|
||||
/// 创建/更新/删除文章的统一响应结构。
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CreatePostResponse {
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
//! 密码哈希与校验(Argon2)。
|
||||
//!
|
||||
//! 使用随机 salt 生成 PHC 字符串格式哈希,并通过 Argon2 验证。
|
||||
//! `#[allow(dead_code)]` 用于避免 WASM 构建中服务端函数体被剥离后的未使用警告。
|
||||
//! 仅在 `feature = "server"` 启用的服务端构建中使用。
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
use argon2::{
|
||||
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
#[cfg(feature = "server")]
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
/// 使用 Argon2 对明文密码进行哈希。
|
||||
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
@ -18,7 +20,7 @@ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Er
|
||||
Ok(password_hash.to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
/// 校验明文密码是否与已存储的哈希匹配。
|
||||
pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
|
||||
let parsed_hash = PasswordHash::new(hash)?;
|
||||
@ -30,7 +32,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::passw
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "server"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
@ -4,17 +4,20 @@
|
||||
//! Cookie 包含 HttpOnly、SameSite=Lax 与可选 Secure 标志。
|
||||
//! 服务端上下文解析函数仅在 `feature = "server"` 时可用。
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
#[cfg(feature = "server")]
|
||||
use sha2::{Digest, Sha256};
|
||||
#[cfg(feature = "server")]
|
||||
use uuid::Uuid;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
/// 生成新的随机会话 token(UUID 格式)。
|
||||
pub fn generate_token() -> String {
|
||||
Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
/// 使用 SHA-256 对 token 进行哈希,用于数据库存储。
|
||||
pub fn hash_token(token: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
@ -22,7 +25,7 @@ pub fn hash_token(token: &str) -> String {
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
/// 返回默认会话过期时间(当前时间 + 30 天)。
|
||||
pub fn default_expiry() -> DateTime<Utc> {
|
||||
Utc::now() + Duration::days(30)
|
||||
|
||||
@ -33,7 +33,7 @@ impl CommentStatus {
|
||||
}
|
||||
|
||||
/// 将 CommentStatus 序列化为小写字符串。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Pending => "pending",
|
||||
@ -145,6 +145,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "server")]
|
||||
fn comment_status_from_str() {
|
||||
assert_eq!(CommentStatus::from_str("pending"), CommentStatus::Pending);
|
||||
assert_eq!(CommentStatus::from_str("approved"), CommentStatus::Approved);
|
||||
@ -153,6 +154,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "server")]
|
||||
fn comment_status_from_str_unknown_defaults_to_pending() {
|
||||
assert_eq!(CommentStatus::from_str("unknown"), CommentStatus::Pending);
|
||||
assert_eq!(CommentStatus::from_str(""), CommentStatus::Pending);
|
||||
|
||||
@ -17,7 +17,6 @@ pub enum PostStatus {
|
||||
|
||||
impl PostStatus {
|
||||
/// 将状态序列化为数据库或 API 使用的小写字符串。
|
||||
#[allow(dead_code)]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
PostStatus::Draft => "draft",
|
||||
@ -26,7 +25,7 @@ impl PostStatus {
|
||||
}
|
||||
|
||||
/// 将字符串解析为 PostStatus,无法识别时返回 None。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"draft" => Some(PostStatus::Draft),
|
||||
@ -175,6 +174,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "server")]
|
||||
fn post_status_from_str() {
|
||||
assert_eq!(PostStatus::from_str("draft"), Some(PostStatus::Draft));
|
||||
assert_eq!(
|
||||
@ -192,6 +192,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "server")]
|
||||
fn post_status_roundtrip() {
|
||||
for status in [PostStatus::Draft, PostStatus::Published] {
|
||||
assert_eq!(PostStatus::from_str(status.as_str()), Some(status.clone()));
|
||||
|
||||
@ -17,7 +17,7 @@ pub enum UserRole {
|
||||
|
||||
impl UserRole {
|
||||
/// 将数据库中的角色字符串解析为 UserRole,无法识别时返回 None。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "server")]
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"admin" => Some(UserRole::Admin),
|
||||
@ -89,6 +89,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "server")]
|
||||
fn user_role_from_str() {
|
||||
assert_eq!(UserRole::from_str("admin"), Some(UserRole::Admin));
|
||||
assert_eq!(UserRole::from_str("blocked"), Some(UserRole::Blocked));
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// localStorage 中存储主题值的键名。
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
const THEME_KEY: &str = "yggdrasil-theme";
|
||||
|
||||
/// 应用主题枚举。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user