后台用户详情改走只读会员快照
新增只读会员快照 procedure get_profile_membership_snapshot_and_return,不刷新钱包 / 账期、不写库 module-runtime 新增 RuntimeProfileMembershipGetInput 与 RuntimeProfileMembershipSnapshotProcedureResult 及入参构造器 spacetime-client 增加 get_profile_membership_snapshot facade、From 转换与结果映射,并同步生成绑定 build_profile_membership_snapshot 拆成写库与纯读两条路径,共用 from_row 投影 admin_get_user_detail 改用只读会员快照,打开详情不再改用户钱包与会员账期
This commit is contained in:
@@ -162,10 +162,9 @@ pub async fn admin_get_user_detail(
|
||||
fetch_admin_user_cumulative_recharged_cents(&state, &user.id).await;
|
||||
let membership_record = state
|
||||
.spacetime_client()
|
||||
.get_profile_recharge_center(user.id.clone())
|
||||
.get_profile_membership_snapshot(user.id.clone())
|
||||
.await
|
||||
.map_err(|error| spacetime_error_response(&request_context, error))?
|
||||
.membership;
|
||||
.map_err(|error| spacetime_error_response(&request_context, error))?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
|
||||
@@ -345,6 +345,13 @@ pub fn build_runtime_profile_recharge_center_get_input(
|
||||
Ok(RuntimeProfileRechargeCenterGetInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_membership_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfileMembershipGetInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
Ok(RuntimeProfileMembershipGetInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_get_input(
|
||||
order_id: String,
|
||||
) -> Result<RuntimeProfileRechargeOrderGetInput, RuntimeProfileFieldError> {
|
||||
|
||||
@@ -1532,6 +1532,22 @@ pub struct RuntimeProfileMembershipSnapshot {
|
||||
pub cycle_kind: RuntimeProfileMembershipCycleKind,
|
||||
}
|
||||
|
||||
/// 只读会员快照的入参;后台详情等只读入口使用。
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileMembershipGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
/// 只读会员快照 procedure 的结果:成功时 `record` 为会员投影,失败时给出 `error_message`。
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileMembershipSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderSnapshot {
|
||||
|
||||
@@ -72,8 +72,9 @@ use module_runtime::{
|
||||
AnalyticsMetricQueryResponse as DomainAnalyticsMetricQueryResponse,
|
||||
RuntimeProfileDashboardRecord, RuntimeProfileFeedbackSubmissionRecord,
|
||||
RuntimeProfileInviteCodeAdminListRecord, RuntimeProfileInviteCodeRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeProductConfigRecord, RuntimeProfileRedeemCodeAdminListRecord,
|
||||
RuntimeProfileMembershipRecord, RuntimeProfileRechargeCenterRecord,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeProductConfigRecord,
|
||||
RuntimeProfileRedeemCodeAdminListRecord,
|
||||
RuntimeProfileRedeemCodeMode as DomainRuntimeProfileRedeemCodeMode,
|
||||
RuntimeProfileRedeemCodeRecord, RuntimeProfileRewardCodeRedeemRecord,
|
||||
RuntimeProfileTaskCenterRecord, RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord,
|
||||
@@ -87,9 +88,10 @@ use module_runtime::{
|
||||
build_runtime_profile_feedback_submission_record,
|
||||
build_runtime_profile_invite_code_admin_list_input,
|
||||
build_runtime_profile_invite_code_admin_upsert_input, build_runtime_profile_invite_code_record,
|
||||
build_runtime_profile_membership_get_input,
|
||||
build_runtime_profile_membership_plan_admin_list_input,
|
||||
build_runtime_profile_membership_plan_admin_upsert_input,
|
||||
build_runtime_profile_membership_upgrade_quote_input,
|
||||
build_runtime_profile_membership_record, build_runtime_profile_membership_upgrade_quote_input,
|
||||
build_runtime_profile_membership_upgrade_quote_record,
|
||||
build_runtime_profile_recharge_center_get_input, build_runtime_profile_recharge_center_record,
|
||||
build_runtime_profile_recharge_order_close_input,
|
||||
|
||||
@@ -18,6 +18,14 @@ impl From<module_runtime::RuntimeProfileRechargeCenterGetInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileMembershipGetInput> for RuntimeProfileMembershipGetInput {
|
||||
fn from(input: module_runtime::RuntimeProfileMembershipGetInput) -> Self {
|
||||
Self {
|
||||
user_id: input.user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_wallet_ledger_source_type_back(
|
||||
value: crate::module_bindings::RuntimeProfileWalletLedgerSourceType,
|
||||
) -> module_runtime::RuntimeProfileWalletLedgerSourceType {
|
||||
@@ -740,6 +748,20 @@ pub(crate) fn map_runtime_profile_recharge_center_procedure_result(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_membership_snapshot_procedure_result(
|
||||
result: RuntimeProfileMembershipSnapshotProcedureResult,
|
||||
) -> Result<RuntimeProfileMembershipRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
let snapshot = result
|
||||
.record
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("profile membership 快照"))?;
|
||||
Ok(build_runtime_profile_membership_record(
|
||||
map_runtime_profile_membership_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_recharge_order_procedure_result(
|
||||
result: RuntimeProfileRechargeCenterProcedureResult,
|
||||
) -> Result<
|
||||
|
||||
@@ -394,6 +394,34 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// 只读会员快照:不刷新钱包 / 账期,供后台详情等只读入口使用。
|
||||
pub async fn get_profile_membership_snapshot(
|
||||
&self,
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfileMembershipRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_membership_get_input(user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(
|
||||
"get_profile_membership_snapshot_and_return",
|
||||
move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.get_profile_membership_snapshot_and_return_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_membership_snapshot_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_profile_recharge_order(
|
||||
&self,
|
||||
user_id: String,
|
||||
|
||||
@@ -478,6 +478,7 @@ pub mod get_game_distribution_version_and_return_procedure;
|
||||
pub mod get_llm_router_account_and_return_procedure;
|
||||
pub mod get_owner_game_distribution_version_and_return_procedure;
|
||||
pub mod get_profile_dashboard_procedure;
|
||||
pub mod get_profile_membership_snapshot_and_return_procedure;
|
||||
pub mod get_profile_membership_upgrade_quote_procedure;
|
||||
pub mod get_profile_recharge_center_procedure;
|
||||
pub mod get_profile_recharge_order_and_return_procedure;
|
||||
@@ -653,6 +654,7 @@ pub mod runtime_profile_invite_code_admin_upsert_input_type;
|
||||
pub mod runtime_profile_invite_code_snapshot_type;
|
||||
pub mod runtime_profile_membership_change_kind_type;
|
||||
pub mod runtime_profile_membership_cycle_kind_type;
|
||||
pub mod runtime_profile_membership_get_input_type;
|
||||
pub mod runtime_profile_membership_model_access_type;
|
||||
pub mod runtime_profile_membership_order_change_snapshot_type;
|
||||
pub mod runtime_profile_membership_plan_admin_list_input_type;
|
||||
@@ -661,6 +663,7 @@ pub mod runtime_profile_membership_plan_admin_procedure_result_type;
|
||||
pub mod runtime_profile_membership_plan_admin_upsert_input_type;
|
||||
pub mod runtime_profile_membership_plan_record_type;
|
||||
pub mod runtime_profile_membership_plan_type;
|
||||
pub mod runtime_profile_membership_snapshot_procedure_result_type;
|
||||
pub mod runtime_profile_membership_snapshot_type;
|
||||
pub mod runtime_profile_membership_status_type;
|
||||
pub mod runtime_profile_membership_upgrade_quote_get_input_type;
|
||||
@@ -1295,6 +1298,7 @@ pub use get_game_distribution_version_and_return_procedure::get_game_distributio
|
||||
pub use get_llm_router_account_and_return_procedure::get_llm_router_account_and_return;
|
||||
pub use get_owner_game_distribution_version_and_return_procedure::get_owner_game_distribution_version_and_return;
|
||||
pub use get_profile_dashboard_procedure::get_profile_dashboard;
|
||||
pub use get_profile_membership_snapshot_and_return_procedure::get_profile_membership_snapshot_and_return;
|
||||
pub use get_profile_membership_upgrade_quote_procedure::get_profile_membership_upgrade_quote;
|
||||
pub use get_profile_recharge_center_procedure::get_profile_recharge_center;
|
||||
pub use get_profile_recharge_order_and_return_procedure::get_profile_recharge_order_and_return;
|
||||
@@ -1470,6 +1474,7 @@ pub use runtime_profile_invite_code_admin_upsert_input_type::RuntimeProfileInvit
|
||||
pub use runtime_profile_invite_code_snapshot_type::RuntimeProfileInviteCodeSnapshot;
|
||||
pub use runtime_profile_membership_change_kind_type::RuntimeProfileMembershipChangeKind;
|
||||
pub use runtime_profile_membership_cycle_kind_type::RuntimeProfileMembershipCycleKind;
|
||||
pub use runtime_profile_membership_get_input_type::RuntimeProfileMembershipGetInput;
|
||||
pub use runtime_profile_membership_model_access_type::RuntimeProfileMembershipModelAccess;
|
||||
pub use runtime_profile_membership_order_change_snapshot_type::RuntimeProfileMembershipOrderChangeSnapshot;
|
||||
pub use runtime_profile_membership_plan_admin_list_input_type::RuntimeProfileMembershipPlanAdminListInput;
|
||||
@@ -1478,6 +1483,7 @@ pub use runtime_profile_membership_plan_admin_procedure_result_type::RuntimeProf
|
||||
pub use runtime_profile_membership_plan_admin_upsert_input_type::RuntimeProfileMembershipPlanAdminUpsertInput;
|
||||
pub use runtime_profile_membership_plan_record_type::RuntimeProfileMembershipPlanRecord;
|
||||
pub use runtime_profile_membership_plan_type::RuntimeProfileMembershipPlan;
|
||||
pub use runtime_profile_membership_snapshot_procedure_result_type::RuntimeProfileMembershipSnapshotProcedureResult;
|
||||
pub use runtime_profile_membership_snapshot_type::RuntimeProfileMembershipSnapshot;
|
||||
pub use runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
|
||||
pub use runtime_profile_membership_upgrade_quote_get_input_type::RuntimeProfileMembershipUpgradeQuoteGetInput;
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_membership_get_input_type::RuntimeProfileMembershipGetInput;
|
||||
use super::runtime_profile_membership_snapshot_procedure_result_type::RuntimeProfileMembershipSnapshotProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct GetProfileMembershipSnapshotAndReturnArgs {
|
||||
pub input: RuntimeProfileMembershipGetInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for GetProfileMembershipSnapshotAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `get_profile_membership_snapshot_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait get_profile_membership_snapshot_and_return {
|
||||
fn get_profile_membership_snapshot_and_return(&self, input: RuntimeProfileMembershipGetInput) {
|
||||
self.get_profile_membership_snapshot_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn get_profile_membership_snapshot_and_return_then(
|
||||
&self,
|
||||
input: RuntimeProfileMembershipGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileMembershipSnapshotProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl get_profile_membership_snapshot_and_return for super::RemoteProcedures {
|
||||
fn get_profile_membership_snapshot_and_return_then(
|
||||
&self,
|
||||
input: RuntimeProfileMembershipGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileMembershipSnapshotProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileMembershipSnapshotProcedureResult>(
|
||||
"get_profile_membership_snapshot_and_return",
|
||||
GetProfileMembershipSnapshotAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileMembershipGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileMembershipGetInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_membership_snapshot_type::RuntimeProfileMembershipSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileMembershipSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -1619,6 +1619,37 @@ pub fn get_profile_recharge_center(
|
||||
}
|
||||
}
|
||||
|
||||
/// 只读会员快照:不刷新钱包 / 账期、不写库,供后台详情等只读入口使用。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_profile_membership_snapshot_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileMembershipGetInput,
|
||||
) -> RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
let caller = ctx.sender();
|
||||
match ctx.try_with_tx(|tx| {
|
||||
crate::editor_project_storage::require_editor_generation_runtime_service_identity(
|
||||
tx, caller,
|
||||
)?;
|
||||
let validated = build_runtime_profile_membership_get_input(input.user_id.clone())
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(build_profile_membership_snapshot_readonly(
|
||||
tx,
|
||||
&validated.user_id,
|
||||
))
|
||||
}) {
|
||||
Ok(record) => RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileMembershipSnapshotProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_profile_recharge_order_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -9764,17 +9795,38 @@ fn refresh_profile_membership_cycle(ctx: &ReducerContext, user_id: &str, now: Ti
|
||||
);
|
||||
}
|
||||
|
||||
/// 会写库的会员快照:先刷新钱包 / 账期(可能换期、补发每日免费额度),再按库内投影读取。
|
||||
fn build_profile_membership_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
) -> RuntimeProfileMembershipSnapshot {
|
||||
refresh_profile_wallet_expiring_points(ctx, user_id, ctx.timestamp);
|
||||
build_profile_membership_snapshot_readonly(ctx, user_id)
|
||||
}
|
||||
|
||||
/// 纯读会员快照:用只读账期投影替代写库刷新,不落任何表,供后台详情等只读入口使用。
|
||||
///
|
||||
/// 与写库刷新后的对外语义一致:未过期按当前账期投影,已过期按非会员返回;区别只是不触发换期与补发。
|
||||
fn build_profile_membership_snapshot_readonly(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
) -> RuntimeProfileMembershipSnapshot {
|
||||
let now_micros = ctx.timestamp.to_micros_since_unix_epoch();
|
||||
let membership = ctx
|
||||
.db
|
||||
.profile_membership()
|
||||
.user_id()
|
||||
.find(&user_id.to_string());
|
||||
.find(&user_id.to_string())
|
||||
.map(|row| project_profile_membership_cycle_at(ctx, row, ctx.timestamp));
|
||||
build_profile_membership_snapshot_from_row(user_id, membership, now_micros)
|
||||
}
|
||||
|
||||
/// 把会员行(`None` 表示从未开通)映射为对外快照,是写库与纯读两条路径共用的投影。
|
||||
fn build_profile_membership_snapshot_from_row(
|
||||
user_id: &str,
|
||||
membership: Option<ProfileMembership>,
|
||||
now_micros: i64,
|
||||
) -> RuntimeProfileMembershipSnapshot {
|
||||
match membership {
|
||||
Some(row) if row.expires_at.to_micros_since_unix_epoch() > now_micros => {
|
||||
RuntimeProfileMembershipSnapshot {
|
||||
|
||||
Reference in New Issue
Block a user