Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -105,10 +105,14 @@ use crate::{
|
||||
},
|
||||
runtime_inventory::get_runtime_inventory_state,
|
||||
runtime_profile::{
|
||||
admin_disable_profile_redeem_code, admin_upsert_profile_invite_code,
|
||||
admin_upsert_profile_redeem_code, create_profile_recharge_order, get_profile_dashboard,
|
||||
admin_disable_profile_redeem_code, admin_disable_profile_task_config,
|
||||
admin_list_profile_invite_codes, admin_list_profile_redeem_codes,
|
||||
admin_list_profile_task_configs, admin_upsert_profile_invite_code,
|
||||
admin_upsert_profile_redeem_code, admin_upsert_profile_task_config,
|
||||
claim_profile_task_reward, create_profile_recharge_order, get_profile_dashboard,
|
||||
get_profile_play_stats, get_profile_recharge_center, get_profile_referral_invite_center,
|
||||
get_profile_wallet_ledger, redeem_profile_referral_invite_code, redeem_profile_reward_code,
|
||||
get_profile_task_center, get_profile_wallet_ledger, redeem_profile_referral_invite_code,
|
||||
redeem_profile_reward_code,
|
||||
},
|
||||
runtime_save::{
|
||||
delete_runtime_snapshot, get_runtime_snapshot, list_profile_save_archives,
|
||||
@@ -157,10 +161,12 @@ pub fn build_router(state: AppState) -> Router {
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/redeem-codes",
|
||||
post(admin_upsert_profile_redeem_code).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_admin_auth,
|
||||
)),
|
||||
get(admin_list_profile_redeem_codes)
|
||||
.post(admin_upsert_profile_redeem_code)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_admin_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/redeem-codes/disable",
|
||||
@@ -171,7 +177,25 @@ pub fn build_router(state: AppState) -> Router {
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/invite-codes",
|
||||
post(admin_upsert_profile_invite_code).route_layer(middleware::from_fn_with_state(
|
||||
get(admin_list_profile_invite_codes)
|
||||
.post(admin_upsert_profile_invite_code)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_admin_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/tasks",
|
||||
get(admin_list_profile_task_configs)
|
||||
.post(admin_upsert_profile_task_config)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_admin_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/tasks/disable",
|
||||
post(admin_disable_profile_task_config).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_admin_auth,
|
||||
)),
|
||||
@@ -1057,6 +1081,20 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/profile/tasks",
|
||||
get(get_profile_task_center).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/profile/tasks/{task_id}/claim",
|
||||
post(claim_profile_task_reward).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/profile/save-archives",
|
||||
get(list_profile_save_archives).route_layer(middleware::from_fn_with_state(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
extract::{Extension, Path, State},
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
@@ -9,15 +9,22 @@ use module_runtime::{
|
||||
RuntimeProfileMembershipBenefitRecord, RuntimeProfileRechargeCenterRecord,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeProductRecord,
|
||||
RuntimeProfileRedeemCodeMode, RuntimeProfileRedeemCodeRecord,
|
||||
RuntimeProfileRewardCodeRedeemRecord, RuntimeProfileWalletLedgerSourceType,
|
||||
RuntimeReferralInviteCenterRecord,
|
||||
RuntimeProfileRewardCodeRedeemRecord, RuntimeProfileTaskCenterRecord,
|
||||
RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord, RuntimeProfileTaskCycle,
|
||||
RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus, RuntimeProfileWalletLedgerSourceType,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::runtime::{
|
||||
AdminDisableProfileRedeemCodeRequest, AdminUpsertProfileInviteCodeRequest,
|
||||
AdminUpsertProfileRedeemCodeRequest, CreateProfileRechargeOrderRequest,
|
||||
CreateProfileRechargeOrderResponse, PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_CONSUME,
|
||||
AdminDisableProfileRedeemCodeRequest, AdminDisableProfileTaskConfigRequest,
|
||||
AdminUpsertProfileInviteCodeRequest, AdminUpsertProfileRedeemCodeRequest,
|
||||
AdminUpsertProfileTaskConfigRequest, ClaimProfileTaskRewardResponse,
|
||||
CreateProfileRechargeOrderRequest, CreateProfileRechargeOrderResponse,
|
||||
PROFILE_TASK_CYCLE_DAILY, PROFILE_TASK_STATUS_CLAIMABLE, PROFILE_TASK_STATUS_CLAIMED,
|
||||
PROFILE_TASK_STATUS_DISABLED, PROFILE_TASK_STATUS_INCOMPLETE,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_CONSUME,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITEE_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITER_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_NEW_USER_REGISTRATION_REWARD,
|
||||
@@ -25,13 +32,17 @@ use shared_contracts::runtime::{
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC, ProfileDashboardSummaryResponse,
|
||||
ProfileInviteCodeAdminResponse, ProfileMembershipBenefitResponse, ProfileMembershipResponse,
|
||||
ProfilePlayStatsResponse, ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse,
|
||||
ProfileRechargeOrderResponse, ProfileRechargeProductResponse, ProfileRedeemCodeAdminResponse,
|
||||
ProfileReferralInviteCenterResponse, ProfileReferralInvitedUserResponse,
|
||||
ProfileInviteCodeAdminListResponse, ProfileInviteCodeAdminResponse,
|
||||
ProfileMembershipBenefitResponse, ProfileMembershipResponse, ProfilePlayStatsResponse,
|
||||
ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse, ProfileRechargeOrderResponse,
|
||||
ProfileRechargeProductResponse, ProfileRedeemCodeAdminListResponse,
|
||||
ProfileRedeemCodeAdminResponse, ProfileReferralInviteCenterResponse,
|
||||
ProfileReferralInvitedUserResponse, ProfileTaskCenterResponse,
|
||||
ProfileTaskConfigAdminListResponse, ProfileTaskConfigAdminResponse, ProfileTaskItemResponse,
|
||||
ProfileWalletLedgerEntryResponse, ProfileWalletLedgerResponse,
|
||||
RedeemProfileReferralInviteCodeRequest, RedeemProfileReferralInviteCodeResponse,
|
||||
RedeemProfileRewardCodeRequest, RedeemProfileRewardCodeResponse,
|
||||
RedeemProfileRewardCodeRequest, RedeemProfileRewardCodeResponse, TRACKING_SCOPE_KIND_MODULE,
|
||||
TRACKING_SCOPE_KIND_SITE, TRACKING_SCOPE_KIND_USER, TRACKING_SCOPE_KIND_WORK,
|
||||
};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
use time::OffsetDateTime;
|
||||
@@ -91,14 +102,7 @@ pub async fn get_profile_wallet_ledger(
|
||||
ProfileWalletLedgerResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(|entry| ProfileWalletLedgerEntryResponse {
|
||||
id: entry.wallet_ledger_id,
|
||||
amount_delta: entry.amount_delta,
|
||||
balance_after: entry.balance_after,
|
||||
source_type: format_profile_wallet_ledger_source_type(entry.source_type)
|
||||
.to_string(),
|
||||
created_at: entry.created_at,
|
||||
})
|
||||
.map(build_profile_wallet_ledger_entry_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
@@ -135,6 +139,9 @@ fn format_profile_wallet_ledger_source_type(
|
||||
RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim => {
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM
|
||||
}
|
||||
RuntimeProfileWalletLedgerSourceType::DailyTaskReward => {
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +277,184 @@ pub async fn redeem_profile_reward_code(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_task_center(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.get_profile_task_center(user_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_profile_task_center_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn claim_profile_task_reward(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Path(task_id): Path<String>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.claim_profile_task_reward(user_id, task_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_claim_profile_task_reward_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_task_configs(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_task_configs(admin.session().subject.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileTaskConfigAdminListResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(build_profile_task_config_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_task_config(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
Json(payload): Json<AdminUpsertProfileTaskConfigRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let cycle = parse_profile_task_cycle(&payload.cycle).map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
|
||||
)
|
||||
})?;
|
||||
let scope_kind = parse_tracking_scope_kind(&payload.scope_kind).map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
|
||||
)
|
||||
})?;
|
||||
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.admin_upsert_profile_task_config(
|
||||
admin.session().subject.clone(),
|
||||
payload.task_id,
|
||||
payload.title,
|
||||
payload.description.unwrap_or_default(),
|
||||
payload.event_key,
|
||||
cycle,
|
||||
scope_kind,
|
||||
payload.threshold,
|
||||
payload.reward_points,
|
||||
payload.enabled,
|
||||
payload.sort_order.unwrap_or(10),
|
||||
updated_at_micros as i64,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_profile_task_config_admin_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_disable_profile_task_config(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
Json(payload): Json<AdminDisableProfileTaskConfigRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.admin_disable_profile_task_config(
|
||||
admin.session().subject.clone(),
|
||||
payload.task_id,
|
||||
updated_at_micros as i64,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_profile_task_config_admin_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_redeem_codes(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_redeem_codes(admin.session().subject.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileRedeemCodeAdminListResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(build_profile_redeem_code_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_redeem_code(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -338,6 +523,33 @@ pub async fn admin_disable_profile_redeem_code(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_invite_codes(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_invite_codes(admin.session().subject.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileInviteCodeAdminListResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(build_profile_invite_code_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_invite_code(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -553,14 +765,87 @@ fn build_redeem_profile_reward_code_response(
|
||||
RedeemProfileRewardCodeResponse {
|
||||
wallet_balance: record.wallet_balance,
|
||||
amount_granted: record.amount_granted,
|
||||
ledger_entry: ProfileWalletLedgerEntryResponse {
|
||||
id: record.ledger_entry.wallet_ledger_id,
|
||||
amount_delta: record.ledger_entry.amount_delta,
|
||||
balance_after: record.ledger_entry.balance_after,
|
||||
source_type: format_profile_wallet_ledger_source_type(record.ledger_entry.source_type)
|
||||
.to_string(),
|
||||
created_at: record.ledger_entry.created_at,
|
||||
},
|
||||
ledger_entry: build_profile_wallet_ledger_entry_response(record.ledger_entry),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_wallet_ledger_entry_response(
|
||||
record: module_runtime::RuntimeProfileWalletLedgerEntryRecord,
|
||||
) -> ProfileWalletLedgerEntryResponse {
|
||||
ProfileWalletLedgerEntryResponse {
|
||||
id: record.wallet_ledger_id,
|
||||
amount_delta: record.amount_delta,
|
||||
balance_after: record.balance_after,
|
||||
source_type: format_profile_wallet_ledger_source_type(record.source_type).to_string(),
|
||||
created_at: record.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_task_center_response(
|
||||
record: RuntimeProfileTaskCenterRecord,
|
||||
) -> ProfileTaskCenterResponse {
|
||||
ProfileTaskCenterResponse {
|
||||
day_key: record.day_key,
|
||||
wallet_balance: record.wallet_balance,
|
||||
tasks: record
|
||||
.tasks
|
||||
.into_iter()
|
||||
.map(build_profile_task_item_response)
|
||||
.collect(),
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_task_item_response(
|
||||
record: RuntimeProfileTaskItemRecord,
|
||||
) -> ProfileTaskItemResponse {
|
||||
ProfileTaskItemResponse {
|
||||
task_id: record.task_id,
|
||||
title: record.title,
|
||||
description: record.description,
|
||||
event_key: record.event_key,
|
||||
cycle: format_profile_task_cycle(record.cycle).to_string(),
|
||||
threshold: record.threshold,
|
||||
progress_count: record.progress_count,
|
||||
reward_points: record.reward_points,
|
||||
status: format_profile_task_status(record.status).to_string(),
|
||||
day_key: record.day_key,
|
||||
claimed_at: record.claimed_at,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_claim_profile_task_reward_response(
|
||||
record: RuntimeProfileTaskClaimRecord,
|
||||
) -> ClaimProfileTaskRewardResponse {
|
||||
ClaimProfileTaskRewardResponse {
|
||||
task_id: record.task_id,
|
||||
day_key: record.day_key,
|
||||
reward_points: record.reward_points,
|
||||
wallet_balance: record.wallet_balance,
|
||||
ledger_entry: build_profile_wallet_ledger_entry_response(record.ledger_entry),
|
||||
center: build_profile_task_center_response(record.center),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_task_config_admin_response(
|
||||
record: RuntimeProfileTaskConfigRecord,
|
||||
) -> ProfileTaskConfigAdminResponse {
|
||||
ProfileTaskConfigAdminResponse {
|
||||
task_id: record.task_id,
|
||||
title: record.title,
|
||||
description: record.description,
|
||||
event_key: record.event_key,
|
||||
cycle: format_profile_task_cycle(record.cycle).to_string(),
|
||||
scope_kind: format_tracking_scope_kind(record.scope_kind).to_string(),
|
||||
threshold: record.threshold,
|
||||
reward_points: record.reward_points,
|
||||
enabled: record.enabled,
|
||||
sort_order: record.sort_order,
|
||||
created_by: record.created_by,
|
||||
created_at: record.created_at,
|
||||
updated_by: record.updated_by,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,6 +882,47 @@ fn parse_profile_redeem_code_mode(raw: &str) -> Result<RuntimeProfileRedeemCodeM
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_profile_task_cycle(raw: &str) -> Result<RuntimeProfileTaskCycle, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
PROFILE_TASK_CYCLE_DAILY => Ok(RuntimeProfileTaskCycle::Daily),
|
||||
_ => Err("任务周期无效".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_tracking_scope_kind(raw: &str) -> Result<RuntimeTrackingScopeKind, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
TRACKING_SCOPE_KIND_SITE => Ok(RuntimeTrackingScopeKind::Site),
|
||||
TRACKING_SCOPE_KIND_WORK => Ok(RuntimeTrackingScopeKind::Work),
|
||||
TRACKING_SCOPE_KIND_MODULE => Ok(RuntimeTrackingScopeKind::Module),
|
||||
TRACKING_SCOPE_KIND_USER => Ok(RuntimeTrackingScopeKind::User),
|
||||
_ => Err("埋点范围无效".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_profile_task_cycle(cycle: RuntimeProfileTaskCycle) -> &'static str {
|
||||
match cycle {
|
||||
RuntimeProfileTaskCycle::Daily => PROFILE_TASK_CYCLE_DAILY,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_profile_task_status(status: RuntimeProfileTaskStatus) -> &'static str {
|
||||
match status {
|
||||
RuntimeProfileTaskStatus::Incomplete => PROFILE_TASK_STATUS_INCOMPLETE,
|
||||
RuntimeProfileTaskStatus::Claimable => PROFILE_TASK_STATUS_CLAIMABLE,
|
||||
RuntimeProfileTaskStatus::Claimed => PROFILE_TASK_STATUS_CLAIMED,
|
||||
RuntimeProfileTaskStatus::Disabled => PROFILE_TASK_STATUS_DISABLED,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_tracking_scope_kind(scope_kind: RuntimeTrackingScopeKind) -> &'static str {
|
||||
match scope_kind {
|
||||
RuntimeTrackingScopeKind::Site => TRACKING_SCOPE_KIND_SITE,
|
||||
RuntimeTrackingScopeKind::Work => TRACKING_SCOPE_KIND_WORK,
|
||||
RuntimeTrackingScopeKind::Module => TRACKING_SCOPE_KIND_MODULE,
|
||||
RuntimeTrackingScopeKind::User => TRACKING_SCOPE_KIND_USER,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_invite_code_admin_response(
|
||||
record: RuntimeProfileInviteCodeRecord,
|
||||
) -> ProfileInviteCodeAdminResponse {
|
||||
@@ -675,6 +1001,12 @@ mod tests {
|
||||
),
|
||||
shared_contracts::runtime::PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM
|
||||
);
|
||||
assert_eq!(
|
||||
format_profile_wallet_ledger_source_type(
|
||||
RuntimeProfileWalletLedgerSourceType::DailyTaskReward
|
||||
),
|
||||
shared_contracts::runtime::PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -713,6 +1045,36 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_tasks_require_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let list_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/profile/tasks")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
let claim_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/profile/tasks/daily_login/claim")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(list_response.status(), StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(claim_response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_play_stats_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
@@ -892,6 +1254,78 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn admin_profile_task_routes_require_admin_authentication() {
|
||||
let app = build_router(
|
||||
AppState::new(admin_enabled_test_config()).expect("state should build"),
|
||||
);
|
||||
|
||||
let list_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/admin/api/profile/tasks")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
let upsert_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/admin/api/profile/tasks")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"taskId":"daily_login"}"#))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
let disable_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/admin/api/profile/tasks/disable")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"taskId":"daily_login"}"#))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(list_response.status(), StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(upsert_response.status(), StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(disable_response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn admin_profile_code_list_routes_require_admin_authentication() {
|
||||
let app = build_router(
|
||||
AppState::new(admin_enabled_test_config()).expect("state should build"),
|
||||
);
|
||||
|
||||
for uri in [
|
||||
"/admin/api/profile/redeem-codes",
|
||||
"/admin/api/profile/invite-codes",
|
||||
] {
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED, "{uri}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn seed_authenticated_state() -> AppState {
|
||||
let state = AppState::new(fast_spacetime_timeout_config()).expect("state should build");
|
||||
state
|
||||
@@ -908,6 +1342,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_enabled_test_config() -> AppConfig {
|
||||
AppConfig {
|
||||
admin_username: Some("root".to_string()),
|
||||
admin_password: Some("secret123".to_string()),
|
||||
..AppConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn issue_access_token(state: &AppState) -> String {
|
||||
let claims = AccessTokenClaims::from_input(
|
||||
AccessTokenClaimsInput {
|
||||
|
||||
@@ -2,15 +2,56 @@ use shared_kernel::{normalize_optional_string, normalize_required_string, normal
|
||||
|
||||
use crate::commands::{default_tags_for_theme, validate_result_publish_fields};
|
||||
use crate::{
|
||||
MATCH3D_BOARD_CENTER, MATCH3D_BOARD_RADIUS, MATCH3D_BOARD_SAFE_MARGIN,
|
||||
MATCH3D_DEFAULT_DURATION_LIMIT_MS, MATCH3D_FRUIT_VISUAL_KEYS, MATCH3D_ITEMS_PER_CLEAR,
|
||||
MATCH3D_MAX_DIFFICULTY, MATCH3D_MIN_DIFFICULTY, MATCH3D_SHAPE_VISUAL_KEYS,
|
||||
MATCH3D_TRAY_SLOT_COUNT, Match3DClickConfirmation, Match3DClickInput, Match3DClickRejectReason,
|
||||
Match3DCreatorConfig, Match3DFailureReason, Match3DFieldError, Match3DItemSnapshot,
|
||||
Match3DItemState, Match3DPublicationStatus, Match3DResultDraft, Match3DRunSnapshot,
|
||||
Match3DRunStatus, Match3DTraySlot, Match3DWorkProfile,
|
||||
MATCH3D_BLOCK_VISUAL_KEYS, MATCH3D_BOARD_CENTER, MATCH3D_BOARD_RADIUS,
|
||||
MATCH3D_BOARD_SAFE_MARGIN, MATCH3D_DEFAULT_DURATION_LIMIT_MS, MATCH3D_ITEMS_PER_CLEAR,
|
||||
MATCH3D_MAX_DIFFICULTY, MATCH3D_MAX_ITEM_TYPE_COUNT, MATCH3D_MIN_DIFFICULTY,
|
||||
MATCH3D_TRAY_SLOT_COUNT, Match3DClickConfirmation, Match3DClickInput,
|
||||
Match3DClickRejectReason, Match3DCreatorConfig, Match3DFailureReason, Match3DFieldError,
|
||||
Match3DItemSnapshot, Match3DItemState, Match3DPublicationStatus, Match3DResultDraft,
|
||||
Match3DRunSnapshot, Match3DRunStatus, Match3DTraySlot, Match3DWorkProfile,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Match3DSizeTierRule {
|
||||
ratio: f32,
|
||||
radius_scale: f32,
|
||||
relative_volume: f32,
|
||||
tier: &'static str,
|
||||
}
|
||||
|
||||
const MATCH3D_SIZE_TIER_RULES: [Match3DSizeTierRule; 5] = [
|
||||
Match3DSizeTierRule {
|
||||
tier: "XL",
|
||||
ratio: 0.20,
|
||||
relative_volume: 1.86,
|
||||
radius_scale: 1.23,
|
||||
},
|
||||
Match3DSizeTierRule {
|
||||
tier: "L",
|
||||
ratio: 0.30,
|
||||
relative_volume: 1.40,
|
||||
radius_scale: 1.12,
|
||||
},
|
||||
Match3DSizeTierRule {
|
||||
tier: "M",
|
||||
ratio: 0.30,
|
||||
relative_volume: 1.00,
|
||||
radius_scale: 1.00,
|
||||
},
|
||||
Match3DSizeTierRule {
|
||||
tier: "XS",
|
||||
ratio: 0.15,
|
||||
relative_volume: 0.73,
|
||||
radius_scale: 0.90,
|
||||
},
|
||||
Match3DSizeTierRule {
|
||||
tier: "S",
|
||||
ratio: 0.05,
|
||||
relative_volume: 0.44,
|
||||
radius_scale: 0.76,
|
||||
},
|
||||
];
|
||||
|
||||
pub fn compile_result_draft(config: &Match3DCreatorConfig) -> Match3DResultDraft {
|
||||
let game_name = format!("{}抓大鹅", config.theme_text);
|
||||
let summary = format!(
|
||||
@@ -268,17 +309,18 @@ fn build_initial_items(
|
||||
) -> Vec<Match3DItemSnapshot> {
|
||||
let mut rng = DeterministicRng::new(seed ^ ((clear_count as u64) << 32) ^ difficulty as u64);
|
||||
let base_radius = resolve_item_radius(difficulty);
|
||||
let visual_keys = visual_keys_for_theme(theme_text);
|
||||
let selected_visual_keys = select_visual_keys(&mut rng, theme_text, clear_count);
|
||||
let item_type_count = resolve_item_type_count(clear_count);
|
||||
let size_tier_plan = resolve_size_tier_plan(item_type_count);
|
||||
let mut items = Vec::with_capacity((clear_count * MATCH3D_ITEMS_PER_CLEAR) as usize);
|
||||
|
||||
for clear_index in 0..clear_count {
|
||||
let visual_index = (clear_index as usize) % visual_keys.len();
|
||||
let visual_index = (clear_index as usize) % item_type_count;
|
||||
let item_type_id = format!("match3d-type-{:02}", visual_index + 1);
|
||||
let visual_key = visual_keys[visual_index].to_string();
|
||||
let visual_key = selected_visual_keys[visual_index].to_string();
|
||||
let radius = resolve_item_radius_variant(base_radius, size_tier_plan[visual_index]);
|
||||
|
||||
for copy_index in 0..MATCH3D_ITEMS_PER_CLEAR {
|
||||
let radius =
|
||||
resolve_item_radius_variant(base_radius, &visual_key, visual_index, copy_index);
|
||||
let (x, y) = random_point_in_circle(&mut rng, max_spawn_offset(radius));
|
||||
let instance_index = clear_index * MATCH3D_ITEMS_PER_CLEAR + copy_index;
|
||||
items.push(Match3DItemSnapshot {
|
||||
@@ -308,22 +350,57 @@ fn build_initial_items(
|
||||
items
|
||||
}
|
||||
|
||||
fn visual_keys_for_theme(theme_text: &str) -> &'static [&'static str; 10] {
|
||||
if is_fruit_theme(theme_text) {
|
||||
&MATCH3D_FRUIT_VISUAL_KEYS
|
||||
} else {
|
||||
&MATCH3D_SHAPE_VISUAL_KEYS
|
||||
fn resolve_size_tier_plan(item_type_count: usize) -> Vec<Match3DSizeTierRule> {
|
||||
let mut plans = MATCH3D_SIZE_TIER_RULES
|
||||
.iter()
|
||||
.map(|rule| {
|
||||
let exact_count = item_type_count as f32 * rule.ratio;
|
||||
(exact_count.floor() as usize, exact_count.fract(), *rule)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut assigned_count = plans
|
||||
.iter()
|
||||
.map(|(count, _, _)| *count)
|
||||
.sum::<usize>();
|
||||
let mut remainder_order = (0..plans.len()).collect::<Vec<_>>();
|
||||
remainder_order.sort_by(|left, right| {
|
||||
plans[*right]
|
||||
.1
|
||||
.partial_cmp(&plans[*left].1)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
let mut cursor = 0;
|
||||
while assigned_count < item_type_count {
|
||||
let plan_index = remainder_order[cursor % remainder_order.len()];
|
||||
plans[plan_index].0 += 1;
|
||||
assigned_count += 1;
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
plans
|
||||
.into_iter()
|
||||
.flat_map(|(count, _, rule)| std::iter::repeat(rule).take(count))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn is_fruit_theme(theme_text: &str) -> bool {
|
||||
let normalized = theme_text.trim().to_lowercase();
|
||||
[
|
||||
"水果", "果蔬", "果物", "fruit", "fruits", "苹果", "香蕉", "葡萄", "西瓜", "草莓", "桃",
|
||||
"李", "柠", "橙", "梨",
|
||||
]
|
||||
.iter()
|
||||
.any(|marker| normalized.contains(marker))
|
||||
fn resolve_item_type_count(clear_count: u32) -> usize {
|
||||
clear_count.clamp(1, MATCH3D_MAX_ITEM_TYPE_COUNT) as usize
|
||||
}
|
||||
|
||||
fn select_visual_keys(
|
||||
rng: &mut DeterministicRng,
|
||||
_theme_text: &str,
|
||||
clear_count: u32,
|
||||
) -> Vec<&'static str> {
|
||||
let item_type_count = resolve_item_type_count(clear_count);
|
||||
let mut visual_keys = MATCH3D_BLOCK_VISUAL_KEYS.to_vec();
|
||||
// 中文注释:只打乱类型池顺序,不改变每个类型三件一组的可通关结构。
|
||||
for index in (1..visual_keys.len()).rev() {
|
||||
let swap_index = (rng.next_u32() as usize) % (index + 1);
|
||||
visual_keys.swap(index, swap_index);
|
||||
}
|
||||
visual_keys.truncate(item_type_count);
|
||||
visual_keys
|
||||
}
|
||||
|
||||
fn resolve_item_radius(difficulty: u32) -> f32 {
|
||||
@@ -332,48 +409,10 @@ fn resolve_item_radius(difficulty: u32) -> f32 {
|
||||
radius.max(0.052)
|
||||
}
|
||||
|
||||
fn resolve_item_radius_variant(
|
||||
base_radius: f32,
|
||||
visual_key: &str,
|
||||
visual_index: usize,
|
||||
copy_index: u32,
|
||||
) -> f32 {
|
||||
let copy_delta = (copy_index as f32 - 1.0) * 0.002;
|
||||
if is_fruit_visual_key(visual_key) {
|
||||
return (base_radius * fruit_visual_size_scale(visual_key) + copy_delta).clamp(0.04, 0.13);
|
||||
}
|
||||
|
||||
let type_delta = ((visual_index % 5) as f32 - 2.0) * 0.004;
|
||||
(base_radius + type_delta + copy_delta).clamp(0.045, 0.12)
|
||||
}
|
||||
|
||||
fn is_fruit_visual_key(visual_key: &str) -> bool {
|
||||
matches!(
|
||||
visual_key,
|
||||
"watermelon-green"
|
||||
| "apple-red"
|
||||
| "banana-yellow"
|
||||
| "grape-purple"
|
||||
| "melon-green"
|
||||
| "berry-blue"
|
||||
| "peach-pink"
|
||||
| "plum-indigo"
|
||||
| "lime-lime"
|
||||
| "orange-orange"
|
||||
| "pear-cyan"
|
||||
)
|
||||
}
|
||||
|
||||
fn fruit_visual_size_scale(visual_key: &str) -> f32 {
|
||||
match visual_key {
|
||||
"watermelon-green" => 1.24,
|
||||
"melon-green" => 1.12,
|
||||
"banana-yellow" => 1.04,
|
||||
"apple-red" | "orange-orange" | "peach-pink" | "pear-cyan" => 1.0,
|
||||
"plum-indigo" | "lime-lime" => 0.86,
|
||||
"grape-purple" | "berry-blue" => 0.78,
|
||||
_ => 1.0,
|
||||
}
|
||||
fn resolve_item_radius_variant(base_radius: f32, size_tier: Match3DSizeTierRule) -> f32 {
|
||||
debug_assert!(!size_tier.tier.is_empty());
|
||||
debug_assert!(size_tier.relative_volume > 0.0);
|
||||
(base_radius * size_tier.radius_scale).clamp(0.045, 0.13)
|
||||
}
|
||||
|
||||
fn max_spawn_offset(radius: f32) -> f32 {
|
||||
@@ -623,6 +662,79 @@ mod tests {
|
||||
assert!(counts.values().all(|count| count % 3 == 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_type_count_follows_clear_count_until_twenty_five() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-types-small".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(12),
|
||||
42,
|
||||
1_000,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let mut counts = BTreeMap::<String, u32>::new();
|
||||
for item in &run.items {
|
||||
*counts.entry(item.item_type_id.clone()).or_default() += 1;
|
||||
}
|
||||
|
||||
assert_eq!(counts.len(), 12);
|
||||
assert!(counts.values().all(|count| *count == 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_key_count_follows_fifteen_clear_count() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-types-fifteen".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(15),
|
||||
42,
|
||||
1_000,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let mut counts = BTreeMap::<String, u32>::new();
|
||||
let mut item_types_by_visual_key = BTreeMap::<String, Vec<String>>::new();
|
||||
for item in &run.items {
|
||||
*counts.entry(item.visual_key.clone()).or_default() += 1;
|
||||
item_types_by_visual_key
|
||||
.entry(item.visual_key.clone())
|
||||
.or_default()
|
||||
.push(item.item_type_id.clone());
|
||||
}
|
||||
|
||||
assert_eq!(counts.len(), 15);
|
||||
assert!(counts.values().all(|count| *count == 3));
|
||||
assert!(item_types_by_visual_key.values().all(|item_type_ids| {
|
||||
item_type_ids
|
||||
.iter()
|
||||
.all(|item_type_id| item_type_id == &item_type_ids[0])
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_type_count_is_capped_at_twenty_five() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-types-large".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(100),
|
||||
42,
|
||||
1_000,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let mut counts = BTreeMap::<String, u32>::new();
|
||||
for item in &run.items {
|
||||
*counts.entry(item.item_type_id.clone()).or_default() += 1;
|
||||
}
|
||||
|
||||
assert_eq!(counts.len(), 25);
|
||||
assert!(counts.values().all(|count| count % 3 == 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_run_uses_slightly_different_item_sizes() {
|
||||
let run = start_run_with_seed_at(
|
||||
@@ -647,9 +759,58 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruit_theme_generates_fruit_visuals_inside_board() {
|
||||
fn size_tier_plan_follows_ratio_for_twenty_five_types() {
|
||||
let plan = resolve_size_tier_plan(25);
|
||||
let mut counts = BTreeMap::<&str, usize>::new();
|
||||
for rule in plan {
|
||||
*counts.entry(rule.tier).or_default() += 1;
|
||||
match rule.tier {
|
||||
"XL" => assert!((1.60..=2.30).contains(&rule.relative_volume)),
|
||||
"L" => assert!((1.25..=1.60).contains(&rule.relative_volume)),
|
||||
"M" => assert_eq!(rule.relative_volume, 1.00),
|
||||
"XS" => assert!((0.65..=0.85).contains(&rule.relative_volume)),
|
||||
"S" => assert!((0.35..=0.50).contains(&rule.relative_volume)),
|
||||
_ => panic!("unknown size tier"),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(counts.get("XL"), Some(&5));
|
||||
assert_eq!(counts.get("L"), Some(&8));
|
||||
assert_eq!(counts.get("M"), Some(&7));
|
||||
assert_eq!(counts.get("XS"), Some(&4));
|
||||
assert_eq!(counts.get("S"), Some(&1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_visual_key_keeps_one_size_in_run() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-fruit".to_string(),
|
||||
"run-size-unique".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(30),
|
||||
42,
|
||||
1_000,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let mut radii_by_visual_key = BTreeMap::<String, Vec<u32>>::new();
|
||||
for item in &run.items {
|
||||
radii_by_visual_key
|
||||
.entry(item.visual_key.clone())
|
||||
.or_default()
|
||||
.push((item.radius * 10_000.0).round() as u32);
|
||||
}
|
||||
|
||||
assert_eq!(radii_by_visual_key.len(), 25);
|
||||
assert!(radii_by_visual_key.values().all(|radii| {
|
||||
radii.iter().all(|radius| radius == &radii[0])
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_visuals_stay_inside_board() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-blocks".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(10),
|
||||
@@ -663,10 +824,7 @@ mod tests {
|
||||
.iter()
|
||||
.map(|item| item.visual_key.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert!(visual_keys.contains(&"watermelon-green"));
|
||||
assert!(visual_keys.contains(&"apple-red"));
|
||||
assert!(visual_keys.contains(&"banana-yellow"));
|
||||
assert!(!visual_keys.contains(&"red_circle"));
|
||||
assert!(visual_keys.iter().all(|visual_key| visual_key.starts_with("block-")));
|
||||
|
||||
for item in &run.items {
|
||||
let dx = item.x - MATCH3D_BOARD_CENTER;
|
||||
@@ -684,38 +842,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruit_theme_uses_common_sense_relative_sizes() {
|
||||
fn twenty_five_or_less_does_not_repeat_visual_keys() {
|
||||
let run = start_run_with_seed_at(
|
||||
"run-fruit-size".to_string(),
|
||||
"run-block-unique".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&test_config(10),
|
||||
&test_config(25),
|
||||
27,
|
||||
1_000,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let max_radius_for_visual = |visual_key: &str| {
|
||||
run.items
|
||||
.iter()
|
||||
.filter(|item| item.visual_key == visual_key)
|
||||
.map(|item| item.radius)
|
||||
.fold(0.0, f32::max)
|
||||
};
|
||||
let mut counts = BTreeMap::<String, u32>::new();
|
||||
for item in &run.items {
|
||||
*counts.entry(item.visual_key.clone()).or_default() += 1;
|
||||
}
|
||||
|
||||
let watermelon = max_radius_for_visual("watermelon-green");
|
||||
let apple = max_radius_for_visual("apple-red");
|
||||
let grape = max_radius_for_visual("grape-purple");
|
||||
|
||||
assert!(watermelon > apple);
|
||||
assert!(apple > grape);
|
||||
assert_eq!(counts.len(), 25);
|
||||
assert!(counts.values().all(|count| *count == 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_fruit_theme_generates_shape_visuals() {
|
||||
fn block_visuals_have_different_relative_sizes() {
|
||||
let config = build_creator_config("玩具", None, 3, 4).expect("config should be valid");
|
||||
let run = start_run_with_seed_at(
|
||||
"run-shapes".to_string(),
|
||||
"run-block-size".to_string(),
|
||||
"user-1".to_string(),
|
||||
"profile-1".to_string(),
|
||||
&config,
|
||||
@@ -724,14 +875,15 @@ mod tests {
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let visual_keys = run
|
||||
let mut radii = run
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.visual_key.as_str())
|
||||
.map(|item| (item.radius * 1_000.0).round() as u32)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(visual_keys.contains(&"red_circle"));
|
||||
assert!(visual_keys.contains(&"yellow_triangle"));
|
||||
assert!(!visual_keys.contains(&"apple-red"));
|
||||
radii.sort();
|
||||
radii.dedup();
|
||||
|
||||
assert!(radii.len() > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -9,6 +9,8 @@ pub const MATCH3D_WORK_ID_PREFIX: &str = "match3d-work-";
|
||||
pub const MATCH3D_RUN_ID_PREFIX: &str = "match3d-run-";
|
||||
pub const MATCH3D_TRAY_SLOT_COUNT: u32 = 7;
|
||||
pub const MATCH3D_ITEMS_PER_CLEAR: u32 = 3;
|
||||
pub const MATCH3D_MAX_ITEM_TYPE_COUNT: u32 = 25;
|
||||
pub(crate) const MATCH3D_MAX_ITEM_TYPE_COUNT_USIZE: usize = 25;
|
||||
pub const MATCH3D_MIN_DIFFICULTY: u32 = 1;
|
||||
pub const MATCH3D_MAX_DIFFICULTY: u32 = 10;
|
||||
pub const MATCH3D_DEFAULT_DURATION_LIMIT_MS: u64 = 10 * 60 * 1000;
|
||||
@@ -16,32 +18,34 @@ pub const MATCH3D_BOARD_CENTER: f32 = 0.5;
|
||||
pub const MATCH3D_BOARD_RADIUS: f32 = 0.5;
|
||||
pub const MATCH3D_BOARD_SAFE_MARGIN: f32 = 0.035;
|
||||
|
||||
// 中文注释:首版 demo 不接真实图片生成,但水果题材必须先给出可辨认的水果内置视觉键。
|
||||
pub(crate) const MATCH3D_FRUIT_VISUAL_KEYS: [&str; 10] = [
|
||||
"watermelon-green",
|
||||
"apple-red",
|
||||
"banana-yellow",
|
||||
"grape-purple",
|
||||
"melon-green",
|
||||
"berry-blue",
|
||||
"peach-pink",
|
||||
"plum-indigo",
|
||||
"lime-lime",
|
||||
"orange-orange",
|
||||
];
|
||||
|
||||
// 中文注释:非水果题材使用颜色形状兜底 key;前端必须逐个渲染,不能统一兜成同一图案。
|
||||
pub(crate) const MATCH3D_SHAPE_VISUAL_KEYS: [&str; 10] = [
|
||||
"red_circle",
|
||||
"yellow_triangle",
|
||||
"purple_diamond",
|
||||
"green_square",
|
||||
"blue_star",
|
||||
"orange_hexagon",
|
||||
"cyan_capsule",
|
||||
"pink_heart",
|
||||
"lime_leaf",
|
||||
"white_moon",
|
||||
// 中文注释:首版 demo 不接真实图片生成,当前先用程序化积木件作为稳定可辨认的默认素材。
|
||||
// 中文注释:当前 demo 使用 25 个积木件作为默认可消除物资源池,前端据 visual_key 程序化生成 3D 模型。
|
||||
pub(crate) const MATCH3D_BLOCK_VISUAL_KEYS: [&str; MATCH3D_MAX_ITEM_TYPE_COUNT_USIZE] = [
|
||||
"block-red-2x4",
|
||||
"block-blue-1x2",
|
||||
"block-yellow-2x2",
|
||||
"block-green-1x4",
|
||||
"block-orange-1x6",
|
||||
"block-white-1x1",
|
||||
"block-black-1x8",
|
||||
"block-tan-2x3",
|
||||
"block-lime-1x2",
|
||||
"block-darkred-2x2",
|
||||
"block-blue-1x4",
|
||||
"block-pink-2x4",
|
||||
"block-gray-1x6",
|
||||
"block-lavender-tile-2x2",
|
||||
"block-teal-tile-1x3",
|
||||
"block-mint-tile-1x4",
|
||||
"block-magenta-tile-2x2",
|
||||
"block-orange-tile-2x2-stud",
|
||||
"block-purple-slope-1x2",
|
||||
"block-brown-slope-1x2",
|
||||
"block-sky-slope-2x2",
|
||||
"block-green-cylinder",
|
||||
"block-clear-ring",
|
||||
"block-mint-arch",
|
||||
"block-gold-cone",
|
||||
];
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -15,6 +15,16 @@ pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND: &str = "asse
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD: &str = "redeem_code_reward";
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM: &str =
|
||||
"puzzle_author_incentive_claim";
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD: &str = "daily_task_reward";
|
||||
pub const PROFILE_TASK_CYCLE_DAILY: &str = "daily";
|
||||
pub const PROFILE_TASK_STATUS_INCOMPLETE: &str = "incomplete";
|
||||
pub const PROFILE_TASK_STATUS_CLAIMABLE: &str = "claimable";
|
||||
pub const PROFILE_TASK_STATUS_CLAIMED: &str = "claimed";
|
||||
pub const PROFILE_TASK_STATUS_DISABLED: &str = "disabled";
|
||||
pub const TRACKING_SCOPE_KIND_SITE: &str = "site";
|
||||
pub const TRACKING_SCOPE_KIND_WORK: &str = "work";
|
||||
pub const TRACKING_SCOPE_KIND_MODULE: &str = "module";
|
||||
pub const TRACKING_SCOPE_KIND_USER: &str = "user";
|
||||
pub const BROWSE_HISTORY_THEME_MODE_MARTIAL: &str = "martial";
|
||||
pub const BROWSE_HISTORY_THEME_MODE_ARCANE: &str = "arcane";
|
||||
pub const BROWSE_HISTORY_THEME_MODE_MACHINA: &str = "machina";
|
||||
@@ -295,6 +305,92 @@ pub struct RedeemProfileRewardCodeResponse {
|
||||
pub ledger_entry: ProfileWalletLedgerEntryResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileTaskItemResponse {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: String,
|
||||
pub threshold: u32,
|
||||
pub progress_count: u32,
|
||||
pub reward_points: u64,
|
||||
pub status: String,
|
||||
pub day_key: i64,
|
||||
pub claimed_at: Option<String>,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileTaskCenterResponse {
|
||||
pub day_key: i64,
|
||||
pub wallet_balance: u64,
|
||||
pub tasks: Vec<ProfileTaskItemResponse>,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClaimProfileTaskRewardResponse {
|
||||
pub task_id: String,
|
||||
pub day_key: i64,
|
||||
pub reward_points: u64,
|
||||
pub wallet_balance: u64,
|
||||
pub ledger_entry: ProfileWalletLedgerEntryResponse,
|
||||
pub center: ProfileTaskCenterResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileTaskConfigAdminResponse {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub event_key: String,
|
||||
pub cycle: String,
|
||||
pub scope_kind: String,
|
||||
pub threshold: u32,
|
||||
pub reward_points: u64,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
pub created_at: String,
|
||||
pub updated_by: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileTaskConfigAdminListResponse {
|
||||
pub entries: Vec<ProfileTaskConfigAdminResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdminUpsertProfileTaskConfigRequest {
|
||||
pub task_id: String,
|
||||
pub title: String,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
pub event_key: String,
|
||||
pub cycle: String,
|
||||
pub scope_kind: String,
|
||||
pub threshold: u32,
|
||||
pub reward_points: u64,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
pub sort_order: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdminDisableProfileTaskConfigRequest {
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdminUpsertProfileRedeemCodeRequest {
|
||||
@@ -339,6 +435,12 @@ pub struct ProfileRedeemCodeAdminResponse {
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileRedeemCodeAdminListResponse {
|
||||
pub entries: Vec<ProfileRedeemCodeAdminResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileInviteCodeAdminResponse {
|
||||
@@ -349,6 +451,12 @@ pub struct ProfileInviteCodeAdminResponse {
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileInviteCodeAdminListResponse {
|
||||
pub entries: Vec<ProfileInviteCodeAdminResponse>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -958,6 +1066,13 @@ mod tests {
|
||||
.to_string(),
|
||||
created_at: "2026-04-22T10:06:00Z".to_string(),
|
||||
},
|
||||
ProfileWalletLedgerEntryResponse {
|
||||
id: "ledger-9".to_string(),
|
||||
amount_delta: 10,
|
||||
balance_after: 212,
|
||||
source_type: PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD.to_string(),
|
||||
created_at: "2026-04-22T10:07:00Z".to_string(),
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
@@ -996,12 +1111,66 @@ mod tests {
|
||||
payload["entries"][7]["sourceType"],
|
||||
json!(PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM)
|
||||
);
|
||||
assert_eq!(
|
||||
payload["entries"][8]["sourceType"],
|
||||
json!(PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD)
|
||||
);
|
||||
assert_eq!(
|
||||
payload["entries"][0]["createdAt"],
|
||||
json!("2026-04-22T09:59:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_task_center_response_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(ProfileTaskCenterResponse {
|
||||
day_key: 20576,
|
||||
wallet_balance: 18,
|
||||
tasks: vec![ProfileTaskItemResponse {
|
||||
task_id: "daily_login".to_string(),
|
||||
title: "每日登录".to_string(),
|
||||
description: "".to_string(),
|
||||
event_key: "daily_login".to_string(),
|
||||
cycle: PROFILE_TASK_CYCLE_DAILY.to_string(),
|
||||
threshold: 1,
|
||||
progress_count: 1,
|
||||
reward_points: 10,
|
||||
status: PROFILE_TASK_STATUS_CLAIMABLE.to_string(),
|
||||
day_key: 20576,
|
||||
claimed_at: None,
|
||||
updated_at: "2026-05-03T00:00:00Z".to_string(),
|
||||
}],
|
||||
updated_at: "2026-05-03T00:00:00Z".to_string(),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["walletBalance"], json!(18));
|
||||
assert_eq!(payload["tasks"][0]["taskId"], json!("daily_login"));
|
||||
assert_eq!(payload["tasks"][0]["rewardPoints"], json!(10));
|
||||
assert_eq!(
|
||||
payload["tasks"][0]["status"],
|
||||
json!(PROFILE_TASK_STATUS_CLAIMABLE)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_task_config_request_accepts_defaults() {
|
||||
let payload: AdminUpsertProfileTaskConfigRequest = serde_json::from_value(json!({
|
||||
"taskId": "daily_login",
|
||||
"title": "每日登录",
|
||||
"eventKey": "daily_login",
|
||||
"cycle": "daily",
|
||||
"scopeKind": "user",
|
||||
"threshold": 1,
|
||||
"rewardPoints": 10
|
||||
}))
|
||||
.expect("request should deserialize");
|
||||
|
||||
assert_eq!(payload.description, None);
|
||||
assert_eq!(payload.enabled, true);
|
||||
assert_eq!(payload.sort_order, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_recharge_center_response_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(ProfileRechargeCenterResponse {
|
||||
|
||||
@@ -139,21 +139,31 @@ use module_runtime::{
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRedeemCodeMode as DomainRuntimeProfileRedeemCodeMode,
|
||||
RuntimeProfileRedeemCodeRecord, RuntimeProfileRewardCodeRedeemRecord,
|
||||
RuntimeProfileSaveArchiveRecord, RuntimeProfileWalletLedgerEntryRecord,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeReferralRedeemRecord, RuntimeSettingsRecord,
|
||||
RuntimeSnapshotRecord, build_runtime_browse_history_clear_input,
|
||||
build_runtime_browse_history_list_input, build_runtime_browse_history_record,
|
||||
build_runtime_browse_history_sync_input, build_runtime_profile_dashboard_get_input,
|
||||
build_runtime_profile_dashboard_record, build_runtime_profile_invite_code_admin_upsert_input,
|
||||
build_runtime_profile_invite_code_record, build_runtime_profile_play_stats_get_input,
|
||||
build_runtime_profile_play_stats_record, build_runtime_profile_recharge_center_get_input,
|
||||
build_runtime_profile_recharge_center_record,
|
||||
RuntimeProfileSaveArchiveRecord, RuntimeProfileTaskCenterRecord, RuntimeProfileTaskClaimRecord,
|
||||
RuntimeProfileTaskConfigRecord, RuntimeProfileTaskCycle as DomainRuntimeProfileTaskCycle,
|
||||
RuntimeProfileTaskStatus as DomainRuntimeProfileTaskStatus,
|
||||
RuntimeProfileWalletLedgerEntryRecord, RuntimeReferralInviteCenterRecord,
|
||||
RuntimeReferralRedeemRecord, RuntimeSettingsRecord, RuntimeSnapshotRecord,
|
||||
RuntimeTrackingScopeKind as DomainRuntimeTrackingScopeKind,
|
||||
build_runtime_browse_history_clear_input, build_runtime_browse_history_list_input,
|
||||
build_runtime_browse_history_record, build_runtime_browse_history_sync_input,
|
||||
build_runtime_profile_dashboard_get_input, build_runtime_profile_dashboard_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_play_stats_get_input, build_runtime_profile_play_stats_record,
|
||||
build_runtime_profile_recharge_center_get_input, build_runtime_profile_recharge_center_record,
|
||||
build_runtime_profile_recharge_order_create_input,
|
||||
build_runtime_profile_redeem_code_admin_disable_input,
|
||||
build_runtime_profile_redeem_code_admin_list_input,
|
||||
build_runtime_profile_redeem_code_admin_upsert_input, build_runtime_profile_redeem_code_record,
|
||||
build_runtime_profile_reward_code_redeem_input,
|
||||
build_runtime_profile_reward_code_redeem_record, build_runtime_profile_save_archive_list_input,
|
||||
build_runtime_profile_save_archive_record, build_runtime_profile_save_archive_resume_input,
|
||||
build_runtime_profile_task_center_get_input, build_runtime_profile_task_center_record,
|
||||
build_runtime_profile_task_claim_input, build_runtime_profile_task_claim_record,
|
||||
build_runtime_profile_task_config_admin_disable_input,
|
||||
build_runtime_profile_task_config_admin_list_input,
|
||||
build_runtime_profile_task_config_admin_upsert_input, build_runtime_profile_task_config_record,
|
||||
build_runtime_profile_wallet_adjustment_input,
|
||||
build_runtime_profile_wallet_ledger_entry_record,
|
||||
build_runtime_profile_wallet_ledger_list_input, build_runtime_referral_invite_center_get_input,
|
||||
|
||||
@@ -173,6 +173,66 @@ impl From<module_runtime::RuntimeProfileRewardCodeRedeemInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileTaskCenterGetInput> for RuntimeProfileTaskCenterGetInput {
|
||||
fn from(input: module_runtime::RuntimeProfileTaskCenterGetInput) -> Self {
|
||||
Self {
|
||||
user_id: input.user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileTaskClaimInput> for RuntimeProfileTaskClaimInput {
|
||||
fn from(input: module_runtime::RuntimeProfileTaskClaimInput) -> Self {
|
||||
Self {
|
||||
user_id: input.user_id,
|
||||
task_id: input.task_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileTaskConfigAdminListInput>
|
||||
for RuntimeProfileTaskConfigAdminListInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileTaskConfigAdminListInput) -> Self {
|
||||
Self {
|
||||
admin_user_id: input.admin_user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileTaskConfigAdminUpsertInput>
|
||||
for RuntimeProfileTaskConfigAdminUpsertInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileTaskConfigAdminUpsertInput) -> Self {
|
||||
Self {
|
||||
admin_user_id: input.admin_user_id,
|
||||
task_id: input.task_id,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
event_key: input.event_key,
|
||||
cycle: map_runtime_profile_task_cycle(input.cycle),
|
||||
scope_kind: map_runtime_tracking_scope_kind(input.scope_kind),
|
||||
threshold: input.threshold,
|
||||
reward_points: input.reward_points,
|
||||
enabled: input.enabled,
|
||||
sort_order: input.sort_order,
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileTaskConfigAdminDisableInput>
|
||||
for RuntimeProfileTaskConfigAdminDisableInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileTaskConfigAdminDisableInput) -> Self {
|
||||
Self {
|
||||
admin_user_id: input.admin_user_id,
|
||||
task_id: input.task_id,
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileRedeemCodeAdminUpsertInput>
|
||||
for RuntimeProfileRedeemCodeAdminUpsertInput
|
||||
{
|
||||
@@ -203,6 +263,16 @@ impl From<module_runtime::RuntimeProfileRedeemCodeAdminDisableInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileRedeemCodeAdminListInput>
|
||||
for RuntimeProfileRedeemCodeAdminListInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileRedeemCodeAdminListInput) -> Self {
|
||||
Self {
|
||||
admin_user_id: input.admin_user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileInviteCodeAdminUpsertInput>
|
||||
for RuntimeProfileInviteCodeAdminUpsertInput
|
||||
{
|
||||
@@ -216,6 +286,16 @@ impl From<module_runtime::RuntimeProfileInviteCodeAdminUpsertInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileInviteCodeAdminListInput>
|
||||
for RuntimeProfileInviteCodeAdminListInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileInviteCodeAdminListInput) -> Self {
|
||||
Self {
|
||||
admin_user_id: input.admin_user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeReferralInviteCenterGetInput>
|
||||
for RuntimeReferralInviteCenterGetInput
|
||||
{
|
||||
@@ -801,6 +881,72 @@ pub(crate) fn map_runtime_profile_reward_code_redeem_procedure_result(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_center_procedure_result(
|
||||
result: RuntimeProfileTaskCenterProcedureResult,
|
||||
) -> Result<RuntimeProfileTaskCenterRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
let snapshot = result
|
||||
.record
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("profile task center 快照"))?;
|
||||
|
||||
Ok(build_runtime_profile_task_center_record(
|
||||
map_runtime_profile_task_center_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_claim_procedure_result(
|
||||
result: RuntimeProfileTaskClaimProcedureResult,
|
||||
) -> Result<RuntimeProfileTaskClaimRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
let snapshot = result
|
||||
.record
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("profile task claim 快照"))?;
|
||||
|
||||
Ok(build_runtime_profile_task_claim_record(
|
||||
map_runtime_profile_task_claim_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_config_admin_list_procedure_result(
|
||||
result: RuntimeProfileTaskConfigAdminListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileTaskConfigRecord>, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
build_runtime_profile_task_config_record(map_runtime_profile_task_config_snapshot(
|
||||
snapshot,
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_config_admin_procedure_result(
|
||||
result: RuntimeProfileTaskConfigAdminProcedureResult,
|
||||
) -> Result<RuntimeProfileTaskConfigRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
let snapshot = result
|
||||
.record
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("profile task config 快照"))?;
|
||||
|
||||
Ok(build_runtime_profile_task_config_record(
|
||||
map_runtime_profile_task_config_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_redeem_code_admin_procedure_result(
|
||||
result: RuntimeProfileRedeemCodeAdminProcedureResult,
|
||||
) -> Result<RuntimeProfileRedeemCodeRecord, SpacetimeClientError> {
|
||||
@@ -817,6 +963,24 @@ pub(crate) fn map_runtime_profile_redeem_code_admin_procedure_result(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_redeem_code_admin_list_procedure_result(
|
||||
result: RuntimeProfileRedeemCodeAdminListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeRecord>, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
build_runtime_profile_redeem_code_record(map_runtime_profile_redeem_code_snapshot(
|
||||
snapshot,
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_invite_code_admin_procedure_result(
|
||||
result: RuntimeProfileInviteCodeAdminProcedureResult,
|
||||
) -> Result<RuntimeProfileInviteCodeRecord, SpacetimeClientError> {
|
||||
@@ -837,6 +1001,24 @@ pub(crate) fn map_runtime_profile_invite_code_admin_procedure_result(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_invite_code_admin_list_procedure_result(
|
||||
result: RuntimeProfileInviteCodeAdminListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeRecord>, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
build_runtime_profile_invite_code_record(map_runtime_profile_invite_code_snapshot(
|
||||
snapshot,
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_play_stats_procedure_result(
|
||||
result: RuntimeProfilePlayStatsProcedureResult,
|
||||
) -> Result<RuntimeProfilePlayStatsRecord, SpacetimeClientError> {
|
||||
@@ -1721,6 +1903,76 @@ pub(crate) fn map_runtime_profile_reward_code_redeem_snapshot(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_config_snapshot(
|
||||
snapshot: RuntimeProfileTaskConfigSnapshot,
|
||||
) -> module_runtime::RuntimeProfileTaskConfigSnapshot {
|
||||
module_runtime::RuntimeProfileTaskConfigSnapshot {
|
||||
task_id: snapshot.task_id,
|
||||
title: snapshot.title,
|
||||
description: snapshot.description,
|
||||
event_key: snapshot.event_key,
|
||||
cycle: map_runtime_profile_task_cycle_back(snapshot.cycle),
|
||||
scope_kind: map_runtime_tracking_scope_kind_back(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_micros: snapshot.created_at_micros,
|
||||
updated_by: snapshot.updated_by,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_item_snapshot(
|
||||
snapshot: RuntimeProfileTaskItemSnapshot,
|
||||
) -> module_runtime::RuntimeProfileTaskItemSnapshot {
|
||||
module_runtime::RuntimeProfileTaskItemSnapshot {
|
||||
task_id: snapshot.task_id,
|
||||
title: snapshot.title,
|
||||
description: snapshot.description,
|
||||
event_key: snapshot.event_key,
|
||||
cycle: map_runtime_profile_task_cycle_back(snapshot.cycle),
|
||||
threshold: snapshot.threshold,
|
||||
progress_count: snapshot.progress_count,
|
||||
reward_points: snapshot.reward_points,
|
||||
status: map_runtime_profile_task_status_back(snapshot.status),
|
||||
day_key: snapshot.day_key,
|
||||
claimed_at_micros: snapshot.claimed_at_micros,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_center_snapshot(
|
||||
snapshot: RuntimeProfileTaskCenterSnapshot,
|
||||
) -> module_runtime::RuntimeProfileTaskCenterSnapshot {
|
||||
module_runtime::RuntimeProfileTaskCenterSnapshot {
|
||||
user_id: snapshot.user_id,
|
||||
day_key: snapshot.day_key,
|
||||
wallet_balance: snapshot.wallet_balance,
|
||||
tasks: snapshot
|
||||
.tasks
|
||||
.into_iter()
|
||||
.map(map_runtime_profile_task_item_snapshot)
|
||||
.collect(),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_claim_snapshot(
|
||||
snapshot: RuntimeProfileTaskClaimSnapshot,
|
||||
) -> module_runtime::RuntimeProfileTaskClaimSnapshot {
|
||||
module_runtime::RuntimeProfileTaskClaimSnapshot {
|
||||
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: map_runtime_profile_wallet_ledger_entry_snapshot(snapshot.ledger_entry),
|
||||
center: map_runtime_profile_task_center_snapshot(snapshot.center),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_redeem_code_snapshot(
|
||||
snapshot: RuntimeProfileRedeemCodeSnapshot,
|
||||
) -> module_runtime::RuntimeProfileRedeemCodeSnapshot {
|
||||
@@ -3750,6 +4002,86 @@ pub(crate) fn map_runtime_profile_wallet_ledger_source_type_back(
|
||||
crate::module_bindings::RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim => {
|
||||
module_runtime::RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim
|
||||
}
|
||||
crate::module_bindings::RuntimeProfileWalletLedgerSourceType::DailyTaskReward => {
|
||||
module_runtime::RuntimeProfileWalletLedgerSourceType::DailyTaskReward
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_tracking_scope_kind(
|
||||
value: DomainRuntimeTrackingScopeKind,
|
||||
) -> crate::module_bindings::RuntimeTrackingScopeKind {
|
||||
match value {
|
||||
DomainRuntimeTrackingScopeKind::Site => {
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Site
|
||||
}
|
||||
DomainRuntimeTrackingScopeKind::Work => {
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Work
|
||||
}
|
||||
DomainRuntimeTrackingScopeKind::Module => {
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Module
|
||||
}
|
||||
DomainRuntimeTrackingScopeKind::User => {
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::User
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_tracking_scope_kind_back(
|
||||
value: crate::module_bindings::RuntimeTrackingScopeKind,
|
||||
) -> DomainRuntimeTrackingScopeKind {
|
||||
match value {
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Site => {
|
||||
DomainRuntimeTrackingScopeKind::Site
|
||||
}
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Work => {
|
||||
DomainRuntimeTrackingScopeKind::Work
|
||||
}
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::Module => {
|
||||
DomainRuntimeTrackingScopeKind::Module
|
||||
}
|
||||
crate::module_bindings::RuntimeTrackingScopeKind::User => {
|
||||
DomainRuntimeTrackingScopeKind::User
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_cycle(
|
||||
value: DomainRuntimeProfileTaskCycle,
|
||||
) -> crate::module_bindings::RuntimeProfileTaskCycle {
|
||||
match value {
|
||||
DomainRuntimeProfileTaskCycle::Daily => {
|
||||
crate::module_bindings::RuntimeProfileTaskCycle::Daily
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_cycle_back(
|
||||
value: crate::module_bindings::RuntimeProfileTaskCycle,
|
||||
) -> DomainRuntimeProfileTaskCycle {
|
||||
match value {
|
||||
crate::module_bindings::RuntimeProfileTaskCycle::Daily => {
|
||||
DomainRuntimeProfileTaskCycle::Daily
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_task_status_back(
|
||||
value: crate::module_bindings::RuntimeProfileTaskStatus,
|
||||
) -> DomainRuntimeProfileTaskStatus {
|
||||
match value {
|
||||
crate::module_bindings::RuntimeProfileTaskStatus::Incomplete => {
|
||||
DomainRuntimeProfileTaskStatus::Incomplete
|
||||
}
|
||||
crate::module_bindings::RuntimeProfileTaskStatus::Claimable => {
|
||||
DomainRuntimeProfileTaskStatus::Claimable
|
||||
}
|
||||
crate::module_bindings::RuntimeProfileTaskStatus::Claimed => {
|
||||
DomainRuntimeProfileTaskStatus::Claimed
|
||||
}
|
||||
crate::module_bindings::RuntimeProfileTaskStatus::Disabled => {
|
||||
DomainRuntimeProfileTaskStatus::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_task_config_admin_disable_input_type::RuntimeProfileTaskConfigAdminDisableInput;
|
||||
use super::runtime_profile_task_config_admin_procedure_result_type::RuntimeProfileTaskConfigAdminProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminDisableProfileTaskConfigArgs {
|
||||
pub input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminDisableProfileTaskConfigArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `admin_disable_profile_task_config`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_disable_profile_task_config {
|
||||
fn admin_disable_profile_task_config(&self, input: RuntimeProfileTaskConfigAdminDisableInput) {
|
||||
self.admin_disable_profile_task_config_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_disable_profile_task_config_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl admin_disable_profile_task_config for super::RemoteProcedures {
|
||||
fn admin_disable_profile_task_config_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminProcedureResult>(
|
||||
"admin_disable_profile_task_config",
|
||||
AdminDisableProfileTaskConfigArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_invite_code_admin_list_input_type::RuntimeProfileInviteCodeAdminListInput;
|
||||
use super::runtime_profile_invite_code_admin_list_procedure_result_type::RuntimeProfileInviteCodeAdminListProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminListProfileInviteCodesArgs {
|
||||
pub input: RuntimeProfileInviteCodeAdminListInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminListProfileInviteCodesArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `admin_list_profile_invite_codes`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_list_profile_invite_codes {
|
||||
fn admin_list_profile_invite_codes(&self, input: RuntimeProfileInviteCodeAdminListInput) {
|
||||
self.admin_list_profile_invite_codes_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_list_profile_invite_codes_then(
|
||||
&self,
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileInviteCodeAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl admin_list_profile_invite_codes for super::RemoteProcedures {
|
||||
fn admin_list_profile_invite_codes_then(
|
||||
&self,
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileInviteCodeAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileInviteCodeAdminListProcedureResult>(
|
||||
"admin_list_profile_invite_codes",
|
||||
AdminListProfileInviteCodesArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_redeem_code_admin_list_input_type::RuntimeProfileRedeemCodeAdminListInput;
|
||||
use super::runtime_profile_redeem_code_admin_list_procedure_result_type::RuntimeProfileRedeemCodeAdminListProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminListProfileRedeemCodesArgs {
|
||||
pub input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminListProfileRedeemCodesArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `admin_list_profile_redeem_codes`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_list_profile_redeem_codes {
|
||||
fn admin_list_profile_redeem_codes(&self, input: RuntimeProfileRedeemCodeAdminListInput) {
|
||||
self.admin_list_profile_redeem_codes_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_list_profile_redeem_codes_then(
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl admin_list_profile_redeem_codes for super::RemoteProcedures {
|
||||
fn admin_list_profile_redeem_codes_then(
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminListProcedureResult>(
|
||||
"admin_list_profile_redeem_codes",
|
||||
AdminListProfileRedeemCodesArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_task_config_admin_list_input_type::RuntimeProfileTaskConfigAdminListInput;
|
||||
use super::runtime_profile_task_config_admin_list_procedure_result_type::RuntimeProfileTaskConfigAdminListProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminListProfileTaskConfigsArgs {
|
||||
pub input: RuntimeProfileTaskConfigAdminListInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminListProfileTaskConfigsArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `admin_list_profile_task_configs`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_list_profile_task_configs {
|
||||
fn admin_list_profile_task_configs(&self, input: RuntimeProfileTaskConfigAdminListInput) {
|
||||
self.admin_list_profile_task_configs_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_list_profile_task_configs_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl admin_list_profile_task_configs for super::RemoteProcedures {
|
||||
fn admin_list_profile_task_configs_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminListInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminListProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminListProcedureResult>(
|
||||
"admin_list_profile_task_configs",
|
||||
AdminListProfileTaskConfigsArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_task_config_admin_procedure_result_type::RuntimeProfileTaskConfigAdminProcedureResult;
|
||||
use super::runtime_profile_task_config_admin_upsert_input_type::RuntimeProfileTaskConfigAdminUpsertInput;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminUpsertProfileTaskConfigArgs {
|
||||
pub input: RuntimeProfileTaskConfigAdminUpsertInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminUpsertProfileTaskConfigArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `admin_upsert_profile_task_config`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_upsert_profile_task_config {
|
||||
fn admin_upsert_profile_task_config(&self, input: RuntimeProfileTaskConfigAdminUpsertInput) {
|
||||
self.admin_upsert_profile_task_config_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_upsert_profile_task_config_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminUpsertInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl admin_upsert_profile_task_config for super::RemoteProcedures {
|
||||
fn admin_upsert_profile_task_config_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskConfigAdminUpsertInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminProcedureResult>(
|
||||
"admin_upsert_profile_task_config",
|
||||
AdminUpsertProfileTaskConfigArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_task_claim_input_type::RuntimeProfileTaskClaimInput;
|
||||
use super::runtime_profile_task_claim_procedure_result_type::RuntimeProfileTaskClaimProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct ClaimProfileTaskRewardAndReturnArgs {
|
||||
pub input: RuntimeProfileTaskClaimInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ClaimProfileTaskRewardAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `claim_profile_task_reward_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait claim_profile_task_reward_and_return {
|
||||
fn claim_profile_task_reward_and_return(&self, input: RuntimeProfileTaskClaimInput) {
|
||||
self.claim_profile_task_reward_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn claim_profile_task_reward_and_return_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskClaimInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskClaimProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl claim_profile_task_reward_and_return for super::RemoteProcedures {
|
||||
fn claim_profile_task_reward_and_return_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskClaimInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskClaimProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskClaimProcedureResult>(
|
||||
"claim_profile_task_reward_and_return",
|
||||
ClaimProfileTaskRewardAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_task_center_get_input_type::RuntimeProfileTaskCenterGetInput;
|
||||
use super::runtime_profile_task_center_procedure_result_type::RuntimeProfileTaskCenterProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct GetProfileTaskCenterArgs {
|
||||
pub input: RuntimeProfileTaskCenterGetInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for GetProfileTaskCenterArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `get_profile_task_center`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait get_profile_task_center {
|
||||
fn get_profile_task_center(&self, input: RuntimeProfileTaskCenterGetInput) {
|
||||
self.get_profile_task_center_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn get_profile_task_center_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskCenterGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskCenterProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl get_profile_task_center for super::RemoteProcedures {
|
||||
fn get_profile_task_center_then(
|
||||
&self,
|
||||
input: RuntimeProfileTaskCenterGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskCenterProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskCenterProcedureResult>(
|
||||
"get_profile_task_center",
|
||||
GetProfileTaskCenterArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,13 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
pub mod accept_quest_reducer;
|
||||
pub mod acknowledge_quest_completion_reducer;
|
||||
pub mod admin_disable_profile_redeem_code_procedure;
|
||||
pub mod admin_disable_profile_task_config_procedure;
|
||||
pub mod admin_list_profile_invite_codes_procedure;
|
||||
pub mod admin_list_profile_redeem_codes_procedure;
|
||||
pub mod admin_list_profile_task_configs_procedure;
|
||||
pub mod admin_upsert_profile_invite_code_procedure;
|
||||
pub mod admin_upsert_profile_redeem_code_procedure;
|
||||
pub mod admin_upsert_profile_task_config_procedure;
|
||||
pub mod advance_puzzle_next_level_procedure;
|
||||
pub mod ai_result_reference_input_type;
|
||||
pub mod ai_result_reference_kind_type;
|
||||
@@ -129,6 +134,7 @@ pub mod chapter_progression_ledger_input_type;
|
||||
pub mod chapter_progression_procedure_result_type;
|
||||
pub mod chapter_progression_snapshot_type;
|
||||
pub mod chapter_progression_type;
|
||||
pub mod claim_profile_task_reward_and_return_procedure;
|
||||
pub mod claim_puzzle_work_point_incentive_procedure;
|
||||
pub mod clear_database_migration_import_chunks_procedure;
|
||||
pub mod clear_platform_browse_history_and_return_procedure;
|
||||
@@ -260,6 +266,7 @@ pub mod get_profile_dashboard_procedure;
|
||||
pub mod get_profile_play_stats_procedure;
|
||||
pub mod get_profile_recharge_center_procedure;
|
||||
pub mod get_profile_referral_invite_center_procedure;
|
||||
pub mod get_profile_task_center_procedure;
|
||||
pub mod get_puzzle_agent_session_procedure;
|
||||
pub mod get_puzzle_gallery_detail_procedure;
|
||||
pub mod get_puzzle_run_procedure;
|
||||
@@ -351,6 +358,9 @@ pub mod profile_redeem_code_type;
|
||||
pub mod profile_redeem_code_usage_type;
|
||||
pub mod profile_referral_relation_type;
|
||||
pub mod profile_save_archive_type;
|
||||
pub mod profile_task_config_type;
|
||||
pub mod profile_task_progress_type;
|
||||
pub mod profile_task_reward_claim_type;
|
||||
pub mod profile_wallet_ledger_type;
|
||||
pub mod public_work_like_type;
|
||||
pub mod public_work_play_daily_stat_type;
|
||||
@@ -482,6 +492,8 @@ pub mod runtime_platform_theme_type;
|
||||
pub mod runtime_profile_dashboard_get_input_type;
|
||||
pub mod runtime_profile_dashboard_procedure_result_type;
|
||||
pub mod runtime_profile_dashboard_snapshot_type;
|
||||
pub mod runtime_profile_invite_code_admin_list_input_type;
|
||||
pub mod runtime_profile_invite_code_admin_list_procedure_result_type;
|
||||
pub mod runtime_profile_invite_code_admin_procedure_result_type;
|
||||
pub mod runtime_profile_invite_code_admin_upsert_input_type;
|
||||
pub mod runtime_profile_invite_code_snapshot_type;
|
||||
@@ -502,6 +514,8 @@ pub mod runtime_profile_recharge_order_status_type;
|
||||
pub mod runtime_profile_recharge_product_kind_type;
|
||||
pub mod runtime_profile_recharge_product_snapshot_type;
|
||||
pub mod runtime_profile_redeem_code_admin_disable_input_type;
|
||||
pub mod runtime_profile_redeem_code_admin_list_input_type;
|
||||
pub mod runtime_profile_redeem_code_admin_list_procedure_result_type;
|
||||
pub mod runtime_profile_redeem_code_admin_procedure_result_type;
|
||||
pub mod runtime_profile_redeem_code_admin_upsert_input_type;
|
||||
pub mod runtime_profile_redeem_code_mode_type;
|
||||
@@ -513,6 +527,21 @@ pub mod runtime_profile_save_archive_list_input_type;
|
||||
pub mod runtime_profile_save_archive_procedure_result_type;
|
||||
pub mod runtime_profile_save_archive_resume_input_type;
|
||||
pub mod runtime_profile_save_archive_snapshot_type;
|
||||
pub mod runtime_profile_task_center_get_input_type;
|
||||
pub mod runtime_profile_task_center_procedure_result_type;
|
||||
pub mod runtime_profile_task_center_snapshot_type;
|
||||
pub mod runtime_profile_task_claim_input_type;
|
||||
pub mod runtime_profile_task_claim_procedure_result_type;
|
||||
pub mod runtime_profile_task_claim_snapshot_type;
|
||||
pub mod runtime_profile_task_config_admin_disable_input_type;
|
||||
pub mod runtime_profile_task_config_admin_list_input_type;
|
||||
pub mod runtime_profile_task_config_admin_list_procedure_result_type;
|
||||
pub mod runtime_profile_task_config_admin_procedure_result_type;
|
||||
pub mod runtime_profile_task_config_admin_upsert_input_type;
|
||||
pub mod runtime_profile_task_config_snapshot_type;
|
||||
pub mod runtime_profile_task_cycle_type;
|
||||
pub mod runtime_profile_task_item_snapshot_type;
|
||||
pub mod runtime_profile_task_status_type;
|
||||
pub mod runtime_profile_wallet_adjustment_input_type;
|
||||
pub mod runtime_profile_wallet_adjustment_procedure_result_type;
|
||||
pub mod runtime_profile_wallet_ledger_entry_snapshot_type;
|
||||
@@ -537,6 +566,7 @@ pub mod runtime_snapshot_procedure_result_type;
|
||||
pub mod runtime_snapshot_row_type;
|
||||
pub mod runtime_snapshot_type;
|
||||
pub mod runtime_snapshot_upsert_input_type;
|
||||
pub mod runtime_tracking_scope_kind_type;
|
||||
pub mod save_puzzle_form_draft_procedure;
|
||||
pub mod save_puzzle_generated_images_procedure;
|
||||
pub mod select_puzzle_cover_image_procedure;
|
||||
@@ -564,6 +594,8 @@ pub mod submit_match_3_d_agent_message_procedure;
|
||||
pub mod submit_puzzle_agent_message_procedure;
|
||||
pub mod submit_puzzle_leaderboard_entry_procedure;
|
||||
pub mod swap_puzzle_pieces_procedure;
|
||||
pub mod tracking_daily_stat_type;
|
||||
pub mod tracking_event_type;
|
||||
pub mod treasure_interaction_action_type;
|
||||
pub mod treasure_record_procedure_result_type;
|
||||
pub mod treasure_record_snapshot_type;
|
||||
@@ -594,8 +626,13 @@ pub mod user_browse_history_type;
|
||||
pub use accept_quest_reducer::accept_quest;
|
||||
pub use acknowledge_quest_completion_reducer::acknowledge_quest_completion;
|
||||
pub use admin_disable_profile_redeem_code_procedure::admin_disable_profile_redeem_code;
|
||||
pub use admin_disable_profile_task_config_procedure::admin_disable_profile_task_config;
|
||||
pub use admin_list_profile_invite_codes_procedure::admin_list_profile_invite_codes;
|
||||
pub use admin_list_profile_redeem_codes_procedure::admin_list_profile_redeem_codes;
|
||||
pub use admin_list_profile_task_configs_procedure::admin_list_profile_task_configs;
|
||||
pub use admin_upsert_profile_invite_code_procedure::admin_upsert_profile_invite_code;
|
||||
pub use admin_upsert_profile_redeem_code_procedure::admin_upsert_profile_redeem_code;
|
||||
pub use admin_upsert_profile_task_config_procedure::admin_upsert_profile_task_config;
|
||||
pub use advance_puzzle_next_level_procedure::advance_puzzle_next_level;
|
||||
pub use ai_result_reference_input_type::AiResultReferenceInput;
|
||||
pub use ai_result_reference_kind_type::AiResultReferenceKind;
|
||||
@@ -714,6 +751,7 @@ pub use chapter_progression_ledger_input_type::ChapterProgressionLedgerInput;
|
||||
pub use chapter_progression_procedure_result_type::ChapterProgressionProcedureResult;
|
||||
pub use chapter_progression_snapshot_type::ChapterProgressionSnapshot;
|
||||
pub use chapter_progression_type::ChapterProgression;
|
||||
pub use claim_profile_task_reward_and_return_procedure::claim_profile_task_reward_and_return;
|
||||
pub use claim_puzzle_work_point_incentive_procedure::claim_puzzle_work_point_incentive;
|
||||
pub use clear_database_migration_import_chunks_procedure::clear_database_migration_import_chunks;
|
||||
pub use clear_platform_browse_history_and_return_procedure::clear_platform_browse_history_and_return;
|
||||
@@ -845,6 +883,7 @@ pub use get_profile_dashboard_procedure::get_profile_dashboard;
|
||||
pub use get_profile_play_stats_procedure::get_profile_play_stats;
|
||||
pub use get_profile_recharge_center_procedure::get_profile_recharge_center;
|
||||
pub use get_profile_referral_invite_center_procedure::get_profile_referral_invite_center;
|
||||
pub use get_profile_task_center_procedure::get_profile_task_center;
|
||||
pub use get_puzzle_agent_session_procedure::get_puzzle_agent_session;
|
||||
pub use get_puzzle_gallery_detail_procedure::get_puzzle_gallery_detail;
|
||||
pub use get_puzzle_run_procedure::get_puzzle_run;
|
||||
@@ -936,6 +975,9 @@ pub use profile_redeem_code_type::ProfileRedeemCode;
|
||||
pub use profile_redeem_code_usage_type::ProfileRedeemCodeUsage;
|
||||
pub use profile_referral_relation_type::ProfileReferralRelation;
|
||||
pub use profile_save_archive_type::ProfileSaveArchive;
|
||||
pub use profile_task_config_type::ProfileTaskConfig;
|
||||
pub use profile_task_progress_type::ProfileTaskProgress;
|
||||
pub use profile_task_reward_claim_type::ProfileTaskRewardClaim;
|
||||
pub use profile_wallet_ledger_type::ProfileWalletLedger;
|
||||
pub use public_work_like_type::PublicWorkLike;
|
||||
pub use public_work_play_daily_stat_type::PublicWorkPlayDailyStat;
|
||||
@@ -1067,6 +1109,8 @@ pub use runtime_platform_theme_type::RuntimePlatformTheme;
|
||||
pub use runtime_profile_dashboard_get_input_type::RuntimeProfileDashboardGetInput;
|
||||
pub use runtime_profile_dashboard_procedure_result_type::RuntimeProfileDashboardProcedureResult;
|
||||
pub use runtime_profile_dashboard_snapshot_type::RuntimeProfileDashboardSnapshot;
|
||||
pub use runtime_profile_invite_code_admin_list_input_type::RuntimeProfileInviteCodeAdminListInput;
|
||||
pub use runtime_profile_invite_code_admin_list_procedure_result_type::RuntimeProfileInviteCodeAdminListProcedureResult;
|
||||
pub use runtime_profile_invite_code_admin_procedure_result_type::RuntimeProfileInviteCodeAdminProcedureResult;
|
||||
pub use runtime_profile_invite_code_admin_upsert_input_type::RuntimeProfileInviteCodeAdminUpsertInput;
|
||||
pub use runtime_profile_invite_code_snapshot_type::RuntimeProfileInviteCodeSnapshot;
|
||||
@@ -1087,6 +1131,8 @@ pub use runtime_profile_recharge_order_status_type::RuntimeProfileRechargeOrderS
|
||||
pub use runtime_profile_recharge_product_kind_type::RuntimeProfileRechargeProductKind;
|
||||
pub use runtime_profile_recharge_product_snapshot_type::RuntimeProfileRechargeProductSnapshot;
|
||||
pub use runtime_profile_redeem_code_admin_disable_input_type::RuntimeProfileRedeemCodeAdminDisableInput;
|
||||
pub use runtime_profile_redeem_code_admin_list_input_type::RuntimeProfileRedeemCodeAdminListInput;
|
||||
pub use runtime_profile_redeem_code_admin_list_procedure_result_type::RuntimeProfileRedeemCodeAdminListProcedureResult;
|
||||
pub use runtime_profile_redeem_code_admin_procedure_result_type::RuntimeProfileRedeemCodeAdminProcedureResult;
|
||||
pub use runtime_profile_redeem_code_admin_upsert_input_type::RuntimeProfileRedeemCodeAdminUpsertInput;
|
||||
pub use runtime_profile_redeem_code_mode_type::RuntimeProfileRedeemCodeMode;
|
||||
@@ -1098,6 +1144,21 @@ pub use runtime_profile_save_archive_list_input_type::RuntimeProfileSaveArchiveL
|
||||
pub use runtime_profile_save_archive_procedure_result_type::RuntimeProfileSaveArchiveProcedureResult;
|
||||
pub use runtime_profile_save_archive_resume_input_type::RuntimeProfileSaveArchiveResumeInput;
|
||||
pub use runtime_profile_save_archive_snapshot_type::RuntimeProfileSaveArchiveSnapshot;
|
||||
pub use runtime_profile_task_center_get_input_type::RuntimeProfileTaskCenterGetInput;
|
||||
pub use runtime_profile_task_center_procedure_result_type::RuntimeProfileTaskCenterProcedureResult;
|
||||
pub use runtime_profile_task_center_snapshot_type::RuntimeProfileTaskCenterSnapshot;
|
||||
pub use runtime_profile_task_claim_input_type::RuntimeProfileTaskClaimInput;
|
||||
pub use runtime_profile_task_claim_procedure_result_type::RuntimeProfileTaskClaimProcedureResult;
|
||||
pub use runtime_profile_task_claim_snapshot_type::RuntimeProfileTaskClaimSnapshot;
|
||||
pub use runtime_profile_task_config_admin_disable_input_type::RuntimeProfileTaskConfigAdminDisableInput;
|
||||
pub use runtime_profile_task_config_admin_list_input_type::RuntimeProfileTaskConfigAdminListInput;
|
||||
pub use runtime_profile_task_config_admin_list_procedure_result_type::RuntimeProfileTaskConfigAdminListProcedureResult;
|
||||
pub use runtime_profile_task_config_admin_procedure_result_type::RuntimeProfileTaskConfigAdminProcedureResult;
|
||||
pub use runtime_profile_task_config_admin_upsert_input_type::RuntimeProfileTaskConfigAdminUpsertInput;
|
||||
pub use runtime_profile_task_config_snapshot_type::RuntimeProfileTaskConfigSnapshot;
|
||||
pub use runtime_profile_task_cycle_type::RuntimeProfileTaskCycle;
|
||||
pub use runtime_profile_task_item_snapshot_type::RuntimeProfileTaskItemSnapshot;
|
||||
pub use runtime_profile_task_status_type::RuntimeProfileTaskStatus;
|
||||
pub use runtime_profile_wallet_adjustment_input_type::RuntimeProfileWalletAdjustmentInput;
|
||||
pub use runtime_profile_wallet_adjustment_procedure_result_type::RuntimeProfileWalletAdjustmentProcedureResult;
|
||||
pub use runtime_profile_wallet_ledger_entry_snapshot_type::RuntimeProfileWalletLedgerEntrySnapshot;
|
||||
@@ -1122,6 +1183,7 @@ pub use runtime_snapshot_procedure_result_type::RuntimeSnapshotProcedureResult;
|
||||
pub use runtime_snapshot_row_type::RuntimeSnapshotRow;
|
||||
pub use runtime_snapshot_type::RuntimeSnapshot;
|
||||
pub use runtime_snapshot_upsert_input_type::RuntimeSnapshotUpsertInput;
|
||||
pub use runtime_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
pub use save_puzzle_form_draft_procedure::save_puzzle_form_draft;
|
||||
pub use save_puzzle_generated_images_procedure::save_puzzle_generated_images;
|
||||
pub use select_puzzle_cover_image_procedure::select_puzzle_cover_image;
|
||||
@@ -1149,6 +1211,8 @@ pub use submit_match_3_d_agent_message_procedure::submit_match_3_d_agent_message
|
||||
pub use submit_puzzle_agent_message_procedure::submit_puzzle_agent_message;
|
||||
pub use submit_puzzle_leaderboard_entry_procedure::submit_puzzle_leaderboard_entry;
|
||||
pub use swap_puzzle_pieces_procedure::swap_puzzle_pieces;
|
||||
pub use tracking_daily_stat_type::TrackingDailyStat;
|
||||
pub use tracking_event_type::TrackingEvent;
|
||||
pub use treasure_interaction_action_type::TreasureInteractionAction;
|
||||
pub use treasure_record_procedure_result_type::TreasureRecordProcedureResult;
|
||||
pub use treasure_record_snapshot_type::TreasureRecordSnapshot;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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_task_cycle_type::RuntimeProfileTaskCycle;
|
||||
use super::runtime_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct ProfileTaskConfig {
|
||||
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: __sdk::Timestamp,
|
||||
pub updated_by: String,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ProfileTaskConfig {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `ProfileTaskConfig`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileTaskConfigCols {
|
||||
pub task_id: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub title: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub description: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub event_key: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub cycle: __sdk::__query_builder::Col<ProfileTaskConfig, RuntimeProfileTaskCycle>,
|
||||
pub scope_kind: __sdk::__query_builder::Col<ProfileTaskConfig, RuntimeTrackingScopeKind>,
|
||||
pub threshold: __sdk::__query_builder::Col<ProfileTaskConfig, u32>,
|
||||
pub reward_points: __sdk::__query_builder::Col<ProfileTaskConfig, u64>,
|
||||
pub enabled: __sdk::__query_builder::Col<ProfileTaskConfig, bool>,
|
||||
pub sort_order: __sdk::__query_builder::Col<ProfileTaskConfig, i32>,
|
||||
pub created_by: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub created_at: __sdk::__query_builder::Col<ProfileTaskConfig, __sdk::Timestamp>,
|
||||
pub updated_by: __sdk::__query_builder::Col<ProfileTaskConfig, String>,
|
||||
pub updated_at: __sdk::__query_builder::Col<ProfileTaskConfig, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileTaskConfig {
|
||||
type Cols = ProfileTaskConfigCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileTaskConfigCols {
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
title: __sdk::__query_builder::Col::new(table_name, "title"),
|
||||
description: __sdk::__query_builder::Col::new(table_name, "description"),
|
||||
event_key: __sdk::__query_builder::Col::new(table_name, "event_key"),
|
||||
cycle: __sdk::__query_builder::Col::new(table_name, "cycle"),
|
||||
scope_kind: __sdk::__query_builder::Col::new(table_name, "scope_kind"),
|
||||
threshold: __sdk::__query_builder::Col::new(table_name, "threshold"),
|
||||
reward_points: __sdk::__query_builder::Col::new(table_name, "reward_points"),
|
||||
enabled: __sdk::__query_builder::Col::new(table_name, "enabled"),
|
||||
sort_order: __sdk::__query_builder::Col::new(table_name, "sort_order"),
|
||||
created_by: __sdk::__query_builder::Col::new(table_name, "created_by"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
updated_by: __sdk::__query_builder::Col::new(table_name, "updated_by"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileTaskConfig`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileTaskConfigIxCols {
|
||||
pub task_id: __sdk::__query_builder::IxCol<ProfileTaskConfig, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileTaskConfig {
|
||||
type IxCols = ProfileTaskConfigIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileTaskConfigIxCols {
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileTaskConfig {}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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_task_status_type::RuntimeProfileTaskStatus;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct ProfileTaskProgress {
|
||||
pub progress_id: String,
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
pub day_key: i64,
|
||||
pub progress_count: u32,
|
||||
pub threshold: u32,
|
||||
pub status: RuntimeProfileTaskStatus,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ProfileTaskProgress {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `ProfileTaskProgress`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileTaskProgressCols {
|
||||
pub progress_id: __sdk::__query_builder::Col<ProfileTaskProgress, String>,
|
||||
pub user_id: __sdk::__query_builder::Col<ProfileTaskProgress, String>,
|
||||
pub task_id: __sdk::__query_builder::Col<ProfileTaskProgress, String>,
|
||||
pub day_key: __sdk::__query_builder::Col<ProfileTaskProgress, i64>,
|
||||
pub progress_count: __sdk::__query_builder::Col<ProfileTaskProgress, u32>,
|
||||
pub threshold: __sdk::__query_builder::Col<ProfileTaskProgress, u32>,
|
||||
pub status: __sdk::__query_builder::Col<ProfileTaskProgress, RuntimeProfileTaskStatus>,
|
||||
pub updated_at: __sdk::__query_builder::Col<ProfileTaskProgress, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileTaskProgress {
|
||||
type Cols = ProfileTaskProgressCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileTaskProgressCols {
|
||||
progress_id: __sdk::__query_builder::Col::new(table_name, "progress_id"),
|
||||
user_id: __sdk::__query_builder::Col::new(table_name, "user_id"),
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
day_key: __sdk::__query_builder::Col::new(table_name, "day_key"),
|
||||
progress_count: __sdk::__query_builder::Col::new(table_name, "progress_count"),
|
||||
threshold: __sdk::__query_builder::Col::new(table_name, "threshold"),
|
||||
status: __sdk::__query_builder::Col::new(table_name, "status"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileTaskProgress`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileTaskProgressIxCols {
|
||||
pub progress_id: __sdk::__query_builder::IxCol<ProfileTaskProgress, String>,
|
||||
pub user_id: __sdk::__query_builder::IxCol<ProfileTaskProgress, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileTaskProgress {
|
||||
type IxCols = ProfileTaskProgressIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileTaskProgressIxCols {
|
||||
progress_id: __sdk::__query_builder::IxCol::new(table_name, "progress_id"),
|
||||
user_id: __sdk::__query_builder::IxCol::new(table_name, "user_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileTaskProgress {}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 ProfileTaskRewardClaim {
|
||||
pub claim_id: String,
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
pub day_key: i64,
|
||||
pub reward_points: u64,
|
||||
pub wallet_ledger_id: String,
|
||||
pub claimed_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ProfileTaskRewardClaim {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `ProfileTaskRewardClaim`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileTaskRewardClaimCols {
|
||||
pub claim_id: __sdk::__query_builder::Col<ProfileTaskRewardClaim, String>,
|
||||
pub user_id: __sdk::__query_builder::Col<ProfileTaskRewardClaim, String>,
|
||||
pub task_id: __sdk::__query_builder::Col<ProfileTaskRewardClaim, String>,
|
||||
pub day_key: __sdk::__query_builder::Col<ProfileTaskRewardClaim, i64>,
|
||||
pub reward_points: __sdk::__query_builder::Col<ProfileTaskRewardClaim, u64>,
|
||||
pub wallet_ledger_id: __sdk::__query_builder::Col<ProfileTaskRewardClaim, String>,
|
||||
pub claimed_at: __sdk::__query_builder::Col<ProfileTaskRewardClaim, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileTaskRewardClaim {
|
||||
type Cols = ProfileTaskRewardClaimCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileTaskRewardClaimCols {
|
||||
claim_id: __sdk::__query_builder::Col::new(table_name, "claim_id"),
|
||||
user_id: __sdk::__query_builder::Col::new(table_name, "user_id"),
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
day_key: __sdk::__query_builder::Col::new(table_name, "day_key"),
|
||||
reward_points: __sdk::__query_builder::Col::new(table_name, "reward_points"),
|
||||
wallet_ledger_id: __sdk::__query_builder::Col::new(table_name, "wallet_ledger_id"),
|
||||
claimed_at: __sdk::__query_builder::Col::new(table_name, "claimed_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileTaskRewardClaim`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileTaskRewardClaimIxCols {
|
||||
pub claim_id: __sdk::__query_builder::IxCol<ProfileTaskRewardClaim, String>,
|
||||
pub user_id: __sdk::__query_builder::IxCol<ProfileTaskRewardClaim, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileTaskRewardClaim {
|
||||
type IxCols = ProfileTaskRewardClaimIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileTaskRewardClaimIxCols {
|
||||
claim_id: __sdk::__query_builder::IxCol::new(table_name, "claim_id"),
|
||||
user_id: __sdk::__query_builder::IxCol::new(table_name, "user_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileTaskRewardClaim {}
|
||||
@@ -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 RuntimeProfileInviteCodeAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileInviteCodeAdminListInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_invite_code_snapshot_type::RuntimeProfileInviteCodeSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileInviteCodeSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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 RuntimeProfileRedeemCodeAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileRedeemCodeAdminListInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_redeem_code_snapshot_type::RuntimeProfileRedeemCodeSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRedeemCodeSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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 RuntimeProfileTaskCenterGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskCenterGetInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_task_center_snapshot_type::RuntimeProfileTaskCenterSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileTaskCenterProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskCenterSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskCenterProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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_task_item_snapshot_type::RuntimeProfileTaskItemSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileTaskCenterSnapshot {
|
||||
pub user_id: String,
|
||||
pub day_key: i64,
|
||||
pub wallet_balance: u64,
|
||||
pub tasks: Vec<RuntimeProfileTaskItemSnapshot>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskCenterSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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 RuntimeProfileTaskClaimInput {
|
||||
pub user_id: String,
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskClaimInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_task_claim_snapshot_type::RuntimeProfileTaskClaimSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileTaskClaimProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskClaimSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskClaimProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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_task_center_snapshot_type::RuntimeProfileTaskCenterSnapshot;
|
||||
use super::runtime_profile_wallet_ledger_entry_snapshot_type::RuntimeProfileWalletLedgerEntrySnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskClaimSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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 RuntimeProfileTaskConfigAdminDisableInput {
|
||||
pub admin_user_id: String,
|
||||
pub task_id: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigAdminDisableInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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 RuntimeProfileTaskConfigAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigAdminListInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_task_config_snapshot_type::RuntimeProfileTaskConfigSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileTaskConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -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_task_config_snapshot_type::RuntimeProfileTaskConfigSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileTaskConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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_task_cycle_type::RuntimeProfileTaskCycle;
|
||||
use super::runtime_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigAdminUpsertInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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_task_cycle_type::RuntimeProfileTaskCycle;
|
||||
use super::runtime_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskConfigSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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)]
|
||||
#[derive(Copy, Eq, Hash)]
|
||||
pub enum RuntimeProfileTaskCycle {
|
||||
Daily,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskCycle {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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_task_cycle_type::RuntimeProfileTaskCycle;
|
||||
use super::runtime_profile_task_status_type::RuntimeProfileTaskStatus;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskItemSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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)]
|
||||
#[derive(Copy, Eq, Hash)]
|
||||
pub enum RuntimeProfileTaskStatus {
|
||||
Incomplete,
|
||||
|
||||
Claimable,
|
||||
|
||||
Claimed,
|
||||
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileTaskStatus {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -25,6 +25,8 @@ pub enum RuntimeProfileWalletLedgerSourceType {
|
||||
RedeemCodeReward,
|
||||
|
||||
PuzzleAuthorIncentiveClaim,
|
||||
|
||||
DailyTaskReward,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileWalletLedgerSourceType {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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)]
|
||||
#[derive(Copy, Eq, Hash)]
|
||||
pub enum RuntimeTrackingScopeKind {
|
||||
Site,
|
||||
|
||||
Work,
|
||||
|
||||
Module,
|
||||
|
||||
User,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeTrackingScopeKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct TrackingDailyStat {
|
||||
pub stat_id: String,
|
||||
pub event_key: String,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub scope_id: String,
|
||||
pub day_key: i64,
|
||||
pub count: u32,
|
||||
pub first_occurred_at: __sdk::Timestamp,
|
||||
pub last_occurred_at: __sdk::Timestamp,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for TrackingDailyStat {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `TrackingDailyStat`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct TrackingDailyStatCols {
|
||||
pub stat_id: __sdk::__query_builder::Col<TrackingDailyStat, String>,
|
||||
pub event_key: __sdk::__query_builder::Col<TrackingDailyStat, String>,
|
||||
pub scope_kind: __sdk::__query_builder::Col<TrackingDailyStat, RuntimeTrackingScopeKind>,
|
||||
pub scope_id: __sdk::__query_builder::Col<TrackingDailyStat, String>,
|
||||
pub day_key: __sdk::__query_builder::Col<TrackingDailyStat, i64>,
|
||||
pub count: __sdk::__query_builder::Col<TrackingDailyStat, u32>,
|
||||
pub first_occurred_at: __sdk::__query_builder::Col<TrackingDailyStat, __sdk::Timestamp>,
|
||||
pub last_occurred_at: __sdk::__query_builder::Col<TrackingDailyStat, __sdk::Timestamp>,
|
||||
pub updated_at: __sdk::__query_builder::Col<TrackingDailyStat, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for TrackingDailyStat {
|
||||
type Cols = TrackingDailyStatCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
TrackingDailyStatCols {
|
||||
stat_id: __sdk::__query_builder::Col::new(table_name, "stat_id"),
|
||||
event_key: __sdk::__query_builder::Col::new(table_name, "event_key"),
|
||||
scope_kind: __sdk::__query_builder::Col::new(table_name, "scope_kind"),
|
||||
scope_id: __sdk::__query_builder::Col::new(table_name, "scope_id"),
|
||||
day_key: __sdk::__query_builder::Col::new(table_name, "day_key"),
|
||||
count: __sdk::__query_builder::Col::new(table_name, "count"),
|
||||
first_occurred_at: __sdk::__query_builder::Col::new(table_name, "first_occurred_at"),
|
||||
last_occurred_at: __sdk::__query_builder::Col::new(table_name, "last_occurred_at"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `TrackingDailyStat`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct TrackingDailyStatIxCols {
|
||||
pub stat_id: __sdk::__query_builder::IxCol<TrackingDailyStat, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for TrackingDailyStat {
|
||||
type IxCols = TrackingDailyStatIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
TrackingDailyStatIxCols {
|
||||
stat_id: __sdk::__query_builder::IxCol::new(table_name, "stat_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for TrackingDailyStat {}
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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_tracking_scope_kind_type::RuntimeTrackingScopeKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct TrackingEvent {
|
||||
pub event_id: String,
|
||||
pub event_key: String,
|
||||
pub scope_kind: RuntimeTrackingScopeKind,
|
||||
pub scope_id: String,
|
||||
pub day_key: i64,
|
||||
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: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for TrackingEvent {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `TrackingEvent`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct TrackingEventCols {
|
||||
pub event_id: __sdk::__query_builder::Col<TrackingEvent, String>,
|
||||
pub event_key: __sdk::__query_builder::Col<TrackingEvent, String>,
|
||||
pub scope_kind: __sdk::__query_builder::Col<TrackingEvent, RuntimeTrackingScopeKind>,
|
||||
pub scope_id: __sdk::__query_builder::Col<TrackingEvent, String>,
|
||||
pub day_key: __sdk::__query_builder::Col<TrackingEvent, i64>,
|
||||
pub user_id: __sdk::__query_builder::Col<TrackingEvent, Option<String>>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<TrackingEvent, Option<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<TrackingEvent, Option<String>>,
|
||||
pub module_key: __sdk::__query_builder::Col<TrackingEvent, Option<String>>,
|
||||
pub metadata_json: __sdk::__query_builder::Col<TrackingEvent, String>,
|
||||
pub occurred_at: __sdk::__query_builder::Col<TrackingEvent, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for TrackingEvent {
|
||||
type Cols = TrackingEventCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
TrackingEventCols {
|
||||
event_id: __sdk::__query_builder::Col::new(table_name, "event_id"),
|
||||
event_key: __sdk::__query_builder::Col::new(table_name, "event_key"),
|
||||
scope_kind: __sdk::__query_builder::Col::new(table_name, "scope_kind"),
|
||||
scope_id: __sdk::__query_builder::Col::new(table_name, "scope_id"),
|
||||
day_key: __sdk::__query_builder::Col::new(table_name, "day_key"),
|
||||
user_id: __sdk::__query_builder::Col::new(table_name, "user_id"),
|
||||
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
|
||||
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
|
||||
module_key: __sdk::__query_builder::Col::new(table_name, "module_key"),
|
||||
metadata_json: __sdk::__query_builder::Col::new(table_name, "metadata_json"),
|
||||
occurred_at: __sdk::__query_builder::Col::new(table_name, "occurred_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `TrackingEvent`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct TrackingEventIxCols {
|
||||
pub event_id: __sdk::__query_builder::IxCol<TrackingEvent, String>,
|
||||
pub event_key: __sdk::__query_builder::IxCol<TrackingEvent, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for TrackingEvent {
|
||||
type IxCols = TrackingEventIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
TrackingEventIxCols {
|
||||
event_id: __sdk::__query_builder::IxCol::new(table_name, "event_id"),
|
||||
event_key: __sdk::__query_builder::IxCol::new(table_name, "event_key"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for TrackingEvent {}
|
||||
@@ -304,6 +304,143 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_task_center(
|
||||
&self,
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfileTaskCenterRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_task_center_get_input(user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection.procedures().get_profile_task_center_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_task_center_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn claim_profile_task_reward(
|
||||
&self,
|
||||
user_id: String,
|
||||
task_id: String,
|
||||
) -> Result<RuntimeProfileTaskClaimRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_task_claim_input(user_id, task_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.claim_profile_task_reward_and_return_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_task_claim_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_task_configs(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
) -> Result<Vec<RuntimeProfileTaskConfigRecord>, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_task_config_admin_list_input(admin_user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.admin_list_profile_task_configs_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_task_config_admin_list_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_task_config(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
task_id: String,
|
||||
title: String,
|
||||
description: String,
|
||||
event_key: String,
|
||||
cycle: DomainRuntimeProfileTaskCycle,
|
||||
scope_kind: DomainRuntimeTrackingScopeKind,
|
||||
threshold: u32,
|
||||
reward_points: u64,
|
||||
enabled: bool,
|
||||
sort_order: i32,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeProfileTaskConfigRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_task_config_admin_upsert_input(
|
||||
admin_user_id,
|
||||
task_id,
|
||||
title,
|
||||
description,
|
||||
event_key,
|
||||
cycle,
|
||||
scope_kind,
|
||||
threshold,
|
||||
reward_points,
|
||||
enabled,
|
||||
sort_order,
|
||||
updated_at_micros,
|
||||
)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.admin_upsert_profile_task_config_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_task_config_admin_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_disable_profile_task_config(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
task_id: String,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeProfileTaskConfigRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_task_config_admin_disable_input(
|
||||
admin_user_id,
|
||||
task_id,
|
||||
updated_at_micros,
|
||||
)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.admin_disable_profile_task_config_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_task_config_admin_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_redeem_code(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
@@ -343,6 +480,27 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_redeem_codes(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeRecord>, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_redeem_code_admin_list_input(admin_user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.admin_list_profile_redeem_codes_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_redeem_code_admin_list_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_disable_profile_redeem_code(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
@@ -399,6 +557,27 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_invite_codes(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeRecord>, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_invite_code_admin_list_input(admin_user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.admin_list_profile_invite_codes_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_runtime_profile_invite_code_admin_list_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_play_stats(
|
||||
&self,
|
||||
user_id: String,
|
||||
|
||||
@@ -161,6 +161,11 @@ macro_rules! migration_tables {
|
||||
user_browse_history,
|
||||
profile_dashboard_state,
|
||||
profile_wallet_ledger,
|
||||
tracking_event,
|
||||
tracking_daily_stat,
|
||||
profile_task_config,
|
||||
profile_task_progress,
|
||||
profile_task_reward_claim,
|
||||
profile_redeem_code,
|
||||
profile_redeem_code_usage,
|
||||
profile_invite_code,
|
||||
|
||||
@@ -4,6 +4,10 @@ const PUBLIC_WORK_PLAY_DAY_MICROS: i64 = 86_400_000_000;
|
||||
const PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS: i64 = 7;
|
||||
const PROFILE_REFERRAL_INVITED_USERS_LIMIT: usize = 20;
|
||||
const PROFILE_NEW_USER_REGISTRATION_LEDGER_PREFIX: &str = "new-user-registration";
|
||||
const PROFILE_TASK_SYSTEM_USER_ID: &str = "system:profile-task";
|
||||
const PROFILE_TASK_LOGIN_EVENT_ID_PREFIX: &str = "daily-login";
|
||||
const PROFILE_TRACKING_SITE_SCOPE_ID: &str = "site";
|
||||
const PROFILE_TRACKING_PROFILE_MODULE_KEY: &str = "profile";
|
||||
|
||||
#[spacetimedb::table(accessor = profile_dashboard_state)]
|
||||
pub struct ProfileDashboardState {
|
||||
@@ -33,6 +37,115 @@ pub struct ProfileWalletLedger {
|
||||
pub(crate) created_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = tracking_event,
|
||||
index(accessor = by_tracking_event_event_key, btree(columns = [event_key])),
|
||||
index(
|
||||
accessor = by_tracking_event_scope,
|
||||
btree(columns = [scope_kind, scope_id])
|
||||
),
|
||||
index(
|
||||
accessor = by_tracking_event_user,
|
||||
btree(columns = [user_id, occurred_at])
|
||||
)
|
||||
)]
|
||||
pub struct TrackingEvent {
|
||||
#[primary_key]
|
||||
pub(crate) event_id: String,
|
||||
pub(crate) event_key: String,
|
||||
pub(crate) scope_kind: RuntimeTrackingScopeKind,
|
||||
pub(crate) scope_id: String,
|
||||
pub(crate) day_key: i64,
|
||||
pub(crate) user_id: Option<String>,
|
||||
pub(crate) owner_user_id: Option<String>,
|
||||
pub(crate) profile_id: Option<String>,
|
||||
pub(crate) module_key: Option<String>,
|
||||
pub(crate) metadata_json: String,
|
||||
pub(crate) occurred_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = tracking_daily_stat,
|
||||
index(
|
||||
accessor = by_tracking_daily_stat_event_day,
|
||||
btree(columns = [event_key, day_key])
|
||||
),
|
||||
index(
|
||||
accessor = by_tracking_daily_stat_scope_day,
|
||||
btree(columns = [scope_kind, scope_id, day_key])
|
||||
)
|
||||
)]
|
||||
pub struct TrackingDailyStat {
|
||||
#[primary_key]
|
||||
pub(crate) stat_id: String,
|
||||
pub(crate) event_key: String,
|
||||
pub(crate) scope_kind: RuntimeTrackingScopeKind,
|
||||
pub(crate) scope_id: String,
|
||||
pub(crate) day_key: i64,
|
||||
pub(crate) count: u32,
|
||||
pub(crate) first_occurred_at: Timestamp,
|
||||
pub(crate) last_occurred_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = profile_task_config)]
|
||||
pub struct ProfileTaskConfig {
|
||||
#[primary_key]
|
||||
pub(crate) task_id: String,
|
||||
pub(crate) title: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) event_key: String,
|
||||
pub(crate) cycle: RuntimeProfileTaskCycle,
|
||||
pub(crate) scope_kind: RuntimeTrackingScopeKind,
|
||||
pub(crate) threshold: u32,
|
||||
pub(crate) reward_points: u64,
|
||||
pub(crate) enabled: bool,
|
||||
pub(crate) sort_order: i32,
|
||||
pub(crate) created_by: String,
|
||||
pub(crate) created_at: Timestamp,
|
||||
pub(crate) updated_by: String,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = profile_task_progress,
|
||||
index(accessor = by_profile_task_progress_user, btree(columns = [user_id])),
|
||||
index(
|
||||
accessor = by_profile_task_progress_user_task,
|
||||
btree(columns = [user_id, task_id])
|
||||
)
|
||||
)]
|
||||
pub struct ProfileTaskProgress {
|
||||
#[primary_key]
|
||||
pub(crate) progress_id: String,
|
||||
pub(crate) user_id: String,
|
||||
pub(crate) task_id: String,
|
||||
pub(crate) day_key: i64,
|
||||
pub(crate) progress_count: u32,
|
||||
pub(crate) threshold: u32,
|
||||
pub(crate) status: RuntimeProfileTaskStatus,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = profile_task_reward_claim,
|
||||
index(accessor = by_profile_task_claim_user, btree(columns = [user_id])),
|
||||
index(
|
||||
accessor = by_profile_task_claim_user_task,
|
||||
btree(columns = [user_id, task_id])
|
||||
)
|
||||
)]
|
||||
pub struct ProfileTaskRewardClaim {
|
||||
#[primary_key]
|
||||
pub(crate) claim_id: String,
|
||||
pub(crate) user_id: String,
|
||||
pub(crate) task_id: String,
|
||||
pub(crate) day_key: i64,
|
||||
pub(crate) reward_points: u64,
|
||||
pub(crate) wallet_ledger_id: String,
|
||||
pub(crate) claimed_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = profile_redeem_code)]
|
||||
pub struct ProfileRedeemCode {
|
||||
#[primary_key]
|
||||
@@ -355,6 +468,103 @@ pub fn list_profile_wallet_ledger(
|
||||
}
|
||||
}
|
||||
|
||||
// 任务中心读取会顺手记录当日登录埋点,确保“每日登录”只依赖后端事实。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_profile_task_center(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileTaskCenterGetInput,
|
||||
) -> RuntimeProfileTaskCenterProcedureResult {
|
||||
match ctx.try_with_tx(|tx| get_profile_task_center_snapshot(tx, input.clone(), true)) {
|
||||
Ok(record) => RuntimeProfileTaskCenterProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileTaskCenterProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 领奖记录与光点流水在同一事务内写入,避免任务状态和钱包余额漂移。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn claim_profile_task_reward_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileTaskClaimInput,
|
||||
) -> RuntimeProfileTaskClaimProcedureResult {
|
||||
match ctx.try_with_tx(|tx| claim_profile_task_reward_record(tx, input.clone())) {
|
||||
Ok(record) => RuntimeProfileTaskClaimProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileTaskClaimProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_list_profile_task_configs(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileTaskConfigAdminListInput,
|
||||
) -> RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
match ctx.try_with_tx(|tx| list_profile_task_config_snapshots(tx, input.clone())) {
|
||||
Ok(entries) => RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
ok: true,
|
||||
entries,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileTaskConfigAdminListProcedureResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_upsert_profile_task_config(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileTaskConfigAdminUpsertInput,
|
||||
) -> RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
match ctx.try_with_tx(|tx| upsert_profile_task_config_record(tx, input.clone())) {
|
||||
Ok(record) => RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_disable_profile_task_config(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
) -> RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
match ctx.try_with_tx(|tx| disable_profile_task_config_record(tx, input.clone())) {
|
||||
Ok(record) => RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileTaskConfigAdminProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 新用户注册赠送由后端注册链路调用;流水 ID 固定,保证重试不重复发放。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn grant_new_user_registration_wallet_reward(
|
||||
@@ -591,6 +801,25 @@ pub fn admin_disable_profile_redeem_code(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_list_profile_redeem_codes(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
) -> RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
match ctx.try_with_tx(|tx| admin_list_profile_redeem_code_records(tx, input.clone())) {
|
||||
Ok(entries) => RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
ok: true,
|
||||
entries,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_upsert_profile_invite_code(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -610,6 +839,25 @@ pub fn admin_upsert_profile_invite_code(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn admin_list_profile_invite_codes(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
) -> RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
match ctx.try_with_tx(|tx| admin_list_profile_invite_code_records(tx, input.clone())) {
|
||||
Ok(entries) => RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
ok: true,
|
||||
entries,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn list_profile_save_archive_rows(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileSaveArchiveListInput,
|
||||
@@ -2136,6 +2384,533 @@ fn build_profile_recharge_center_snapshot(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_profile_task_center_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileTaskCenterGetInput,
|
||||
record_login_event: bool,
|
||||
) -> Result<RuntimeProfileTaskCenterSnapshot, String> {
|
||||
let validated_input = build_runtime_profile_task_center_get_input(input.user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
ensure_default_profile_task_config(ctx);
|
||||
|
||||
if record_login_event {
|
||||
record_daily_login_tracking_event(ctx, &validated_input.user_id)?;
|
||||
}
|
||||
|
||||
Ok(build_profile_task_center_snapshot(
|
||||
ctx,
|
||||
&validated_input.user_id,
|
||||
ctx.timestamp,
|
||||
))
|
||||
}
|
||||
|
||||
fn claim_profile_task_reward_record(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileTaskClaimInput,
|
||||
) -> Result<RuntimeProfileTaskClaimSnapshot, String> {
|
||||
let validated_input = build_runtime_profile_task_claim_input(input.user_id, input.task_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
ensure_default_profile_task_config(ctx);
|
||||
|
||||
let config = ctx
|
||||
.db
|
||||
.profile_task_config()
|
||||
.task_id()
|
||||
.find(&validated_input.task_id)
|
||||
.ok_or_else(|| RuntimeProfileFieldError::MissingTaskId.to_string())?;
|
||||
if !config.enabled {
|
||||
return Err(RuntimeProfileFieldError::TaskDisabled.to_string());
|
||||
}
|
||||
|
||||
if is_daily_login_task_config(&config) {
|
||||
record_daily_login_tracking_event(ctx, &validated_input.user_id)?;
|
||||
}
|
||||
let day_key = runtime_profile_beijing_day_key(ctx.timestamp.to_micros_since_unix_epoch());
|
||||
let claim_id =
|
||||
build_runtime_profile_task_claim_id(&validated_input.user_id, &config.task_id, day_key);
|
||||
if ctx
|
||||
.db
|
||||
.profile_task_reward_claim()
|
||||
.claim_id()
|
||||
.find(&claim_id)
|
||||
.is_some()
|
||||
{
|
||||
return Err(RuntimeProfileFieldError::TaskAlreadyClaimed.to_string());
|
||||
}
|
||||
|
||||
let progress_count = profile_task_progress_count(ctx, &validated_input.user_id, &config);
|
||||
if progress_count < config.threshold {
|
||||
return Err(RuntimeProfileFieldError::TaskNotClaimable.to_string());
|
||||
}
|
||||
|
||||
let ledger_id = build_runtime_profile_task_reward_ledger_id(
|
||||
&validated_input.user_id,
|
||||
&config.task_id,
|
||||
day_key,
|
||||
);
|
||||
let wallet_balance = grant_profile_wallet_points(
|
||||
ctx,
|
||||
&validated_input.user_id,
|
||||
config.reward_points,
|
||||
RuntimeProfileWalletLedgerSourceType::DailyTaskReward,
|
||||
&ledger_id,
|
||||
ctx.timestamp,
|
||||
)?;
|
||||
let claim = ctx
|
||||
.db
|
||||
.profile_task_reward_claim()
|
||||
.insert(ProfileTaskRewardClaim {
|
||||
claim_id: claim_id.clone(),
|
||||
user_id: validated_input.user_id.clone(),
|
||||
task_id: config.task_id.clone(),
|
||||
day_key,
|
||||
reward_points: config.reward_points,
|
||||
wallet_ledger_id: ledger_id.clone(),
|
||||
claimed_at: ctx.timestamp,
|
||||
});
|
||||
|
||||
refresh_profile_task_progress(ctx, &validated_input.user_id, &config, day_key);
|
||||
let ledger_entry = ctx
|
||||
.db
|
||||
.profile_wallet_ledger()
|
||||
.wallet_ledger_id()
|
||||
.find(&ledger_id)
|
||||
.ok_or_else(|| "任务奖励钱包流水写入失败".to_string())?;
|
||||
|
||||
Ok(RuntimeProfileTaskClaimSnapshot {
|
||||
user_id: validated_input.user_id.clone(),
|
||||
task_id: config.task_id.clone(),
|
||||
day_key,
|
||||
reward_points: claim.reward_points,
|
||||
wallet_balance,
|
||||
ledger_entry: build_profile_wallet_ledger_snapshot_from_row(&ledger_entry),
|
||||
center: build_profile_task_center_snapshot(ctx, &validated_input.user_id, ctx.timestamp),
|
||||
})
|
||||
}
|
||||
|
||||
fn list_profile_task_config_snapshots(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileTaskConfigAdminListInput,
|
||||
) -> Result<Vec<RuntimeProfileTaskConfigSnapshot>, String> {
|
||||
let _validated_input = build_runtime_profile_task_config_admin_list_input(input.admin_user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
ensure_default_profile_task_config(ctx);
|
||||
|
||||
let mut entries = ctx
|
||||
.db
|
||||
.profile_task_config()
|
||||
.iter()
|
||||
.map(|row| build_profile_task_config_snapshot_from_row(&row))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|left, right| {
|
||||
left.sort_order
|
||||
.cmp(&right.sort_order)
|
||||
.then_with(|| left.task_id.cmp(&right.task_id))
|
||||
});
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn admin_list_profile_redeem_code_records(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeSnapshot>, String> {
|
||||
let _validated_input = build_runtime_profile_redeem_code_admin_list_input(input.admin_user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
let mut entries = ctx
|
||||
.db
|
||||
.profile_redeem_code()
|
||||
.iter()
|
||||
.map(|row| build_profile_redeem_code_snapshot_from_row(&row))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_micros
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.code.cmp(&right.code))
|
||||
});
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn admin_list_profile_invite_code_records(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeSnapshot>, String> {
|
||||
let _validated_input = build_runtime_profile_invite_code_admin_list_input(input.admin_user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
let mut entries = ctx
|
||||
.db
|
||||
.profile_invite_code()
|
||||
.iter()
|
||||
.filter(|row| is_admin_profile_invite_code_user_id(&row.user_id))
|
||||
.map(|row| build_profile_invite_code_snapshot_from_row(&row))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_micros
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.invite_code.cmp(&right.invite_code))
|
||||
});
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn upsert_profile_task_config_record(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileTaskConfigAdminUpsertInput,
|
||||
) -> Result<RuntimeProfileTaskConfigSnapshot, String> {
|
||||
let validated_input = build_runtime_profile_task_config_admin_upsert_input(
|
||||
input.admin_user_id,
|
||||
input.task_id,
|
||||
input.title,
|
||||
input.description,
|
||||
input.event_key,
|
||||
input.cycle,
|
||||
input.scope_kind,
|
||||
input.threshold,
|
||||
input.reward_points,
|
||||
input.enabled,
|
||||
input.sort_order,
|
||||
input.updated_at_micros,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(validated_input.updated_at_micros);
|
||||
let existing = ctx
|
||||
.db
|
||||
.profile_task_config()
|
||||
.task_id()
|
||||
.find(&validated_input.task_id);
|
||||
if let Some(row) = existing.as_ref() {
|
||||
ctx.db.profile_task_config().task_id().delete(&row.task_id);
|
||||
}
|
||||
|
||||
let inserted = ctx.db.profile_task_config().insert(ProfileTaskConfig {
|
||||
task_id: validated_input.task_id,
|
||||
title: validated_input.title,
|
||||
description: validated_input.description,
|
||||
event_key: validated_input.event_key,
|
||||
cycle: validated_input.cycle,
|
||||
scope_kind: validated_input.scope_kind,
|
||||
threshold: validated_input.threshold,
|
||||
reward_points: validated_input.reward_points,
|
||||
enabled: validated_input.enabled,
|
||||
sort_order: validated_input.sort_order,
|
||||
created_by: existing
|
||||
.as_ref()
|
||||
.map(|row| row.created_by.clone())
|
||||
.unwrap_or_else(|| validated_input.admin_user_id.clone()),
|
||||
created_at: existing
|
||||
.as_ref()
|
||||
.map(|row| row.created_at)
|
||||
.unwrap_or(updated_at),
|
||||
updated_by: validated_input.admin_user_id,
|
||||
updated_at,
|
||||
});
|
||||
Ok(build_profile_task_config_snapshot_from_row(&inserted))
|
||||
}
|
||||
|
||||
fn disable_profile_task_config_record(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
) -> Result<RuntimeProfileTaskConfigSnapshot, String> {
|
||||
let validated_input = build_runtime_profile_task_config_admin_disable_input(
|
||||
input.admin_user_id,
|
||||
input.task_id,
|
||||
input.updated_at_micros,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let row = ctx
|
||||
.db
|
||||
.profile_task_config()
|
||||
.task_id()
|
||||
.find(&validated_input.task_id)
|
||||
.ok_or_else(|| RuntimeProfileFieldError::MissingTaskId.to_string())?;
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(validated_input.updated_at_micros);
|
||||
ctx.db.profile_task_config().task_id().delete(&row.task_id);
|
||||
let inserted = ctx.db.profile_task_config().insert(ProfileTaskConfig {
|
||||
enabled: false,
|
||||
updated_by: validated_input.admin_user_id,
|
||||
updated_at,
|
||||
..row
|
||||
});
|
||||
Ok(build_profile_task_config_snapshot_from_row(&inserted))
|
||||
}
|
||||
|
||||
fn build_profile_task_center_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
updated_at: Timestamp,
|
||||
) -> RuntimeProfileTaskCenterSnapshot {
|
||||
ensure_default_profile_task_config(ctx);
|
||||
let day_key = runtime_profile_beijing_day_key(updated_at.to_micros_since_unix_epoch());
|
||||
let mut configs = ctx.db.profile_task_config().iter().collect::<Vec<_>>();
|
||||
configs.sort_by(|left, right| {
|
||||
left.sort_order
|
||||
.cmp(&right.sort_order)
|
||||
.then_with(|| left.task_id.cmp(&right.task_id))
|
||||
});
|
||||
let tasks = configs
|
||||
.into_iter()
|
||||
.map(|config| {
|
||||
let progress_count = profile_task_progress_count(ctx, user_id, &config);
|
||||
refresh_profile_task_progress(ctx, user_id, &config, day_key);
|
||||
let claim = ctx.db.profile_task_reward_claim().claim_id().find(
|
||||
&build_runtime_profile_task_claim_id(user_id, &config.task_id, day_key),
|
||||
);
|
||||
RuntimeProfileTaskItemSnapshot {
|
||||
task_id: config.task_id,
|
||||
title: config.title,
|
||||
description: config.description,
|
||||
event_key: config.event_key,
|
||||
cycle: config.cycle,
|
||||
threshold: config.threshold,
|
||||
progress_count,
|
||||
reward_points: config.reward_points,
|
||||
status: resolve_runtime_profile_task_status(
|
||||
config.enabled,
|
||||
progress_count,
|
||||
config.threshold,
|
||||
claim.is_some(),
|
||||
),
|
||||
day_key,
|
||||
claimed_at_micros: claim.map(|row| row.claimed_at.to_micros_since_unix_epoch()),
|
||||
updated_at_micros: updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
RuntimeProfileTaskCenterSnapshot {
|
||||
user_id: user_id.to_string(),
|
||||
day_key,
|
||||
wallet_balance: profile_wallet_balance(ctx, user_id),
|
||||
tasks,
|
||||
updated_at_micros: updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_profile_task_progress(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
config: &ProfileTaskConfig,
|
||||
day_key: i64,
|
||||
) -> ProfileTaskProgress {
|
||||
let progress_id = build_runtime_profile_task_progress_id(user_id, &config.task_id, day_key);
|
||||
if let Some(existing) = ctx
|
||||
.db
|
||||
.profile_task_progress()
|
||||
.progress_id()
|
||||
.find(&progress_id)
|
||||
{
|
||||
ctx.db
|
||||
.profile_task_progress()
|
||||
.progress_id()
|
||||
.delete(&existing.progress_id);
|
||||
}
|
||||
let progress_count = profile_task_progress_count(ctx, user_id, config);
|
||||
let claimed = ctx
|
||||
.db
|
||||
.profile_task_reward_claim()
|
||||
.claim_id()
|
||||
.find(&build_runtime_profile_task_claim_id(
|
||||
user_id,
|
||||
&config.task_id,
|
||||
day_key,
|
||||
))
|
||||
.is_some();
|
||||
ctx.db.profile_task_progress().insert(ProfileTaskProgress {
|
||||
progress_id,
|
||||
user_id: user_id.to_string(),
|
||||
task_id: config.task_id.clone(),
|
||||
day_key,
|
||||
progress_count,
|
||||
threshold: config.threshold,
|
||||
status: resolve_runtime_profile_task_status(
|
||||
config.enabled,
|
||||
progress_count,
|
||||
config.threshold,
|
||||
claimed,
|
||||
),
|
||||
updated_at: ctx.timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
fn profile_task_progress_count(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
config: &ProfileTaskConfig,
|
||||
) -> u32 {
|
||||
let day_key = runtime_profile_beijing_day_key(ctx.timestamp.to_micros_since_unix_epoch());
|
||||
let scope_id = profile_task_tracking_scope_id(user_id, config);
|
||||
ctx.db
|
||||
.tracking_daily_stat()
|
||||
.stat_id()
|
||||
.find(&build_runtime_tracking_daily_stat_id(
|
||||
&config.event_key,
|
||||
config.scope_kind,
|
||||
&scope_id,
|
||||
day_key,
|
||||
))
|
||||
.map(|row| row.count)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn profile_task_tracking_scope_id(user_id: &str, config: &ProfileTaskConfig) -> String {
|
||||
match config.scope_kind {
|
||||
RuntimeTrackingScopeKind::Site => PROFILE_TRACKING_SITE_SCOPE_ID.to_string(),
|
||||
RuntimeTrackingScopeKind::Module => PROFILE_TRACKING_PROFILE_MODULE_KEY.to_string(),
|
||||
RuntimeTrackingScopeKind::User => user_id.to_string(),
|
||||
RuntimeTrackingScopeKind::Work => user_id.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_daily_login_task_config(config: &ProfileTaskConfig) -> bool {
|
||||
config.task_id == PROFILE_TASK_ID_DAILY_LOGIN
|
||||
&& config.event_key == PROFILE_TASK_EVENT_KEY_DAILY_LOGIN
|
||||
&& config.scope_kind == RuntimeTrackingScopeKind::User
|
||||
}
|
||||
|
||||
fn record_daily_login_tracking_event(ctx: &ReducerContext, user_id: &str) -> Result<(), String> {
|
||||
let day_key = runtime_profile_beijing_day_key(ctx.timestamp.to_micros_since_unix_epoch());
|
||||
let event_id = format!(
|
||||
"{}:{}:{}",
|
||||
PROFILE_TASK_LOGIN_EVENT_ID_PREFIX,
|
||||
user_id.trim(),
|
||||
day_key
|
||||
);
|
||||
if ctx.db.tracking_event().event_id().find(&event_id).is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
record_tracking_event(
|
||||
ctx,
|
||||
RuntimeTrackingEventInput {
|
||||
event_id,
|
||||
event_key: PROFILE_TASK_EVENT_KEY_DAILY_LOGIN.to_string(),
|
||||
scope_kind: RuntimeTrackingScopeKind::User,
|
||||
scope_id: user_id.to_string(),
|
||||
user_id: Some(user_id.to_string()),
|
||||
owner_user_id: None,
|
||||
profile_id: None,
|
||||
module_key: Some(PROFILE_TRACKING_PROFILE_MODULE_KEY.to_string()),
|
||||
metadata_json: PROFILE_INVITE_CODE_METADATA_DEFAULT_JSON.to_string(),
|
||||
occurred_at_micros: ctx.timestamp.to_micros_since_unix_epoch(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn record_tracking_event(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeTrackingEventInput,
|
||||
) -> Result<(), String> {
|
||||
let validated_input = build_runtime_tracking_event_input(
|
||||
input.event_id,
|
||||
input.event_key,
|
||||
input.scope_kind,
|
||||
input.scope_id,
|
||||
input.user_id,
|
||||
input.owner_user_id,
|
||||
input.profile_id,
|
||||
input.module_key,
|
||||
input.metadata_json,
|
||||
input.occurred_at_micros,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let occurred_at = Timestamp::from_micros_since_unix_epoch(validated_input.occurred_at_micros);
|
||||
let day_key = runtime_profile_beijing_day_key(validated_input.occurred_at_micros);
|
||||
ctx.db.tracking_event().insert(TrackingEvent {
|
||||
event_id: validated_input.event_id,
|
||||
event_key: validated_input.event_key.clone(),
|
||||
scope_kind: validated_input.scope_kind,
|
||||
scope_id: validated_input.scope_id.clone(),
|
||||
day_key,
|
||||
user_id: validated_input.user_id,
|
||||
owner_user_id: validated_input.owner_user_id,
|
||||
profile_id: validated_input.profile_id,
|
||||
module_key: validated_input.module_key,
|
||||
metadata_json: validated_input.metadata_json,
|
||||
occurred_at,
|
||||
});
|
||||
upsert_tracking_daily_stat(
|
||||
ctx,
|
||||
&validated_input.event_key,
|
||||
validated_input.scope_kind,
|
||||
&validated_input.scope_id,
|
||||
day_key,
|
||||
occurred_at,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upsert_tracking_daily_stat(
|
||||
ctx: &ReducerContext,
|
||||
event_key: &str,
|
||||
scope_kind: RuntimeTrackingScopeKind,
|
||||
scope_id: &str,
|
||||
day_key: i64,
|
||||
occurred_at: Timestamp,
|
||||
) {
|
||||
let stat_id = build_runtime_tracking_daily_stat_id(event_key, scope_kind, scope_id, day_key);
|
||||
let existing = ctx.db.tracking_daily_stat().stat_id().find(&stat_id);
|
||||
if let Some(row) = existing {
|
||||
ctx.db.tracking_daily_stat().stat_id().delete(&row.stat_id);
|
||||
ctx.db.tracking_daily_stat().insert(TrackingDailyStat {
|
||||
stat_id,
|
||||
event_key: row.event_key,
|
||||
scope_kind: row.scope_kind,
|
||||
scope_id: row.scope_id,
|
||||
day_key: row.day_key,
|
||||
count: row.count.saturating_add(1),
|
||||
first_occurred_at: row.first_occurred_at,
|
||||
last_occurred_at: occurred_at,
|
||||
updated_at: occurred_at,
|
||||
});
|
||||
} else {
|
||||
ctx.db.tracking_daily_stat().insert(TrackingDailyStat {
|
||||
stat_id,
|
||||
event_key: event_key.to_string(),
|
||||
scope_kind,
|
||||
scope_id: scope_id.to_string(),
|
||||
day_key,
|
||||
count: 1,
|
||||
first_occurred_at: occurred_at,
|
||||
last_occurred_at: occurred_at,
|
||||
updated_at: occurred_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_default_profile_task_config(ctx: &ReducerContext) -> ProfileTaskConfig {
|
||||
if let Some(row) = ctx
|
||||
.db
|
||||
.profile_task_config()
|
||||
.task_id()
|
||||
.find(&PROFILE_TASK_ID_DAILY_LOGIN.to_string())
|
||||
{
|
||||
return row;
|
||||
}
|
||||
|
||||
let default_config = build_default_runtime_profile_task_config(
|
||||
ctx.timestamp.to_micros_since_unix_epoch(),
|
||||
PROFILE_TASK_SYSTEM_USER_ID.to_string(),
|
||||
);
|
||||
ctx.db.profile_task_config().insert(ProfileTaskConfig {
|
||||
task_id: default_config.task_id,
|
||||
title: default_config.title,
|
||||
description: default_config.description,
|
||||
event_key: default_config.event_key,
|
||||
cycle: default_config.cycle,
|
||||
scope_kind: default_config.scope_kind,
|
||||
threshold: default_config.threshold,
|
||||
reward_points: default_config.reward_points,
|
||||
enabled: default_config.enabled,
|
||||
sort_order: default_config.sort_order,
|
||||
created_by: default_config.created_by,
|
||||
created_at: ctx.timestamp,
|
||||
updated_by: default_config.updated_by,
|
||||
updated_at: ctx.timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_profile_membership_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
@@ -2485,6 +3260,27 @@ fn build_profile_wallet_ledger_snapshot_from_row(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_task_config_snapshot_from_row(
|
||||
row: &ProfileTaskConfig,
|
||||
) -> RuntimeProfileTaskConfigSnapshot {
|
||||
RuntimeProfileTaskConfigSnapshot {
|
||||
task_id: row.task_id.clone(),
|
||||
title: row.title.clone(),
|
||||
description: row.description.clone(),
|
||||
event_key: row.event_key.clone(),
|
||||
cycle: row.cycle,
|
||||
scope_kind: row.scope_kind,
|
||||
threshold: row.threshold,
|
||||
reward_points: row.reward_points,
|
||||
enabled: row.enabled,
|
||||
sort_order: row.sort_order,
|
||||
created_by: row.created_by.clone(),
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
updated_by: row.updated_by.clone(),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_recharge_order_snapshot_from_row(
|
||||
row: &ProfileRechargeOrder,
|
||||
) -> RuntimeProfileRechargeOrderSnapshot {
|
||||
|
||||
Reference in New Issue
Block a user