use module_auth::AuthUser; use crate::state::{AppState, PuzzleApiState}; 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, } /// 中文注释:作品作者的真相源是 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, ) } pub fn resolve_puzzle_work_author_by_user_id( state: &PuzzleApiState, 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 { 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 )); } }