Merge branch 'master' into codex/refine-creation-progress-wooden-fish

This commit is contained in:
2026-05-27 22:45:09 +08:00
10 changed files with 450 additions and 25 deletions

View File

@@ -51,6 +51,7 @@ use crate::{
platform_errors::map_oss_error,
request_context::RequestContext,
state::AppState,
work_author::resolve_work_author_by_user_id,
work_play_tracking::{WorkPlayTrackingDraft, record_work_play_start_after_success},
};
@@ -1015,17 +1016,7 @@ fn resolve_bark_battle_author_display_name_for_record(state: &AppState, value: &
}
fn resolve_bark_battle_author_display_name(state: &AppState, owner_user_id: &str) -> String {
let display_name = if owner_user_id.trim().is_empty() {
None
} else {
state
.auth_user_service()
.get_user_by_id(owner_user_id)
.ok()
.flatten()
.map(|user| user.display_name)
};
normalize_author_display_name(display_name)
resolve_work_author_by_user_id(state, owner_user_id, None, None).display_name
}
fn normalize_author_display_name(display_name: Option<String>) -> String {

View File

@@ -35,6 +35,9 @@ use crate::puzzle_gallery_cache::PuzzleGalleryCache;
use crate::tracking_outbox::TrackingOutbox;
use crate::wechat_pay::{WechatPayClient, map_wechat_pay_init_error};
use crate::wechat_provider::build_wechat_provider;
use crate::work_author::{
ORPHAN_WORK_AUTHOR_DISPLAY_NAME, ORPHAN_WORK_AUTHOR_PUBLIC_USER_CODE, ORPHAN_WORK_OWNER_USER_ID,
};
const ADMIN_ROLE: &str = "admin";
@@ -361,6 +364,14 @@ impl AppState {
)?)?;
let password_entry_service = PasswordEntryService::new(auth_store.clone());
let auth_user_service = AuthUserService::new(auth_store.clone());
auth_user_service
.ensure_orphan_work_owner_user(
ORPHAN_WORK_OWNER_USER_ID,
ORPHAN_WORK_OWNER_USER_ID,
ORPHAN_WORK_AUTHOR_DISPLAY_NAME,
ORPHAN_WORK_AUTHOR_PUBLIC_USER_CODE,
)
.map_err(|error| AppStateInitError::AuthStore(error.to_string()))?;
let phone_auth_service = PhoneAuthService::new(auth_store.clone(), sms_provider);
let wechat_auth_state_service =
WechatAuthStateService::new(auth_store.clone(), config.wechat_state_ttl_minutes);

View File

@@ -2,6 +2,10 @@ 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,
@@ -45,21 +49,15 @@ fn resolve_work_author_by_user_id_with_service(
) -> 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 _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 WorkAuthorSummary {
display_name: fallback_display_name,
public_user_code: fallback_public_user_code,
};
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(_) => WorkAuthorSummary {
display_name: fallback_display_name,
public_user_code: fallback_public_user_code,
},
Ok(None) | Err(_) => orphan_work_author_summary(),
}
}
@@ -80,3 +78,65 @@ fn normalize_optional_text(value: Option<&str>) -> Option<String> {
.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避免把有效作品误转给占位账号。
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));
}
}