Files
Genarrative/server-rs/crates/api-server/src/work_author.rs
T
kdletters e9c3dc1120 退役旧创作模板业务
保留 SpacetimeDB 历史表、迁移白名单与旧业务源码
切换前端 active 入口并解除旧创作页面和路由编译链
移除旧后端路由、worker 与纯业务 crate 依赖
收敛 SpacetimeDB 模块为历史数据壳
同步 Nginx、Pingora、验证门禁与架构文档
2026-07-17 22:07:52 +08:00

136 lines
4.3 KiB
Rust

use module_auth::AuthUser;
use crate::state::AppState;
pub const ORPHAN_WORK_OWNER_USER_ID: &str = "wx-openid-placeholder";
pub const ORPHAN_WORK_AUTHOR_DISPLAY_NAME: &str = "失效作者";
pub const ORPHAN_WORK_AUTHOR_PUBLIC_USER_CODE: &str = "SY-00000000";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkAuthorSummary {
pub display_name: String,
pub public_user_code: Option<String>,
}
/// 中文注释:作品作者的真相源是 owner_user_id;历史昵称字段只作为账号资料不可读时的兼容回退。
pub fn resolve_work_author_by_user_id(
state: &AppState,
owner_user_id: &str,
fallback_display_name: Option<&str>,
fallback_public_user_code: Option<&str>,
) -> WorkAuthorSummary {
resolve_work_author_by_user_id_with_service(
state.auth_user_service(),
owner_user_id,
fallback_display_name,
fallback_public_user_code,
)
}
fn resolve_work_author_by_user_id_with_service(
auth_user_service: &module_auth::AuthUserService,
owner_user_id: &str,
fallback_display_name: Option<&str>,
fallback_public_user_code: Option<&str>,
) -> WorkAuthorSummary {
let fallback_display_name =
normalize_optional_text(fallback_display_name).unwrap_or_else(|| "玩家".to_string());
let _fallback_public_user_code = normalize_optional_text(fallback_public_user_code);
let Some(owner_user_id) = normalize_optional_text(Some(owner_user_id)) else {
return orphan_work_author_summary();
};
match auth_user_service.get_user_by_id(&owner_user_id) {
Ok(Some(user)) => map_auth_user_to_work_author_summary(user, fallback_display_name),
Ok(None) | Err(_) => orphan_work_author_summary(),
}
}
fn map_auth_user_to_work_author_summary(
user: AuthUser,
fallback_display_name: String,
) -> WorkAuthorSummary {
WorkAuthorSummary {
display_name: normalize_optional_text(Some(user.display_name.as_str()))
.unwrap_or(fallback_display_name),
public_user_code: normalize_optional_text(Some(user.public_user_code.as_str())),
}
}
fn normalize_optional_text(value: Option<&str>) -> Option<String> {
value
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn orphan_work_author_summary() -> WorkAuthorSummary {
WorkAuthorSummary {
display_name: ORPHAN_WORK_AUTHOR_DISPLAY_NAME.to_string(),
public_user_code: Some(ORPHAN_WORK_AUTHOR_PUBLIC_USER_CODE.to_string()),
}
}
/// 中文注释:运维回填只处理空作者或认证仓储不可再解析的历史 owner_user_id,避免把有效作品误转给占位账号。
#[cfg(test)]
pub fn should_rebind_orphan_work_owner(
auth_user_service: &module_auth::AuthUserService,
owner_user_id: &str,
) -> bool {
let Some(owner_user_id) = normalize_optional_text(Some(owner_user_id)) else {
return true;
};
if owner_user_id == ORPHAN_WORK_OWNER_USER_ID {
return false;
}
!matches!(
auth_user_service.get_user_by_id(&owner_user_id),
Ok(Some(_))
)
}
#[cfg(test)]
mod tests {
use module_auth::{AuthUserService, InMemoryAuthStore};
use super::*;
#[test]
fn orphan_work_author_summary_uses_placeholder_account() {
assert_eq!(
orphan_work_author_summary(),
WorkAuthorSummary {
display_name: "失效作者".to_string(),
public_user_code: Some("SY-00000000".to_string()),
}
);
}
#[test]
fn missing_author_resolves_to_placeholder_account() {
let service = AuthUserService::new(InMemoryAuthStore::default());
let author = resolve_work_author_by_user_id_with_service(
&service,
"user_missing",
Some("历史昵称"),
Some("SY-00000001"),
);
assert_eq!(author, orphan_work_author_summary());
}
#[test]
fn should_rebind_orphan_work_owner_detects_missing_and_empty_author() {
let service = AuthUserService::new(InMemoryAuthStore::default());
assert!(should_rebind_orphan_work_owner(&service, ""));
assert!(should_rebind_orphan_work_owner(&service, "user_missing"));
assert!(!should_rebind_orphan_work_owner(
&service,
ORPHAN_WORK_OWNER_USER_ID
));
}
}