Add skill for gameplay entry type workflows
This commit is contained in:
@@ -217,6 +217,183 @@ pub fn build_runtime_profile_reward_code_redeem_record(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runtime_profile_beijing_day_key(now_micros: i64) -> i64 {
|
||||
now_micros
|
||||
.saturating_add(PROFILE_TASK_BEIJING_OFFSET_MICROS)
|
||||
.div_euclid(PROFILE_RUNTIME_DAY_MICROS)
|
||||
}
|
||||
|
||||
pub fn build_default_runtime_profile_task_config(
|
||||
updated_at_micros: i64,
|
||||
updated_by: String,
|
||||
) -> RuntimeProfileTaskConfigSnapshot {
|
||||
RuntimeProfileTaskConfigSnapshot {
|
||||
task_id: PROFILE_TASK_ID_DAILY_LOGIN.to_string(),
|
||||
title: PROFILE_TASK_DEFAULT_TITLE_DAILY_LOGIN.to_string(),
|
||||
description: String::new(),
|
||||
event_key: PROFILE_TASK_EVENT_KEY_DAILY_LOGIN.to_string(),
|
||||
cycle: RuntimeProfileTaskCycle::Daily,
|
||||
scope_kind: RuntimeTrackingScopeKind::User,
|
||||
threshold: PROFILE_TASK_DEFAULT_THRESHOLD,
|
||||
reward_points: PROFILE_TASK_DEFAULT_REWARD_POINTS,
|
||||
enabled: true,
|
||||
sort_order: 10,
|
||||
created_by: updated_by.clone(),
|
||||
created_at_micros: updated_at_micros,
|
||||
updated_by,
|
||||
updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_runtime_profile_task_status(
|
||||
enabled: bool,
|
||||
progress_count: u32,
|
||||
threshold: u32,
|
||||
claimed: bool,
|
||||
) -> RuntimeProfileTaskStatus {
|
||||
if !enabled {
|
||||
return RuntimeProfileTaskStatus::Disabled;
|
||||
}
|
||||
if claimed {
|
||||
return RuntimeProfileTaskStatus::Claimed;
|
||||
}
|
||||
if progress_count >= threshold {
|
||||
RuntimeProfileTaskStatus::Claimable
|
||||
} else {
|
||||
RuntimeProfileTaskStatus::Incomplete
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_progress_id(
|
||||
user_id: &str,
|
||||
task_id: &str,
|
||||
day_key: i64,
|
||||
) -> String {
|
||||
format!("{}:{}:{}", user_id.trim(), task_id.trim(), day_key)
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_claim_id(user_id: &str, task_id: &str, day_key: i64) -> String {
|
||||
build_runtime_profile_task_progress_id(user_id, task_id, day_key)
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_reward_ledger_id(
|
||||
user_id: &str,
|
||||
task_id: &str,
|
||||
day_key: i64,
|
||||
) -> String {
|
||||
format!(
|
||||
"task-reward:{}:{}:{}",
|
||||
user_id.trim(),
|
||||
task_id.trim(),
|
||||
day_key
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_runtime_tracking_event_id(
|
||||
event_key: &str,
|
||||
scope_kind: RuntimeTrackingScopeKind,
|
||||
scope_id: &str,
|
||||
occurred_at_micros: i64,
|
||||
) -> String {
|
||||
format!(
|
||||
"tracking:{}:{}:{}:{}",
|
||||
event_key.trim(),
|
||||
scope_kind.as_str(),
|
||||
scope_id.trim(),
|
||||
occurred_at_micros
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_runtime_tracking_daily_stat_id(
|
||||
event_key: &str,
|
||||
scope_kind: RuntimeTrackingScopeKind,
|
||||
scope_id: &str,
|
||||
day_key: i64,
|
||||
) -> String {
|
||||
format!(
|
||||
"tracking-stat:{}:{}:{}:{}",
|
||||
event_key.trim(),
|
||||
scope_kind.as_str(),
|
||||
scope_id.trim(),
|
||||
day_key
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_config_record(
|
||||
snapshot: RuntimeProfileTaskConfigSnapshot,
|
||||
) -> RuntimeProfileTaskConfigRecord {
|
||||
RuntimeProfileTaskConfigRecord {
|
||||
task_id: snapshot.task_id,
|
||||
title: snapshot.title,
|
||||
description: snapshot.description,
|
||||
event_key: snapshot.event_key,
|
||||
cycle: snapshot.cycle,
|
||||
scope_kind: snapshot.scope_kind,
|
||||
threshold: snapshot.threshold,
|
||||
reward_points: snapshot.reward_points,
|
||||
enabled: snapshot.enabled,
|
||||
sort_order: snapshot.sort_order,
|
||||
created_by: snapshot.created_by,
|
||||
created_at: format_utc_micros(snapshot.created_at_micros),
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
updated_by: snapshot.updated_by,
|
||||
updated_at: format_utc_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_item_record(
|
||||
snapshot: RuntimeProfileTaskItemSnapshot,
|
||||
) -> RuntimeProfileTaskItemRecord {
|
||||
RuntimeProfileTaskItemRecord {
|
||||
task_id: snapshot.task_id,
|
||||
title: snapshot.title,
|
||||
description: snapshot.description,
|
||||
event_key: snapshot.event_key,
|
||||
cycle: snapshot.cycle,
|
||||
threshold: snapshot.threshold,
|
||||
progress_count: snapshot.progress_count,
|
||||
reward_points: snapshot.reward_points,
|
||||
status: snapshot.status,
|
||||
day_key: snapshot.day_key,
|
||||
claimed_at: snapshot.claimed_at_micros.map(format_utc_micros),
|
||||
claimed_at_micros: snapshot.claimed_at_micros,
|
||||
updated_at: format_utc_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_center_record(
|
||||
snapshot: RuntimeProfileTaskCenterSnapshot,
|
||||
) -> RuntimeProfileTaskCenterRecord {
|
||||
RuntimeProfileTaskCenterRecord {
|
||||
user_id: snapshot.user_id,
|
||||
day_key: snapshot.day_key,
|
||||
wallet_balance: snapshot.wallet_balance,
|
||||
tasks: snapshot
|
||||
.tasks
|
||||
.into_iter()
|
||||
.map(build_runtime_profile_task_item_record)
|
||||
.collect(),
|
||||
updated_at: format_utc_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_claim_record(
|
||||
snapshot: RuntimeProfileTaskClaimSnapshot,
|
||||
) -> RuntimeProfileTaskClaimRecord {
|
||||
RuntimeProfileTaskClaimRecord {
|
||||
user_id: snapshot.user_id,
|
||||
task_id: snapshot.task_id,
|
||||
day_key: snapshot.day_key,
|
||||
reward_points: snapshot.reward_points,
|
||||
wallet_balance: snapshot.wallet_balance,
|
||||
ledger_entry: build_runtime_profile_wallet_ledger_entry_record(snapshot.ledger_entry),
|
||||
center: build_runtime_profile_task_center_record(snapshot.center),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_redeem_code_record(
|
||||
snapshot: RuntimeProfileRedeemCodeSnapshot,
|
||||
) -> RuntimeProfileRedeemCodeRecord {
|
||||
|
||||
@@ -75,6 +75,121 @@ pub fn build_runtime_profile_wallet_ledger_list_input(
|
||||
Ok(RuntimeProfileWalletLedgerListInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_tracking_event_input(
|
||||
event_id: String,
|
||||
event_key: String,
|
||||
scope_kind: RuntimeTrackingScopeKind,
|
||||
scope_id: String,
|
||||
user_id: Option<String>,
|
||||
owner_user_id: Option<String>,
|
||||
profile_id: Option<String>,
|
||||
module_key: Option<String>,
|
||||
metadata_json: String,
|
||||
occurred_at_micros: i64,
|
||||
) -> Result<RuntimeTrackingEventInput, RuntimeProfileFieldError> {
|
||||
let event_id = normalize_required_string(event_id)
|
||||
.ok_or(RuntimeProfileFieldError::MissingTrackingEventId)?;
|
||||
let event_key =
|
||||
normalize_required_string(event_key).ok_or(RuntimeProfileFieldError::MissingTaskEventKey)?;
|
||||
let scope_id = normalize_required_string(scope_id)
|
||||
.ok_or(RuntimeProfileFieldError::MissingTrackingScopeId)?;
|
||||
let metadata_json = normalize_tracking_metadata_json(metadata_json)?;
|
||||
|
||||
Ok(RuntimeTrackingEventInput {
|
||||
event_id,
|
||||
event_key,
|
||||
scope_kind,
|
||||
scope_id,
|
||||
user_id: normalize_optional_string(user_id),
|
||||
owner_user_id: normalize_optional_string(owner_user_id),
|
||||
profile_id: normalize_optional_string(profile_id),
|
||||
module_key: normalize_optional_string(module_key),
|
||||
metadata_json,
|
||||
occurred_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_center_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfileTaskCenterGetInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
Ok(RuntimeProfileTaskCenterGetInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_claim_input(
|
||||
user_id: String,
|
||||
task_id: String,
|
||||
) -> Result<RuntimeProfileTaskClaimInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
let task_id = normalize_profile_task_id(task_id)?;
|
||||
Ok(RuntimeProfileTaskClaimInput { user_id, task_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_config_admin_list_input(
|
||||
admin_user_id: String,
|
||||
) -> Result<RuntimeProfileTaskConfigAdminListInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
Ok(RuntimeProfileTaskConfigAdminListInput { admin_user_id })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_runtime_profile_task_config_admin_upsert_input(
|
||||
admin_user_id: String,
|
||||
task_id: String,
|
||||
title: String,
|
||||
description: String,
|
||||
event_key: String,
|
||||
cycle: RuntimeProfileTaskCycle,
|
||||
scope_kind: RuntimeTrackingScopeKind,
|
||||
threshold: u32,
|
||||
reward_points: u64,
|
||||
enabled: bool,
|
||||
sort_order: i32,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeProfileTaskConfigAdminUpsertInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
let task_id = normalize_profile_task_id(task_id)?;
|
||||
let title =
|
||||
normalize_required_string(title).ok_or(RuntimeProfileFieldError::MissingTaskTitle)?;
|
||||
let event_key =
|
||||
normalize_required_string(event_key).ok_or(RuntimeProfileFieldError::MissingTaskEventKey)?;
|
||||
if threshold == 0 {
|
||||
return Err(RuntimeProfileFieldError::InvalidTaskThreshold);
|
||||
}
|
||||
if reward_points == 0 || reward_points > i64::MAX as u64 {
|
||||
return Err(RuntimeProfileFieldError::InvalidTaskReward);
|
||||
}
|
||||
|
||||
Ok(RuntimeProfileTaskConfigAdminUpsertInput {
|
||||
admin_user_id,
|
||||
task_id,
|
||||
title,
|
||||
description: normalize_optional_string(Some(description)).unwrap_or_default(),
|
||||
event_key,
|
||||
cycle,
|
||||
scope_kind,
|
||||
threshold,
|
||||
reward_points,
|
||||
enabled,
|
||||
sort_order,
|
||||
updated_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_task_config_admin_disable_input(
|
||||
admin_user_id: String,
|
||||
task_id: String,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeProfileTaskConfigAdminDisableInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
let task_id = normalize_profile_task_id(task_id)?;
|
||||
Ok(RuntimeProfileTaskConfigAdminDisableInput {
|
||||
admin_user_id,
|
||||
task_id,
|
||||
updated_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_wallet_adjustment_input(
|
||||
user_id: String,
|
||||
amount: u64,
|
||||
@@ -200,6 +315,13 @@ pub fn build_runtime_profile_redeem_code_admin_upsert_input(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_redeem_code_admin_list_input(
|
||||
admin_user_id: String,
|
||||
) -> Result<RuntimeProfileRedeemCodeAdminListInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
Ok(RuntimeProfileRedeemCodeAdminListInput { admin_user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_invite_code_admin_upsert_input(
|
||||
admin_user_id: String,
|
||||
invite_code: String,
|
||||
@@ -219,6 +341,13 @@ pub fn build_runtime_profile_invite_code_admin_upsert_input(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_invite_code_admin_list_input(
|
||||
admin_user_id: String,
|
||||
) -> Result<RuntimeProfileInviteCodeAdminListInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
Ok(RuntimeProfileInviteCodeAdminListInput { admin_user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_redeem_code_admin_disable_input(
|
||||
admin_user_id: String,
|
||||
code: String,
|
||||
@@ -509,3 +638,22 @@ pub fn normalize_invite_code_metadata_json(
|
||||
|
||||
serde_json::to_string(&parsed).map_err(|_| RuntimeProfileFieldError::InvalidInviteCodeMetadata)
|
||||
}
|
||||
|
||||
fn normalize_tracking_metadata_json(value: String) -> Result<String, RuntimeProfileFieldError> {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(PROFILE_INVITE_CODE_METADATA_DEFAULT_JSON.to_string());
|
||||
}
|
||||
|
||||
let parsed = serde_json::from_str::<Value>(trimmed)
|
||||
.map_err(|_| RuntimeProfileFieldError::InvalidInviteCodeMetadata)?;
|
||||
if !parsed.is_object() {
|
||||
return Err(RuntimeProfileFieldError::InvalidInviteCodeMetadata);
|
||||
}
|
||||
|
||||
serde_json::to_string(&parsed).map_err(|_| RuntimeProfileFieldError::InvalidInviteCodeMetadata)
|
||||
}
|
||||
|
||||
fn normalize_profile_task_id(value: String) -> Result<String, RuntimeProfileFieldError> {
|
||||
normalize_required_string(value).ok_or(RuntimeProfileFieldError::MissingTaskId)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ pub const PROFILE_REFERRAL_DAILY_INVITER_REWARD_LIMIT: u32 = 10;
|
||||
pub const PROFILE_INVITE_CODE_METADATA_DEFAULT_JSON: &str = "{}";
|
||||
pub const PROFILE_INVITE_CODE_METADATA_MAX_BYTES: usize = 4096;
|
||||
pub const PROFILE_RUNTIME_DAY_MICROS: i64 = 86_400_000_000;
|
||||
pub const PROFILE_TASK_BEIJING_OFFSET_MICROS: i64 = 28_800_000_000;
|
||||
pub const PROFILE_TASK_ID_DAILY_LOGIN: &str = "daily_login";
|
||||
pub const PROFILE_TASK_EVENT_KEY_DAILY_LOGIN: &str = "daily_login";
|
||||
pub const PROFILE_TASK_DEFAULT_TITLE_DAILY_LOGIN: &str = "每日登录";
|
||||
pub const PROFILE_TASK_DEFAULT_REWARD_POINTS: u64 = 10;
|
||||
pub const PROFILE_TASK_DEFAULT_THRESHOLD: u32 = 1;
|
||||
pub const SAVE_SNAPSHOT_VERSION: u32 = 2;
|
||||
pub const DEFAULT_SAVE_ARCHIVE_SUMMARY_TEXT: &str = "继续推进上一次保存的故事。";
|
||||
pub const PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK: &str = "mock";
|
||||
@@ -334,6 +340,226 @@ pub struct RuntimeProfileDashboardGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeTrackingScopeKind {
|
||||
Site,
|
||||
Work,
|
||||
Module,
|
||||
User,
|
||||
}
|
||||
|
||||
impl RuntimeTrackingScopeKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Site => "site",
|
||||
Self::Work => "work",
|
||||
Self::Module => "module",
|
||||
Self::User => "user",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_client_str(value: &str) -> Option<Self> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"site" => Some(Self::Site),
|
||||
"work" => Some(Self::Work),
|
||||
"module" => Some(Self::Module),
|
||||
"user" => Some(Self::User),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileTaskCycle {
|
||||
Daily,
|
||||
}
|
||||
|
||||
impl RuntimeProfileTaskCycle {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Daily => "daily",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_client_str(value: &str) -> Option<Self> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"daily" => Some(Self::Daily),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileTaskStatus {
|
||||
Incomplete,
|
||||
Claimable,
|
||||
Claimed,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl RuntimeProfileTaskStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Incomplete => "incomplete",
|
||||
Self::Claimable => "claimable",
|
||||
Self::Claimed => "claimed",
|
||||
Self::Disabled => "disabled",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeTrackingEventInput {
|
||||
pub event_id: String,
|
||||
pub event_key: String,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub scope_id: String,
|
||||
pub user_id: Option<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub module_key: Option<String>,
|
||||
pub metadata_json: String,
|
||||
pub occurred_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigSnapshot {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: RuntimeProfileTaskCycle,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub threshold: u32,
|
||||
pub reward_points: u64,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_by: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskItemSnapshot {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: RuntimeProfileTaskCycle,
|
||||
pub threshold: u32,
|
||||
pub progress_count: u32,
|
||||
pub reward_points: u64,
|
||||
pub status: RuntimeProfileTaskStatus,
|
||||
pub day_key: i64,
|
||||
pub claimed_at_micros: Option<i64>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskCenterSnapshot {
|
||||
pub user_id: String,
|
||||
pub day_key: i64,
|
||||
pub wallet_balance: u64,
|
||||
pub tasks: Vec<RuntimeProfileTaskItemSnapshot>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskCenterProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskCenterSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskClaimSnapshot {
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
pub day_key: i64,
|
||||
pub reward_points: u64,
|
||||
pub wallet_balance: u64,
|
||||
pub ledger_entry: RuntimeProfileWalletLedgerEntrySnapshot,
|
||||
pub center: RuntimeProfileTaskCenterSnapshot,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskClaimProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskClaimSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskCenterGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskClaimInput {
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigAdminUpsertInput {
|
||||
pub admin_user_id: String,
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: RuntimeProfileTaskCycle,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub threshold: u32,
|
||||
pub reward_points: u64,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigAdminDisableInput {
|
||||
pub admin_user_id: String,
|
||||
pub task_id: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileTaskConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileWalletLedgerSourceType {
|
||||
@@ -346,6 +572,7 @@ pub enum RuntimeProfileWalletLedgerSourceType {
|
||||
AssetOperationRefund,
|
||||
RedeemCodeReward,
|
||||
PuzzleAuthorIncentiveClaim,
|
||||
DailyTaskReward,
|
||||
}
|
||||
|
||||
impl RuntimeProfileWalletLedgerSourceType {
|
||||
@@ -360,6 +587,7 @@ impl RuntimeProfileWalletLedgerSourceType {
|
||||
Self::AssetOperationRefund => "asset_operation_refund",
|
||||
Self::RedeemCodeReward => "redeem_code_reward",
|
||||
Self::PuzzleAuthorIncentiveClaim => "puzzle_author_incentive_claim",
|
||||
Self::DailyTaskReward => "daily_task_reward",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -633,6 +861,12 @@ pub struct RuntimeProfileRedeemCodeAdminDisableInput {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRedeemCodeAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRedeemCodeSnapshot {
|
||||
@@ -656,6 +890,14 @@ pub struct RuntimeProfileRedeemCodeAdminProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRedeemCodeSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileInviteCodeAdminUpsertInput {
|
||||
@@ -665,6 +907,12 @@ pub struct RuntimeProfileInviteCodeAdminUpsertInput {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileInviteCodeAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileInviteCodeSnapshot {
|
||||
@@ -683,6 +931,14 @@ pub struct RuntimeProfileInviteCodeAdminProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileInviteCodeSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralInvitedUserSnapshot {
|
||||
@@ -953,6 +1209,65 @@ pub struct RuntimeProfileRewardCodeRedeemRecord {
|
||||
pub ledger_entry: RuntimeProfileWalletLedgerEntryRecord,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileTaskConfigRecord {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: RuntimeProfileTaskCycle,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub threshold: u32,
|
||||
pub reward_points: u64,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
pub created_at: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_by: String,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileTaskItemRecord {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: RuntimeProfileTaskCycle,
|
||||
pub threshold: u32,
|
||||
pub progress_count: u32,
|
||||
pub reward_points: u64,
|
||||
pub status: RuntimeProfileTaskStatus,
|
||||
pub day_key: i64,
|
||||
pub claimed_at: Option<String>,
|
||||
pub claimed_at_micros: Option<i64>,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileTaskCenterRecord {
|
||||
pub user_id: String,
|
||||
pub day_key: i64,
|
||||
pub wallet_balance: u64,
|
||||
pub tasks: Vec<RuntimeProfileTaskItemRecord>,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileTaskClaimRecord {
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
pub day_key: i64,
|
||||
pub reward_points: u64,
|
||||
pub wallet_balance: u64,
|
||||
pub ledger_entry: RuntimeProfileWalletLedgerEntryRecord,
|
||||
pub center: RuntimeProfileTaskCenterRecord,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRedeemCodeRecord {
|
||||
pub code: String,
|
||||
|
||||
@@ -52,6 +52,18 @@ pub enum RuntimeProfileFieldError {
|
||||
InvalidRedeemCodeReward,
|
||||
InvalidRedeemCodeMaxUses,
|
||||
InvalidInviteCodeMetadata,
|
||||
MissingTaskId,
|
||||
MissingTaskTitle,
|
||||
MissingTaskEventKey,
|
||||
MissingTrackingEventId,
|
||||
MissingTrackingScopeId,
|
||||
InvalidTaskCycle,
|
||||
InvalidTaskScopeKind,
|
||||
InvalidTaskThreshold,
|
||||
InvalidTaskReward,
|
||||
TaskDisabled,
|
||||
TaskNotClaimable,
|
||||
TaskAlreadyClaimed,
|
||||
MissingProductId,
|
||||
MissingWorldKey,
|
||||
MissingBottomTab,
|
||||
@@ -86,6 +98,18 @@ impl std::fmt::Display for RuntimeProfileFieldError {
|
||||
Self::InvalidInviteCodeMetadata => {
|
||||
f.write_str("邀请码 metadata 必须是合法 JSON object")
|
||||
}
|
||||
Self::MissingTaskId => f.write_str("profile_task.task_id 不能为空"),
|
||||
Self::MissingTaskTitle => f.write_str("profile_task.title 不能为空"),
|
||||
Self::MissingTaskEventKey => f.write_str("profile_task.event_key 不能为空"),
|
||||
Self::MissingTrackingEventId => f.write_str("tracking_event.event_id 不能为空"),
|
||||
Self::MissingTrackingScopeId => f.write_str("tracking_event.scope_id 不能为空"),
|
||||
Self::InvalidTaskCycle => f.write_str("profile_task.cycle 无效"),
|
||||
Self::InvalidTaskScopeKind => f.write_str("profile_task.scope_kind 无效"),
|
||||
Self::InvalidTaskThreshold => f.write_str("profile_task.threshold 必须大于 0"),
|
||||
Self::InvalidTaskReward => f.write_str("profile_task.reward_points 必须大于 0"),
|
||||
Self::TaskDisabled => f.write_str("任务已停用"),
|
||||
Self::TaskNotClaimable => f.write_str("任务尚未达成"),
|
||||
Self::TaskAlreadyClaimed => f.write_str("任务奖励已领取"),
|
||||
Self::MissingProductId => f.write_str("recharge.product_id 不能为空"),
|
||||
Self::MissingWorldKey => f.write_str("profile.world_key 不能为空"),
|
||||
Self::MissingBottomTab => f.write_str("runtime_snapshot.bottom_tab 不能为空"),
|
||||
|
||||
@@ -448,6 +448,81 @@ mod tests {
|
||||
RuntimeProfileWalletLedgerSourceType::AssetOperationRefund.as_str(),
|
||||
"asset_operation_refund"
|
||||
);
|
||||
assert_eq!(
|
||||
RuntimeProfileWalletLedgerSourceType::DailyTaskReward.as_str(),
|
||||
"daily_task_reward"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_profile_beijing_day_key_uses_business_day_boundary() {
|
||||
let before_beijing_midnight = 1_714_927_999_999_999;
|
||||
let after_beijing_midnight = 1_714_928_000_000_000;
|
||||
|
||||
assert_eq!(
|
||||
runtime_profile_beijing_day_key(before_beijing_midnight),
|
||||
runtime_profile_beijing_day_key(after_beijing_midnight) - 1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_profile_task_status_matches_progress_and_claim() {
|
||||
assert_eq!(
|
||||
resolve_runtime_profile_task_status(false, 1, 1, false),
|
||||
RuntimeProfileTaskStatus::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_runtime_profile_task_status(true, 0, 1, false),
|
||||
RuntimeProfileTaskStatus::Incomplete
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_runtime_profile_task_status(true, 1, 1, false),
|
||||
RuntimeProfileTaskStatus::Claimable
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_runtime_profile_task_status(true, 1, 1, true),
|
||||
RuntimeProfileTaskStatus::Claimed
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_task_config_input_rejects_invalid_reward_and_threshold() {
|
||||
assert_eq!(
|
||||
build_runtime_profile_task_config_admin_upsert_input(
|
||||
"admin".to_string(),
|
||||
PROFILE_TASK_ID_DAILY_LOGIN.to_string(),
|
||||
"每日登录".to_string(),
|
||||
"".to_string(),
|
||||
PROFILE_TASK_EVENT_KEY_DAILY_LOGIN.to_string(),
|
||||
RuntimeProfileTaskCycle::Daily,
|
||||
RuntimeTrackingScopeKind::User,
|
||||
0,
|
||||
10,
|
||||
true,
|
||||
10,
|
||||
1,
|
||||
)
|
||||
.expect_err("zero threshold should fail"),
|
||||
RuntimeProfileFieldError::InvalidTaskThreshold
|
||||
);
|
||||
assert_eq!(
|
||||
build_runtime_profile_task_config_admin_upsert_input(
|
||||
"admin".to_string(),
|
||||
PROFILE_TASK_ID_DAILY_LOGIN.to_string(),
|
||||
"每日登录".to_string(),
|
||||
"".to_string(),
|
||||
PROFILE_TASK_EVENT_KEY_DAILY_LOGIN.to_string(),
|
||||
RuntimeProfileTaskCycle::Daily,
|
||||
RuntimeTrackingScopeKind::User,
|
||||
1,
|
||||
0,
|
||||
true,
|
||||
10,
|
||||
1,
|
||||
)
|
||||
.expect_err("zero reward should fail"),
|
||||
RuntimeProfileFieldError::InvalidTaskReward
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user