1
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"next_user_id": 2,
|
||||
"users_by_username": {
|
||||
"phone_00000002": {
|
||||
"user": {
|
||||
"id": "user_00000001",
|
||||
"public_user_code": "SY-00000001",
|
||||
"username": "phone_00000002",
|
||||
"display_name": "138****8111",
|
||||
"phone_number_masked": "138****8111",
|
||||
"login_method": "Phone",
|
||||
"binding_status": "Active",
|
||||
"wechat_bound": false,
|
||||
"token_version": 1
|
||||
},
|
||||
"password_hash": "$argon2id$v=19$m=19456,t=2,p=1$qnArSgOrZvcQxap4KAMMnA$+K+gQgf7h0jQibJLuvAlOeHnNNYutTvLVDAyo1hqS/o",
|
||||
"password_login_enabled": false,
|
||||
"phone_number": "+8613800138111"
|
||||
}
|
||||
},
|
||||
"phone_to_user_id": {
|
||||
"+8613800138111": "user_00000001"
|
||||
},
|
||||
"sessions_by_id": {},
|
||||
"session_id_by_refresh_token_hash": {},
|
||||
"wechat_identity_by_provider_uid": {},
|
||||
"user_id_by_provider_union_id": {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"next_user_id": 2,
|
||||
"users_by_username": {
|
||||
"phone_00000002": {
|
||||
"user": {
|
||||
"id": "user_00000001",
|
||||
"public_user_code": "SY-00000001",
|
||||
"username": "phone_00000002",
|
||||
"display_name": "138****8112",
|
||||
"phone_number_masked": "138****8112",
|
||||
"login_method": "Phone",
|
||||
"binding_status": "Active",
|
||||
"wechat_bound": false,
|
||||
"token_version": 1
|
||||
},
|
||||
"password_hash": "$argon2id$v=19$m=19456,t=2,p=1$0HR2g/fKOw9EFHz7BuYtGg$cpXb5KBwbEXPxPJHA4Bk1U7NtM97GhGTq7VK6jCJ+lA",
|
||||
"password_login_enabled": false,
|
||||
"phone_number": "+8613800138112"
|
||||
}
|
||||
},
|
||||
"phone_to_user_id": {
|
||||
"+8613800138112": "user_00000001"
|
||||
},
|
||||
"sessions_by_id": {},
|
||||
"session_id_by_refresh_token_hash": {},
|
||||
"wechat_identity_by_provider_uid": {},
|
||||
"user_id_by_provider_union_id": {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"next_user_id": 2,
|
||||
"users_by_username": {
|
||||
"phone_00000002": {
|
||||
"user": {
|
||||
"id": "user_00000001",
|
||||
"public_user_code": "SY-00000001",
|
||||
"username": "phone_00000002",
|
||||
"display_name": "138****8110",
|
||||
"phone_number_masked": "138****8110",
|
||||
"login_method": "Phone",
|
||||
"binding_status": "Active",
|
||||
"wechat_bound": false,
|
||||
"token_version": 1
|
||||
},
|
||||
"password_hash": "$argon2id$v=19$m=19456,t=2,p=1$fEeSrVyialDeb8rarDSpdA$HFihZiuCOyaz8F5iNukmobeiHI/EpYWdeQzhbIYR4zk",
|
||||
"password_login_enabled": false,
|
||||
"phone_number": "+8613800138110"
|
||||
}
|
||||
},
|
||||
"phone_to_user_id": {
|
||||
"+8613800138110": "user_00000001"
|
||||
},
|
||||
"sessions_by_id": {},
|
||||
"session_id_by_refresh_token_hash": {},
|
||||
"wechat_identity_by_provider_uid": {},
|
||||
"user_id_by_provider_union_id": {}
|
||||
}
|
||||
56
server-rs/crates/api-server/server-rs/.data/auth-store.json
Normal file
56
server-rs/crates/api-server/server-rs/.data/auth-store.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"next_user_id": 2,
|
||||
"users_by_username": {
|
||||
"phone_00000002": {
|
||||
"user": {
|
||||
"id": "user_00000001",
|
||||
"public_user_code": "SY-00000001",
|
||||
"username": "phone_00000002",
|
||||
"display_name": "138****8000",
|
||||
"phone_number_masked": "138****8000",
|
||||
"login_method": "Phone",
|
||||
"binding_status": "Active",
|
||||
"wechat_bound": false,
|
||||
"token_version": 1
|
||||
},
|
||||
"password_hash": "$argon2id$v=19$m=19456,t=2,p=1$hoXmK/LzABj2QfWZSO3SNA$Qg71V2iZCPyLOsoQLffiCv3KPkWVNSAsP6IooTIXi/w",
|
||||
"password_login_enabled": false,
|
||||
"phone_number": "+8613800138000"
|
||||
}
|
||||
},
|
||||
"phone_to_user_id": {
|
||||
"+8613800138000": "user_00000001"
|
||||
},
|
||||
"sessions_by_id": {
|
||||
"usess_52522126b58d40e3b9e503808dd11e2c": {
|
||||
"session": {
|
||||
"session_id": "usess_52522126b58d40e3b9e503808dd11e2c",
|
||||
"user_id": "user_00000001",
|
||||
"refresh_token_hash": "f42140526caea3e4a9f533bcc2d8799feae4f96769ea975ef771b1ae11e4dbe9",
|
||||
"issued_by_provider": "Phone",
|
||||
"client_info": {
|
||||
"client_type": "web_browser",
|
||||
"client_runtime": "unknown",
|
||||
"client_platform": "unknown",
|
||||
"client_instance_id": null,
|
||||
"device_fingerprint": null,
|
||||
"device_display_name": "未知设备 / 未知客户端",
|
||||
"mini_program_app_id": null,
|
||||
"mini_program_env": null,
|
||||
"user_agent": null,
|
||||
"ip": null
|
||||
},
|
||||
"expires_at": "2026-05-25T15:41:01.0856147Z",
|
||||
"revoked_at": null,
|
||||
"created_at": "2026-04-25T15:41:01.0856147Z",
|
||||
"updated_at": "2026-04-25T15:41:01.0856147Z",
|
||||
"last_seen_at": "2026-04-25T15:41:01.0856147Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"session_id_by_refresh_token_hash": {
|
||||
"f42140526caea3e4a9f533bcc2d8799feae4f96769ea975ef771b1ae11e4dbe9": "usess_52522126b58d40e3b9e503808dd11e2c"
|
||||
},
|
||||
"wechat_identity_by_provider_uid": {},
|
||||
"user_id_by_provider_union_id": {}
|
||||
}
|
||||
@@ -95,7 +95,8 @@ use crate::{
|
||||
runtime_inventory::get_runtime_inventory_state,
|
||||
runtime_profile::{
|
||||
create_profile_recharge_order, get_profile_dashboard, get_profile_play_stats,
|
||||
get_profile_recharge_center, get_profile_wallet_ledger,
|
||||
get_profile_recharge_center, get_profile_referral_invite_center, get_profile_wallet_ledger,
|
||||
redeem_profile_referral_invite_code,
|
||||
},
|
||||
runtime_save::{
|
||||
delete_runtime_snapshot, get_runtime_snapshot, list_profile_save_archives,
|
||||
@@ -820,6 +821,34 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/profile/referrals/invite-center",
|
||||
get(get_profile_referral_invite_center).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/profile/referrals/invite-center",
|
||||
get(get_profile_referral_invite_center).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/profile/referrals/redeem-code",
|
||||
post(redeem_profile_referral_invite_code).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/profile/referrals/redeem-code",
|
||||
post(redeem_profile_referral_invite_code).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/profile/play-stats",
|
||||
get(get_profile_play_stats).route_layer(middleware::from_fn_with_state(
|
||||
|
||||
@@ -434,13 +434,7 @@ pub async fn execute_big_fish_action(
|
||||
let now = current_utc_micros();
|
||||
let session = match payload.action.trim() {
|
||||
"big_fish_compile_draft" => {
|
||||
compile_big_fish_draft_with_all_assets(
|
||||
&state,
|
||||
session_id,
|
||||
owner_user_id,
|
||||
now,
|
||||
)
|
||||
.await
|
||||
compile_big_fish_draft_with_all_assets(&state, session_id, owner_user_id, now).await
|
||||
}
|
||||
"big_fish_generate_level_main_image" => {
|
||||
let asset_url = generate_big_fish_formal_asset(
|
||||
|
||||
@@ -74,14 +74,9 @@ pub async fn generate_character_visual(
|
||||
// 旧资产工坊接口没有显式 Bearer 头,Rust 兼容层先使用工具用户归属,避免破坏现有前端调用。
|
||||
let owner_user_id = "asset-tool".to_string();
|
||||
let task_id = generate_ai_task_id(current_utc_micros());
|
||||
let prompt = build_character_visual_prompt(
|
||||
payload.prompt_text.as_str(),
|
||||
payload.character_brief_text.as_deref(),
|
||||
);
|
||||
let fallback_prompt = build_fallback_moderation_safe_character_visual_prompt(
|
||||
payload.prompt_text.as_str(),
|
||||
payload.character_brief_text.as_deref(),
|
||||
);
|
||||
let prompt = build_character_visual_prompt(payload.prompt_text.as_str());
|
||||
let fallback_prompt =
|
||||
build_fallback_moderation_safe_character_visual_prompt(payload.prompt_text.as_str());
|
||||
let character_id = normalize_required_text(payload.character_id.as_str(), "character");
|
||||
let model = normalize_required_text(payload.image_model.as_str(), CHARACTER_VISUAL_MODEL);
|
||||
let size = normalize_required_text(payload.size.as_str(), "1024*1024");
|
||||
@@ -296,27 +291,20 @@ pub(crate) async fn generate_character_primary_visual_for_profile(
|
||||
owner_user_id: &str,
|
||||
character_id: &str,
|
||||
prompt_text: &str,
|
||||
character_brief_text: Option<&str>,
|
||||
) -> Result<GeneratedCharacterPrimaryVisual, AppError> {
|
||||
let payload = CharacterVisualGenerateRequest {
|
||||
character_id: character_id.to_string(),
|
||||
source_mode: shared_contracts::assets::CharacterVisualSourceMode::TextToImage,
|
||||
prompt_text: prompt_text.to_string(),
|
||||
character_brief_text: character_brief_text.map(ToOwned::to_owned),
|
||||
reference_image_data_urls: Vec::new(),
|
||||
candidate_count: 1,
|
||||
image_model: CHARACTER_VISUAL_MODEL.to_string(),
|
||||
size: "1024*1024".to_string(),
|
||||
};
|
||||
let task_id = generate_ai_task_id(current_utc_micros());
|
||||
let prompt = build_character_visual_prompt(
|
||||
payload.prompt_text.as_str(),
|
||||
payload.character_brief_text.as_deref(),
|
||||
);
|
||||
let fallback_prompt = build_fallback_moderation_safe_character_visual_prompt(
|
||||
payload.prompt_text.as_str(),
|
||||
payload.character_brief_text.as_deref(),
|
||||
);
|
||||
let prompt = build_character_visual_prompt(payload.prompt_text.as_str());
|
||||
let fallback_prompt =
|
||||
build_fallback_moderation_safe_character_visual_prompt(payload.prompt_text.as_str());
|
||||
let character_id = normalize_required_text(payload.character_id.as_str(), "character");
|
||||
let model = normalize_required_text(payload.image_model.as_str(), CHARACTER_VISUAL_MODEL);
|
||||
let size = normalize_required_text(payload.size.as_str(), "1024*1024");
|
||||
@@ -2068,7 +2056,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_character_visual_prompt_keeps_generation_constraints() {
|
||||
let prompt = build_character_visual_prompt("潮雾港向导", Some("旧港守望者"));
|
||||
let prompt = build_character_visual_prompt("潮雾港向导");
|
||||
|
||||
assert!(prompt.contains("潮雾港向导"));
|
||||
assert!(prompt.contains("右向斜侧身"));
|
||||
@@ -2077,10 +2065,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fallback_character_visual_prompt_removes_risky_specific_names() {
|
||||
let prompt = build_fallback_moderation_safe_character_visual_prompt(
|
||||
"艾瑞克,银发剑士,红色长披风",
|
||||
Some("某知名设定参考"),
|
||||
);
|
||||
let prompt =
|
||||
build_fallback_moderation_safe_character_visual_prompt("艾瑞克,银发剑士,红色长披风");
|
||||
|
||||
assert!(prompt.contains("原创"));
|
||||
assert!(prompt.contains("不参考任何现有"));
|
||||
|
||||
@@ -1613,7 +1613,6 @@ async fn generate_draft_foundation_role_visuals(
|
||||
task_owner_user_id.as_str(),
|
||||
role_ref.role_id.as_str(),
|
||||
role_ref.prompt.as_str(),
|
||||
Some(role_ref.name.as_str()),
|
||||
)
|
||||
.await
|
||||
};
|
||||
@@ -2429,8 +2428,8 @@ fn log_custom_world_publish_gate_diagnostics(
|
||||
has_draft_profile = session.draft_profile.as_object().map(|value| !value.is_empty()).unwrap_or(false),
|
||||
has_result_preview = session.result_preview.is_some(),
|
||||
preview_source = session.result_preview.as_ref().and_then(|value| value.get("source")).and_then(serde_json::Value::as_str).unwrap_or(""),
|
||||
has_world_hook = has_custom_world_publish_text(profile, &["worldHook", "creatorIntent.worldHook", "anchorContent.worldPromise.hook", "settingText"]),
|
||||
has_player_premise = has_custom_world_publish_text(profile, &["playerPremise", "creatorIntent.playerPremise", "anchorContent.playerEntryPoint.openingIdentity", "anchorContent.playerEntryPoint.openingProblem", "anchorContent.playerEntryPoint.entryMotivation"]),
|
||||
has_world_hook = has_custom_world_publish_text(profile, &["worldHook", "creatorIntent.worldHook", "anchorContent.worldPromise", "anchorContent.worldPromise.hook", "settingText"]),
|
||||
has_player_premise = has_custom_world_publish_text(profile, &["playerPremise", "creatorIntent.playerPremise", "anchorContent.playerEntryPoint", "anchorContent.playerEntryPoint.openingIdentity", "anchorContent.playerEntryPoint.openingProblem", "anchorContent.playerEntryPoint.entryMotivation"]),
|
||||
has_core_conflicts = has_custom_world_non_empty_text_array(profile, "coreConflicts"),
|
||||
has_main_chapter = has_custom_world_array(profile, "chapters") || has_custom_world_array(profile, "sceneChapterBlueprints") || has_custom_world_array(profile, "sceneChapters"),
|
||||
has_scene_act = has_custom_world_scene_act(profile),
|
||||
|
||||
@@ -99,113 +99,25 @@ pub(crate) struct PromptDynamicStateInference {
|
||||
judgement_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WorldPromiseValue {
|
||||
#[serde(default)]
|
||||
hook: String,
|
||||
#[serde(default)]
|
||||
differentiator: String,
|
||||
#[serde(default)]
|
||||
desired_experience: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PlayerFantasyValue {
|
||||
#[serde(default)]
|
||||
player_role: String,
|
||||
#[serde(default)]
|
||||
core_pursuit: String,
|
||||
#[serde(default)]
|
||||
fear_of_loss: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ThemeBoundaryValue {
|
||||
#[serde(default)]
|
||||
tone_keywords: Vec<String>,
|
||||
#[serde(default)]
|
||||
aesthetic_directives: Vec<String>,
|
||||
#[serde(default)]
|
||||
forbidden_directives: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PlayerEntryPointValue {
|
||||
#[serde(default)]
|
||||
opening_identity: String,
|
||||
#[serde(default)]
|
||||
opening_problem: String,
|
||||
#[serde(default)]
|
||||
entry_motivation: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CoreConflictValue {
|
||||
#[serde(default)]
|
||||
surface_conflicts: Vec<String>,
|
||||
#[serde(default)]
|
||||
hidden_crisis: String,
|
||||
#[serde(default)]
|
||||
first_touched_conflict: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct KeyRelationshipValue {
|
||||
#[serde(default)]
|
||||
pairs: String,
|
||||
#[serde(default)]
|
||||
relationship_type: String,
|
||||
#[serde(default)]
|
||||
secret_or_cost: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct HiddenLineValue {
|
||||
#[serde(default)]
|
||||
hidden_truths: Vec<String>,
|
||||
#[serde(default)]
|
||||
misdirection_hints: Vec<String>,
|
||||
#[serde(default)]
|
||||
reveal_pacing: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct IconicElementValue {
|
||||
#[serde(default)]
|
||||
iconic_motifs: Vec<String>,
|
||||
#[serde(default)]
|
||||
institutions_or_artifacts: Vec<String>,
|
||||
#[serde(default)]
|
||||
hard_rules: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct EightAnchorContent {
|
||||
#[serde(default)]
|
||||
world_promise: Option<WorldPromiseValue>,
|
||||
world_promise: Option<String>,
|
||||
#[serde(default)]
|
||||
player_fantasy: Option<PlayerFantasyValue>,
|
||||
player_fantasy: Option<String>,
|
||||
#[serde(default)]
|
||||
theme_boundary: Option<ThemeBoundaryValue>,
|
||||
theme_boundary: Option<String>,
|
||||
#[serde(default)]
|
||||
player_entry_point: Option<PlayerEntryPointValue>,
|
||||
player_entry_point: Option<String>,
|
||||
#[serde(default)]
|
||||
core_conflict: Option<CoreConflictValue>,
|
||||
core_conflict: Option<String>,
|
||||
#[serde(default)]
|
||||
key_relationships: Vec<KeyRelationshipValue>,
|
||||
key_relationships: Option<String>,
|
||||
#[serde(default)]
|
||||
hidden_lines: Option<HiddenLineValue>,
|
||||
hidden_lines: Option<String>,
|
||||
#[serde(default)]
|
||||
iconic_elements: Option<IconicElementValue>,
|
||||
iconic_elements: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
@@ -814,137 +726,127 @@ fn build_chat_history(messages: &[CustomWorldAgentMessageRecord]) -> Vec<JsonVal
|
||||
}
|
||||
|
||||
fn normalize_eight_anchor_content(value: &JsonValue) -> EightAnchorContent {
|
||||
serde_json::from_value::<EightAnchorContent>(value.clone()).unwrap_or_default()
|
||||
// Agent session 的新结构要求每个锚点只保存一段文本;这里兼容旧对象/数组存档,
|
||||
// 读取时压缩成单字段,写回时仍由 EightAnchorContent 序列化为新结构。
|
||||
EightAnchorContent {
|
||||
world_promise: normalize_anchor_text(value.get("worldPromise")),
|
||||
player_fantasy: normalize_anchor_text(value.get("playerFantasy")),
|
||||
theme_boundary: normalize_anchor_text(value.get("themeBoundary")),
|
||||
player_entry_point: normalize_anchor_text(value.get("playerEntryPoint")),
|
||||
core_conflict: normalize_anchor_text(value.get("coreConflict")),
|
||||
key_relationships: normalize_anchor_text(value.get("keyRelationships")),
|
||||
hidden_lines: normalize_anchor_text(value.get("hiddenLines")),
|
||||
iconic_elements: normalize_anchor_text(value.get("iconicElements")),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_anchor_text(value: Option<&JsonValue>) -> Option<String> {
|
||||
let normalized = compact_json_anchor_text(value?)?;
|
||||
Some(clamp_text(normalized.as_str(), 180))
|
||||
}
|
||||
|
||||
fn compact_json_anchor_text(value: &JsonValue) -> Option<String> {
|
||||
match value {
|
||||
JsonValue::Null => None,
|
||||
JsonValue::String(text) => {
|
||||
let normalized = text.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
(!normalized.trim().is_empty()).then_some(normalized.trim().to_string())
|
||||
}
|
||||
JsonValue::Array(items) => {
|
||||
let values = items
|
||||
.iter()
|
||||
.filter_map(compact_json_anchor_text)
|
||||
.collect::<Vec<_>>();
|
||||
let compacted = dedupe_string_list(values, 8).join(";");
|
||||
(!compacted.trim().is_empty()).then_some(compacted)
|
||||
}
|
||||
JsonValue::Object(object) => {
|
||||
let values = object
|
||||
.values()
|
||||
.filter_map(compact_json_anchor_text)
|
||||
.collect::<Vec<_>>();
|
||||
let compacted = dedupe_string_list(values, 8).join(";");
|
||||
(!compacted.trim().is_empty()).then_some(compacted)
|
||||
}
|
||||
JsonValue::Bool(value) => Some(value.to_string()),
|
||||
JsonValue::Number(value) => Some(value.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_anchor_phrases(value: Option<&str>) -> Vec<String> {
|
||||
value
|
||||
.unwrap_or_default()
|
||||
.split([';', ';', '、', ',', ',', '\n'])
|
||||
.map(str::trim)
|
||||
.filter(|item| !item.is_empty())
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_creator_intent_from_eight_anchor_content(
|
||||
anchor_content: &EightAnchorContent,
|
||||
) -> CreatorIntentRecord {
|
||||
let key_characters = anchor_content
|
||||
let key_relationship_text = anchor_content
|
||||
.key_relationships
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, entry)| {
|
||||
let (lead_name, relation_to_player) = split_relationship_pair(entry.pairs.as_str());
|
||||
CreatorCharacterSeedRecord {
|
||||
id: format!("creator-character-{}", index + 1),
|
||||
name: if lead_name.is_empty() {
|
||||
format!("关键人物{}", index + 1)
|
||||
} else {
|
||||
lead_name
|
||||
},
|
||||
role: entry.relationship_type.clone(),
|
||||
public_mask: String::new(),
|
||||
hidden_hook: entry.secret_or_cost.clone(),
|
||||
relation_to_player,
|
||||
notes: String::new(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let core_conflicts = anchor_content
|
||||
.core_conflict
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
value
|
||||
.surface_conflicts
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(
|
||||
(!value.hidden_crisis.trim().is_empty()).then_some(value.hidden_crisis.clone()),
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let key_characters = if key_relationship_text.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let (lead_name, relation_to_player) =
|
||||
split_relationship_pair(key_relationship_text.as_str());
|
||||
vec![CreatorCharacterSeedRecord {
|
||||
id: "creator-character-1".to_string(),
|
||||
name: if lead_name.is_empty() {
|
||||
"关键人物1".to_string()
|
||||
} else {
|
||||
lead_name
|
||||
},
|
||||
role: key_relationship_text.clone(),
|
||||
public_mask: String::new(),
|
||||
hidden_hook: key_relationship_text.clone(),
|
||||
relation_to_player,
|
||||
notes: String::new(),
|
||||
}]
|
||||
};
|
||||
|
||||
CreatorIntentRecord {
|
||||
source_mode: "freeform".to_string(),
|
||||
raw_setting_text: compact_lines([
|
||||
anchor_content
|
||||
.world_promise
|
||||
.as_ref()
|
||||
.map(|value| value.differentiator.as_str()),
|
||||
anchor_content
|
||||
.player_fantasy
|
||||
.as_ref()
|
||||
.map(|value| value.core_pursuit.as_str()),
|
||||
anchor_content
|
||||
.hidden_lines
|
||||
.as_ref()
|
||||
.and_then(|value| value.hidden_truths.first().map(String::as_str)),
|
||||
anchor_content.world_promise.as_deref(),
|
||||
anchor_content.player_fantasy.as_deref(),
|
||||
anchor_content.hidden_lines.as_deref(),
|
||||
]),
|
||||
world_hook: compact_lines([
|
||||
anchor_content
|
||||
.world_promise
|
||||
.as_ref()
|
||||
.map(|value| value.hook.as_str()),
|
||||
anchor_content
|
||||
.world_promise
|
||||
.as_ref()
|
||||
.map(|value| value.differentiator.as_str()),
|
||||
]),
|
||||
theme_keywords: anchor_content
|
||||
.theme_boundary
|
||||
.as_ref()
|
||||
.map(|value| value.tone_keywords.clone())
|
||||
.unwrap_or_default(),
|
||||
tone_directives: anchor_content
|
||||
.theme_boundary
|
||||
.as_ref()
|
||||
.map(|value| value.aesthetic_directives.clone())
|
||||
.unwrap_or_default(),
|
||||
world_hook: anchor_content.world_promise.clone().unwrap_or_default(),
|
||||
theme_keywords: split_anchor_phrases(anchor_content.theme_boundary.as_deref()),
|
||||
tone_directives: split_anchor_phrases(anchor_content.theme_boundary.as_deref()),
|
||||
player_premise: compact_lines([
|
||||
anchor_content
|
||||
.player_fantasy
|
||||
.as_ref()
|
||||
.map(|value| value.player_role.as_str()),
|
||||
anchor_content
|
||||
.player_entry_point
|
||||
.as_ref()
|
||||
.map(|value| value.opening_identity.as_str()),
|
||||
anchor_content.player_fantasy.as_deref(),
|
||||
anchor_content.player_entry_point.as_deref(),
|
||||
]),
|
||||
opening_situation: compact_lines([
|
||||
anchor_content
|
||||
.player_entry_point
|
||||
.as_ref()
|
||||
.map(|value| value.opening_problem.as_str()),
|
||||
anchor_content
|
||||
.player_entry_point
|
||||
.as_ref()
|
||||
.map(|value| value.entry_motivation.as_str()),
|
||||
]),
|
||||
core_conflicts: dedupe_string_list(core_conflicts, 6),
|
||||
opening_situation: anchor_content
|
||||
.player_entry_point
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
core_conflicts: dedupe_string_list(
|
||||
split_anchor_phrases(anchor_content.core_conflict.as_deref()),
|
||||
6,
|
||||
),
|
||||
key_characters,
|
||||
key_landmarks: Vec::new(),
|
||||
iconic_elements: dedupe_string_list(
|
||||
anchor_content
|
||||
.iconic_elements
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
value
|
||||
.iconic_motifs
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(value.institutions_or_artifacts.iter().cloned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
split_anchor_phrases(anchor_content.iconic_elements.as_deref()),
|
||||
8,
|
||||
),
|
||||
forbidden_directives: dedupe_string_list(
|
||||
anchor_content
|
||||
.theme_boundary
|
||||
.as_ref()
|
||||
.map(|value| value.forbidden_directives.clone())
|
||||
.unwrap_or_default()
|
||||
split_anchor_phrases(anchor_content.theme_boundary.as_deref())
|
||||
.into_iter()
|
||||
.chain(
|
||||
anchor_content
|
||||
.iconic_elements
|
||||
.as_ref()
|
||||
.map(|value| value.hard_rules.clone())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.chain(split_anchor_phrases(
|
||||
anchor_content.iconic_elements.as_deref(),
|
||||
))
|
||||
.filter(|value| contains_any(value, &["避免", "不要", "禁止", "不能", "硬规则"]))
|
||||
.collect::<Vec<_>>(),
|
||||
8,
|
||||
),
|
||||
@@ -1370,36 +1272,12 @@ fn detect_drift_risk(
|
||||
let filled_count = [
|
||||
anchor_content.world_promise.is_some(),
|
||||
anchor_content.player_fantasy.is_some(),
|
||||
anchor_content
|
||||
.theme_boundary
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
!value.tone_keywords.is_empty()
|
||||
|| !value.aesthetic_directives.is_empty()
|
||||
|| !value.forbidden_directives.is_empty()
|
||||
})
|
||||
.unwrap_or(false),
|
||||
anchor_content.theme_boundary.is_some(),
|
||||
anchor_content.player_entry_point.is_some(),
|
||||
anchor_content.core_conflict.is_some(),
|
||||
!anchor_content.key_relationships.is_empty(),
|
||||
anchor_content
|
||||
.hidden_lines
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
!value.hidden_truths.is_empty()
|
||||
|| !value.misdirection_hints.is_empty()
|
||||
|| !value.reveal_pacing.trim().is_empty()
|
||||
})
|
||||
.unwrap_or(false),
|
||||
anchor_content
|
||||
.iconic_elements
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
!value.iconic_motifs.is_empty()
|
||||
|| !value.institutions_or_artifacts.is_empty()
|
||||
|| !value.hard_rules.is_empty()
|
||||
})
|
||||
.unwrap_or(false),
|
||||
anchor_content.key_relationships.is_some(),
|
||||
anchor_content.hidden_lines.is_some(),
|
||||
anchor_content.iconic_elements.is_some(),
|
||||
]
|
||||
.iter()
|
||||
.filter(|value| **value)
|
||||
|
||||
@@ -2548,26 +2548,24 @@ mod tests {
|
||||
name: Some("礁石神殿".to_string()),
|
||||
description: Some("古老礁石上的半沉神殿。".to_string()),
|
||||
};
|
||||
let manual_prompt = build_custom_world_scene_image_prompt(
|
||||
SceneImagePromptParams {
|
||||
profile: SceneImagePromptProfile {
|
||||
name: profile_input.name.as_deref().unwrap_or_default(),
|
||||
subtitle: profile_input.subtitle.as_deref().unwrap_or_default(),
|
||||
tone: profile_input.tone.as_deref().unwrap_or_default(),
|
||||
player_goal: profile_input.player_goal.as_deref().unwrap_or_default(),
|
||||
summary: profile_input.summary.as_deref().unwrap_or_default(),
|
||||
setting_text: profile_input.setting_text.as_deref().unwrap_or_default(),
|
||||
},
|
||||
landmark: SceneImagePromptLandmark {
|
||||
name: landmark.name.as_deref().unwrap_or_default(),
|
||||
description: landmark.description.as_deref().unwrap_or_default(),
|
||||
},
|
||||
user_prompt,
|
||||
has_reference_image: false,
|
||||
fallback_landmark_name: Some("礁石神殿"),
|
||||
fallback_world_name: "雾海群岛",
|
||||
let manual_prompt = build_custom_world_scene_image_prompt(SceneImagePromptParams {
|
||||
profile: SceneImagePromptProfile {
|
||||
name: profile_input.name.as_deref().unwrap_or_default(),
|
||||
subtitle: profile_input.subtitle.as_deref().unwrap_or_default(),
|
||||
tone: profile_input.tone.as_deref().unwrap_or_default(),
|
||||
player_goal: profile_input.player_goal.as_deref().unwrap_or_default(),
|
||||
summary: profile_input.summary.as_deref().unwrap_or_default(),
|
||||
setting_text: profile_input.setting_text.as_deref().unwrap_or_default(),
|
||||
},
|
||||
);
|
||||
landmark: SceneImagePromptLandmark {
|
||||
name: landmark.name.as_deref().unwrap_or_default(),
|
||||
description: landmark.description.as_deref().unwrap_or_default(),
|
||||
},
|
||||
user_prompt,
|
||||
has_reference_image: false,
|
||||
fallback_landmark_name: Some("礁石神殿"),
|
||||
fallback_world_name: "雾海群岛",
|
||||
});
|
||||
|
||||
let normalized = normalize_scene_image_request(CustomWorldSceneImageRequest {
|
||||
profile_id: Some("profile_001".to_string()),
|
||||
|
||||
@@ -933,7 +933,7 @@ fn build_scene_act_blueprint_from_landmark(
|
||||
.map(String::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("");
|
||||
.map(ToOwned::to_owned);
|
||||
let opposite_npc_id = scene_npc_names.first().cloned().unwrap_or_default();
|
||||
let event_description = act_events
|
||||
.get(act_index)
|
||||
@@ -944,13 +944,20 @@ fn build_scene_act_blueprint_from_landmark(
|
||||
.unwrap_or_else(|| {
|
||||
build_default_act_event_description(scene_summary, opposite_npc_id.as_str(), act_index)
|
||||
});
|
||||
// 缺失时保留空值,让后续生图前校验暴露底稿质量问题。
|
||||
let background_prompt = prompt.unwrap_or_else(|| {
|
||||
build_default_act_background_prompt(
|
||||
scene_summary,
|
||||
opposite_npc_id.as_str(),
|
||||
event_description.as_str(),
|
||||
act_index,
|
||||
)
|
||||
});
|
||||
json!({
|
||||
"id": format!("{}-act-{}", scene_id, act_index + 1),
|
||||
"sceneId": scene_id,
|
||||
"title": act_title,
|
||||
"summary": scene_summary,
|
||||
"backgroundPromptText": prompt,
|
||||
"backgroundPromptText": background_prompt,
|
||||
"encounterNpcIds": scene_npc_names,
|
||||
"primaryNpcId": opposite_npc_id,
|
||||
"oppositeNpcId": opposite_npc_id,
|
||||
@@ -982,11 +989,46 @@ fn build_default_act_event_description(
|
||||
} else {
|
||||
scene_summary.trim()
|
||||
};
|
||||
match act_index {
|
||||
0 => format!(
|
||||
"第1幕中,{}先露出与{}有关的异常线索,玩家必须确认局势入口。",
|
||||
role_text, scene_text
|
||||
),
|
||||
1 => format!(
|
||||
"第2幕中,{}的立场或阻碍让{}升级,玩家必须在压力下作出判断。",
|
||||
role_text, scene_text
|
||||
),
|
||||
_ => format!(
|
||||
"第3幕中,{}把{}推向高潮,玩家必须面对关键抉择或直接后果。",
|
||||
role_text, scene_text
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_default_act_background_prompt(
|
||||
scene_summary: &str,
|
||||
opposite_npc_id: &str,
|
||||
event_description: &str,
|
||||
act_index: usize,
|
||||
) -> String {
|
||||
let role_text = if opposite_npc_id.trim().is_empty() {
|
||||
"当前场景关键角色"
|
||||
} else {
|
||||
opposite_npc_id.trim()
|
||||
};
|
||||
let scene_text = if scene_summary.trim().is_empty() {
|
||||
"场景内的主线压力"
|
||||
} else {
|
||||
scene_summary.trim()
|
||||
};
|
||||
let phase_text = match act_index {
|
||||
0 => "铺垫阶段",
|
||||
1 => "冲突升级阶段",
|
||||
_ => "高潮阶段",
|
||||
};
|
||||
// 中文注释:幕背景默认值直接吃同幕事件和角色,避免前端再拼规则说明句。
|
||||
format!(
|
||||
"第{}幕中,玩家与{}正面接触,围绕{}处理一件会改变局势走向的事件。",
|
||||
act_index + 1,
|
||||
role_text,
|
||||
scene_text,
|
||||
"{scene_text}的{phase_text}画面,{role_text}与玩家隔着可站立空间形成对峙,环境里保留“{event_description}”的冲突痕迹与清晰氛围。"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1080,11 +1122,16 @@ fn normalize_framework_shape(framework: &mut JsonValue, setting_text: &str) {
|
||||
JsonValue::Array(
|
||||
(0..3)
|
||||
.map(|index| {
|
||||
JsonValue::String(format!(
|
||||
"{}第{}幕,{},画面保留玩家站位、近景可交互物件与远景世界压力。",
|
||||
camp_name,
|
||||
index + 1,
|
||||
camp_description,
|
||||
let event_description = build_default_act_event_description(
|
||||
camp_description.as_str(),
|
||||
"开局关键角色",
|
||||
index,
|
||||
);
|
||||
JsonValue::String(build_default_act_background_prompt(
|
||||
camp_description.as_str(),
|
||||
"开局关键角色",
|
||||
event_description.as_str(),
|
||||
index,
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
@@ -1382,17 +1429,12 @@ fn normalize_scene_act_blueprint(act: JsonValue, index: usize) -> JsonValue {
|
||||
.unwrap_or_else(|| "当前幕推动场景内的主线压力。".to_string());
|
||||
object.insert("title".to_string(), JsonValue::String(title.clone()));
|
||||
object.insert("summary".to_string(), JsonValue::String(summary.clone()));
|
||||
let background_prompt = object
|
||||
let raw_background_prompt = object
|
||||
.get("backgroundPromptText")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_default();
|
||||
object.insert(
|
||||
"backgroundPromptText".to_string(),
|
||||
JsonValue::String(background_prompt),
|
||||
);
|
||||
.map(ToOwned::to_owned);
|
||||
let encounter_npc_ids = object
|
||||
.get("encounterNpcIds")
|
||||
.and_then(JsonValue::as_array)
|
||||
@@ -1434,6 +1476,18 @@ fn normalize_scene_act_blueprint(act: JsonValue, index: usize) -> JsonValue {
|
||||
.unwrap_or_else(|| {
|
||||
build_default_act_event_description(summary.as_str(), opposite_npc_id.as_str(), index)
|
||||
});
|
||||
let background_prompt = raw_background_prompt.unwrap_or_else(|| {
|
||||
build_default_act_background_prompt(
|
||||
summary.as_str(),
|
||||
opposite_npc_id.as_str(),
|
||||
event_description.as_str(),
|
||||
index,
|
||||
)
|
||||
});
|
||||
object.insert(
|
||||
"backgroundPromptText".to_string(),
|
||||
JsonValue::String(background_prompt),
|
||||
);
|
||||
object.insert(
|
||||
"encounterNpcIds".to_string(),
|
||||
JsonValue::Array(encounter_npc_ids),
|
||||
@@ -1468,15 +1522,25 @@ fn build_fallback_scene_act() -> JsonValue {
|
||||
}
|
||||
|
||||
fn build_fallback_scene_act_with_index(index: usize) -> JsonValue {
|
||||
let event_description = build_default_act_event_description(
|
||||
"玩家被推入第一波局势,必须先确认站位、威胁和下一步追查方向。",
|
||||
"",
|
||||
index,
|
||||
);
|
||||
json!({
|
||||
"id": format!("scene-act-{}", index + 1),
|
||||
"title": if index == 0 { "开场场景幕".to_string() } else { format!("第{}幕", index + 1) },
|
||||
"summary": "玩家被推入第一波局势,必须先确认站位、威胁和下一步追查方向。",
|
||||
"backgroundPromptText": "",
|
||||
"backgroundPromptText": build_default_act_background_prompt(
|
||||
"玩家被推入第一波局势,必须先确认站位、威胁和下一步追查方向。",
|
||||
"",
|
||||
event_description.as_str(),
|
||||
index,
|
||||
),
|
||||
"encounterNpcIds": [],
|
||||
"primaryNpcId": "",
|
||||
"oppositeNpcId": "",
|
||||
"eventDescription": build_default_act_event_description("玩家被推入第一波局势,必须先确认站位、威胁和下一步追查方向。", "", index),
|
||||
"eventDescription": event_description,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1489,12 +1553,21 @@ fn derive_world_name(
|
||||
session
|
||||
.anchor_content
|
||||
.get("worldPromise")
|
||||
.and_then(JsonValue::as_object)
|
||||
.and_then(|entry| entry.get("hook"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.or_else(|| {
|
||||
session
|
||||
.anchor_content
|
||||
.get("worldPromise")
|
||||
.and_then(JsonValue::as_object)
|
||||
.and_then(|entry| entry.get("hook"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| "未命名世界草稿".to_string())
|
||||
}
|
||||
@@ -1508,12 +1581,21 @@ fn derive_world_hook(
|
||||
session
|
||||
.anchor_content
|
||||
.get("worldPromise")
|
||||
.and_then(JsonValue::as_object)
|
||||
.and_then(|entry| entry.get("hook"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.or_else(|| {
|
||||
session
|
||||
.anchor_content
|
||||
.get("worldPromise")
|
||||
.and_then(JsonValue::as_object)
|
||||
.and_then(|entry| entry.get("hook"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
"这个世界正被一条持续扩大的主线危机推向失衡,而玩家会被卷入其中心。".to_string()
|
||||
@@ -1529,28 +1611,37 @@ fn derive_player_premise(
|
||||
session
|
||||
.anchor_content
|
||||
.get("playerEntryPoint")
|
||||
.and_then(JsonValue::as_object)
|
||||
.map(|entry| {
|
||||
let identity = entry
|
||||
.get("openingIdentity")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
let problem = entry
|
||||
.get("openingProblem")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
let motivation = entry
|
||||
.get("entryMotivation")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
[identity, problem, motivation]
|
||||
.into_iter()
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(";")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.or_else(|| {
|
||||
session
|
||||
.anchor_content
|
||||
.get("playerEntryPoint")
|
||||
.and_then(JsonValue::as_object)
|
||||
.map(|entry| {
|
||||
let identity = entry
|
||||
.get("openingIdentity")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
let problem = entry
|
||||
.get("openingProblem")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
let motivation = entry
|
||||
.get("entryMotivation")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
[identity, problem, motivation]
|
||||
.into_iter()
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(";")
|
||||
})
|
||||
})
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
@@ -1740,7 +1831,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_scene_act_keeps_missing_background_prompt_empty() {
|
||||
fn normalize_scene_act_fills_missing_background_prompt_from_event() {
|
||||
let act = normalize_scene_act_blueprint(
|
||||
json!({
|
||||
"title": "第1幕",
|
||||
@@ -1749,7 +1840,15 @@ mod tests {
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(act.get("backgroundPromptText"), Some(&json!("")));
|
||||
assert!(
|
||||
act.get("backgroundPromptText")
|
||||
.and_then(JsonValue::as_str)
|
||||
.is_some_and(|value| {
|
||||
value.contains("铺垫阶段")
|
||||
&& value.contains("玩家进入雾港码头")
|
||||
&& value.contains("冲突痕迹")
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
act.get("eventDescription")
|
||||
.and_then(JsonValue::as_str)
|
||||
|
||||
@@ -182,50 +182,28 @@ pub(crate) const OUTPUT_CONTRACT_REMINDER: &str = r#"请严格按以下 JSON 结
|
||||
"replyText": "",
|
||||
"progressPercent": 0,
|
||||
"nextAnchorContent": {
|
||||
"worldPromise": {
|
||||
"hook": "",
|
||||
"differentiator": "",
|
||||
"desiredExperience": ""
|
||||
},
|
||||
"playerFantasy": {
|
||||
"playerRole": "",
|
||||
"corePursuit": "",
|
||||
"fearOfLoss": ""
|
||||
},
|
||||
"themeBoundary": {
|
||||
"toneKeywords": [],
|
||||
"aestheticDirectives": [],
|
||||
"forbiddenDirectives": []
|
||||
},
|
||||
"playerEntryPoint": {
|
||||
"openingIdentity": "",
|
||||
"openingProblem": "",
|
||||
"entryMotivation": ""
|
||||
},
|
||||
"coreConflict": {
|
||||
"surfaceConflicts": [],
|
||||
"hiddenCrisis": "",
|
||||
"firstTouchedConflict": ""
|
||||
},
|
||||
"keyRelationships": [
|
||||
{
|
||||
"pairs": "",
|
||||
"relationshipType": "",
|
||||
"secretOrCost": ""
|
||||
}
|
||||
],
|
||||
"hiddenLines": {
|
||||
"hiddenTruths": [],
|
||||
"misdirectionHints": [],
|
||||
"revealPacing": ""
|
||||
},
|
||||
"iconicElements": {
|
||||
"iconicMotifs": [],
|
||||
"institutionsOrArtifacts": [],
|
||||
"hardRules": []
|
||||
}
|
||||
"worldPromise": "",
|
||||
"playerFantasy": "",
|
||||
"themeBoundary": "",
|
||||
"playerEntryPoint": "",
|
||||
"coreConflict": "",
|
||||
"keyRelationships": "",
|
||||
"hiddenLines": "",
|
||||
"iconicElements": ""
|
||||
}
|
||||
}"#;
|
||||
}
|
||||
|
||||
nextAnchorContent 的 8 个锚点每个都只能是一个字符串或 null,不允许输出对象或数组。
|
||||
请把每个锚点写成一段凝练中文:
|
||||
- worldPromise 关注世界钩子、差异点、玩家体验。
|
||||
- playerFantasy 关注玩家身份、核心追求、失去风险。
|
||||
- themeBoundary 关注主题气质、美术方向、禁用方向。
|
||||
- playerEntryPoint 关注开局身份、开局问题、行动动机。
|
||||
- coreConflict 关注表层冲突、隐藏危机、首次触发点。
|
||||
- keyRelationships 关注关键人物关系、关系类型、代价或秘密。
|
||||
- hiddenLines 关注隐藏真相、误导线索、揭示节奏。
|
||||
- iconicElements 关注标志意象、组织/物件、硬规则。
|
||||
"#;
|
||||
|
||||
pub(crate) fn render_dynamic_state_context(dynamic_state: &PromptDynamicState) -> String {
|
||||
format!(
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
/// 自定义世界角色主图提示词脚本。
|
||||
pub(crate) fn build_character_visual_prompt(
|
||||
prompt_text: &str,
|
||||
character_brief_text: Option<&str>,
|
||||
) -> String {
|
||||
let character_brief = [character_brief_text.unwrap_or_default(), prompt_text]
|
||||
.into_iter()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
build_master_prompt(character_brief.as_str())
|
||||
/// 自定义世界角色主图提示词脚本。
|
||||
pub(crate) fn build_character_visual_prompt(prompt_text: &str) -> String {
|
||||
build_master_prompt(prompt_text.trim())
|
||||
}
|
||||
|
||||
/// 角色主图被供应商内容审核拦截时使用的安全兜底提示词。
|
||||
///
|
||||
/// 这里刻意不继续携带角色姓名、作品名和长设定文本,避免把可疑专名原样送回上游导致连续失败。
|
||||
pub(crate) fn build_fallback_moderation_safe_character_visual_prompt(
|
||||
prompt_text: &str,
|
||||
character_brief_text: Option<&str>,
|
||||
) -> String {
|
||||
let source = [character_brief_text.unwrap_or_default(), prompt_text].join(" ");
|
||||
let archetype = resolve_original_role_archetype(source.as_str());
|
||||
pub(crate) fn build_fallback_moderation_safe_character_visual_prompt(prompt_text: &str) -> String {
|
||||
let archetype = resolve_original_role_archetype(prompt_text);
|
||||
|
||||
build_master_prompt(
|
||||
[
|
||||
@@ -61,11 +47,11 @@ fn resolve_original_role_archetype(source: &str) -> &'static str {
|
||||
/// 角色主图统一提示词骨架,迁移自旧共享 qwenSprite 主链。
|
||||
fn build_master_prompt(character_brief: &str) -> String {
|
||||
[
|
||||
"单人,2D 横版游戏角色标准设定图,主体完整可见,底部轮廓完整,身体比例稳定,轮廓清楚,细节精致,设计感足,适合后续制作 sprite sheet 动画。".to_string(),
|
||||
"单人,2D像素角色形象,头身比必须控制在 1.5 到 2 头身,主体完整可见,底部轮廓完整,身体比例稳定,轮廓清楚,适合后续制作 sprite sheet 动画。".to_string(),
|
||||
"视角要求:角色采用横版动作素材常用的右向斜侧身站姿,身体整体朝右,但保留少量正面信息,能读到面部轮廓与胸肩结构,不是完全 90 度纯右视图,也不是正面立绘。".to_string(),
|
||||
"主体要求:画面中只保留单个角色主体,不要额外人物、动物、召唤物、载具或陪体。".to_string(),
|
||||
"画面要求:1:1 正方形画布,画面中心构图,角色主体完整置于画面中央,不要裁切主体顶部和底部,不要镜头透视,不要特写。背景固定为纯绿色绿幕,只作为抠像底色,不出现建筑、室内布景、风景、地面道具、漂浮物、烟雾叙事元素、文字或其他角色以外的场景内容。".to_string(),
|
||||
"风格要求:横版像素角色,头身比必须控制在 1 到 1.5 头身。使用深色清楚轮廓、稳定剪影、有限大色块和硬朗边缘,不要柔和厚涂插画感,发型、服装、配饰优先形成醒目可读的像素级识别点。".to_string(),
|
||||
"风格要求:横版像素角色,细节精致,设计感足。使用深色清楚轮廓、稳定剪影、有限大色块和硬朗边缘,不要柔和厚涂插画感,发型、服装、配饰优先形成醒目可读的像素级识别点。".to_string(),
|
||||
"如果角色形象设定没有明确要求非人身体结构,默认优先使用人类或类人动作角色骨架。\
|
||||
默认将角色形象设定作用在角色自身的服装剪裁、材质、纹样、饰品、发光细节上。".to_string(),
|
||||
"角色形象设定:".to_string(),
|
||||
@@ -103,8 +89,6 @@ pub(crate) fn build_character_visual_negative_prompt() -> String {
|
||||
"文字",
|
||||
"水印",
|
||||
"UI 元素",
|
||||
"软萌 Q版大头贴",
|
||||
"儿童绘本风",
|
||||
"厚涂插画感",
|
||||
"低对比柔边",
|
||||
]
|
||||
|
||||
@@ -36,8 +36,8 @@ pub(crate) fn build_custom_world_framework_prompt(setting_text: &str) -> String
|
||||
"- templateWorldType 只是系统兼容字段,不代表正文应当引用的世界名称。".to_string(),
|
||||
"- camp 必须表示玩家开局时的落脚处,更接近归舍、住处、栖居、前哨居所这类“家/归处”的概念。".to_string(),
|
||||
"- camp.sceneTaskDescription 必须描述玩家首次进入开局场景时要完成的核心任务,会作为游戏章节任务生成上下文,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- camp.actBackgroundPromptTexts 必须恰好 3 条,分别对应开局场景第 1/2/3 幕背景图画面内容描述;每条都必须可直接交给生图模型,控制在 40 到 90 个汉字内。".to_string(),
|
||||
"- camp.actEventDescriptions 必须恰好 3 条,分别描述每一幕发生的事件;事件必须和当前幕对面的角色强相关,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- camp.actEventDescriptions 必须恰好 3 条,分别描述每一幕发生的事件;第 1 幕负责铺垫,第 2 幕必须让冲突升级,第 3 幕必须形成高潮或关键抉择;事件必须和当前幕对面的角色强相关,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- camp.actBackgroundPromptTexts 必须恰好 3 条,分别对应第 1/2/3 幕背景图画面内容描述;每条必须基于同序号 actEventDescriptions 和相关角色写出画面主体、站位空间、冲突痕迹与氛围,能直接交给生图模型,控制在 40 到 90 个汉字内。".to_string(),
|
||||
"- 不要输出 playableNpcs、storyNpcs、landmarks、items,也不要输出任何角色和地图细节。".to_string(),
|
||||
"- majorFactions 保持 2 到 3 个,coreConflicts 保持 2 到 3 个。".to_string(),
|
||||
"- 世界设定必须直接源自玩家输入,不要脱离主题乱扩写。".to_string(),
|
||||
@@ -199,9 +199,9 @@ pub(crate) fn build_custom_world_landmark_seed_batch_prompt(
|
||||
"- 每个地点只保留:name、description、visualDescription、sceneTaskDescription、actBackgroundPromptTexts、actEventDescriptions。".to_string(),
|
||||
"- sceneTaskDescription 必须描述玩家首次进入该场景时要完成的核心任务,会作为游戏章节任务生成上下文,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- visualDescription 是打开场景背景图像生成面板时默认填入的场景描述,必须具体到画面主体、远近景层次、地面可站立区域和氛围识别点,控制在 32 到 80 个汉字内。".to_string(),
|
||||
"- actBackgroundPromptTexts 必须恰好 3 条,分别对应这个场景章节的第 1/2/3 幕背景图画面内容描述;每条都必须是大模型根据当前地点、主线阶段和可出场角色直接写出的画面描述,控制在 40 到 90 个汉字内。".to_string(),
|
||||
"- actEventDescriptions 必须恰好 3 条,分别描述每一幕发生的事件;第 1 幕负责铺垫,第 2 幕必须让冲突升级,第 3 幕必须形成高潮或关键抉择;事件必须和当前幕对面的角色强相关,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- actBackgroundPromptTexts 必须恰好 3 条,分别对应这个场景章节的第 1/2/3 幕背景图画面内容描述;每条都必须基于同序号 actEventDescriptions、当前地点和可出场角色直接写出画面主体、站位空间、冲突痕迹与氛围,控制在 40 到 90 个汉字内。".to_string(),
|
||||
"- actBackgroundPromptTexts 禁止使用“某某第1幕背景;玩家会在……”这类标题、摘要、规则句拼接格式;必须像可直接交给生图模型的自然画面描述。".to_string(),
|
||||
"- actEventDescriptions 必须恰好 3 条,分别描述每一幕发生的事件;事件必须和当前幕对面的角色强相关,控制在 24 到 56 个汉字内。".to_string(),
|
||||
"- description 控制在 12 到 24 个汉字内。".to_string(),
|
||||
"- 所有生成文本都必须使用中文。".to_string(),
|
||||
"- 返回前自检:必须是一个能被 JSON.parse 直接解析的单个 JSON 对象。".to_string(),
|
||||
@@ -258,6 +258,7 @@ pub(crate) fn build_custom_world_landmark_network_batch_prompt(
|
||||
"要求:".to_string(),
|
||||
"- 必须只补全本批场景,name 必须与本批场景完全一致,不得增删改名。".to_string(),
|
||||
"- sceneNpcNames 只能引用上方可用场景角色名单中的名字,每个地点 1 到 3 个。".to_string(),
|
||||
"- sceneNpcNames 的第一位会成为每幕对面主角色;三幕事件和幕背景必须围绕这个角色的行动、阻碍、试探或求助展开。".to_string(),
|
||||
"- connectedLandmarkNames 优先引用已知关键场景名称,每个地点 1 到 3 个。".to_string(),
|
||||
"- entryHook 控制在 16 到 36 个汉字内。".to_string(),
|
||||
"- 所有生成文本都必须使用中文。".to_string(),
|
||||
|
||||
@@ -47,13 +47,14 @@ use spacetime_client::{
|
||||
PuzzleAgentMessageRecord, PuzzleAgentMessageSubmitRecordInput,
|
||||
PuzzleAgentSessionCreateRecordInput, PuzzleAgentSessionRecord,
|
||||
PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord, PuzzleAnchorPackRecord,
|
||||
PuzzleCreatorIntentRecord, PuzzleGeneratedImageCandidateRecord,
|
||||
PuzzleGeneratedImagesSaveRecordInput, PuzzlePublishRecordInput, PuzzleResultDraftRecord,
|
||||
PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord,
|
||||
PuzzleBoardRecord, PuzzleCellPositionRecord, PuzzleMergedGroupRecord, PuzzlePieceStateRecord,
|
||||
PuzzleRunDragRecordInput, PuzzleRunRecord, PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput,
|
||||
PuzzleRuntimeLevelRecord, PuzzleSelectCoverImageRecordInput, PuzzleWorkProfileRecord,
|
||||
PuzzleWorkUpsertRecordInput, SpacetimeClientError,
|
||||
PuzzleBoardRecord, PuzzleCellPositionRecord, PuzzleCreatorIntentRecord,
|
||||
PuzzleGeneratedImageCandidateRecord, PuzzleGeneratedImagesSaveRecordInput,
|
||||
PuzzleMergedGroupRecord, PuzzlePieceStateRecord, PuzzlePublishRecordInput,
|
||||
PuzzleResultDraftRecord, PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord,
|
||||
PuzzleResultPreviewRecord, PuzzleRunDragRecordInput, PuzzleRunRecord,
|
||||
PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord,
|
||||
PuzzleSelectCoverImageRecordInput, PuzzleWorkProfileRecord, PuzzleWorkUpsertRecordInput,
|
||||
SpacetimeClientError,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tokio::time::sleep;
|
||||
@@ -1639,7 +1640,10 @@ async fn generate_puzzle_image_candidates(
|
||||
let mut items = Vec::with_capacity(generated.images.len());
|
||||
|
||||
for (index, image) in generated.images.into_iter().enumerate() {
|
||||
let candidate_id = format!("{session_id}-candidate-{}", candidate_start_index + index + 1);
|
||||
let candidate_id = format!(
|
||||
"{session_id}-candidate-{}",
|
||||
candidate_start_index + index + 1
|
||||
);
|
||||
let asset = persist_puzzle_generated_asset(
|
||||
state,
|
||||
owner_user_id,
|
||||
@@ -1690,10 +1694,12 @@ async fn build_local_next_puzzle_run(
|
||||
}))
|
||||
})?;
|
||||
if current_level.status != "cleared" {
|
||||
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "current level is not cleared",
|
||||
})));
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "current level is not cleared",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(gallery_item) = resolve_gallery_next_puzzle_work(state, &run).await? {
|
||||
@@ -1702,10 +1708,12 @@ async fn build_local_next_puzzle_run(
|
||||
|
||||
let source_session_id = payload.source_session_id.unwrap_or_default();
|
||||
if source_session_id.trim().is_empty() {
|
||||
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "sourceSessionId is required when gallery has no next puzzle work",
|
||||
})));
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "sourceSessionId is required when gallery has no next puzzle work",
|
||||
})),
|
||||
);
|
||||
}
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
@@ -1767,14 +1775,23 @@ async fn build_local_next_puzzle_run(
|
||||
let candidate = updated_session
|
||||
.draft
|
||||
.as_ref()
|
||||
.and_then(|draft| draft.candidates.iter().find(|candidate| !candidate.image_src.is_empty()))
|
||||
.and_then(|draft| {
|
||||
draft
|
||||
.candidates
|
||||
.iter()
|
||||
.find(|candidate| !candidate.image_src.is_empty())
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "现场生成后没有可用候选图",
|
||||
}))
|
||||
})?;
|
||||
Ok(build_next_run_from_candidate(run, &updated_session, candidate))
|
||||
Ok(build_next_run_from_candidate(
|
||||
run,
|
||||
&updated_session,
|
||||
candidate,
|
||||
))
|
||||
}
|
||||
|
||||
async fn resolve_gallery_next_puzzle_work(
|
||||
@@ -1788,7 +1805,10 @@ async fn resolve_gallery_next_puzzle_work(
|
||||
.map_err(map_puzzle_client_error)?;
|
||||
Ok(items.into_iter().find(|item| {
|
||||
item.publication_status == "published"
|
||||
&& item.cover_image_src.as_ref().is_some_and(|value| !value.is_empty())
|
||||
&& item
|
||||
.cover_image_src
|
||||
.as_ref()
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
&& !run.played_profile_ids.contains(&item.profile_id)
|
||||
}))
|
||||
}
|
||||
@@ -1836,7 +1856,9 @@ fn build_next_run_from_candidate(
|
||||
.map(|draft| format!("{} · 候选 {}", draft.level_name, level_index))
|
||||
.unwrap_or_else(|| format!("候选拼图 {level_index}")),
|
||||
"当前草稿".to_string(),
|
||||
draft.map(|draft| draft.theme_tags.clone()).unwrap_or_default(),
|
||||
draft
|
||||
.map(|draft| draft.theme_tags.clone())
|
||||
.unwrap_or_default(),
|
||||
Some(candidate.image_src.clone()),
|
||||
)
|
||||
}
|
||||
@@ -1893,13 +1915,14 @@ fn build_local_puzzle_board(grid_size: u32) -> PuzzleBoardRecord {
|
||||
}
|
||||
let pieces = (0..total)
|
||||
.map(|index| {
|
||||
let current = positions
|
||||
.get(index as usize)
|
||||
.cloned()
|
||||
.unwrap_or(PuzzleCellPositionRecord {
|
||||
row: index / grid_size,
|
||||
col: index % grid_size,
|
||||
});
|
||||
let current =
|
||||
positions
|
||||
.get(index as usize)
|
||||
.cloned()
|
||||
.unwrap_or(PuzzleCellPositionRecord {
|
||||
row: index / grid_size,
|
||||
col: index % grid_size,
|
||||
});
|
||||
PuzzlePieceStateRecord {
|
||||
piece_id: format!("piece-{index}"),
|
||||
correct_row: index / grid_size,
|
||||
|
||||
@@ -71,7 +71,7 @@ pub async fn stream_runtime_npc_chat_turn(
|
||||
|
||||
let llm_result =
|
||||
generate_llm_npc_chat_turn(&state, &request_context, &payload, &npc_name).await;
|
||||
let (mut body, npc_reply, suggestions) = match llm_result {
|
||||
let (mut body, npc_reply, suggestions, function_suggestions, force_exit) = match llm_result {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
let npc_reply = build_deterministic_npc_reply(
|
||||
@@ -79,11 +79,21 @@ pub async fn stream_runtime_npc_chat_turn(
|
||||
player_message,
|
||||
payload.npc_initiates_conversation,
|
||||
);
|
||||
let suggestions = if should_force_chat_exit(payload.chat_directive.as_ref()) {
|
||||
let force_exit = should_force_chat_exit(payload.chat_directive.as_ref())
|
||||
|| should_hostile_chat_breakoff_deterministically(
|
||||
player_message,
|
||||
payload.chat_directive.as_ref(),
|
||||
);
|
||||
let suggestions = if force_exit {
|
||||
Vec::new()
|
||||
} else {
|
||||
build_deterministic_chat_suggestions(npc_name.as_str(), player_message)
|
||||
};
|
||||
let function_suggestions = if force_exit {
|
||||
Vec::new()
|
||||
} else {
|
||||
build_fallback_function_suggestions(payload.chat_directive.as_ref())
|
||||
};
|
||||
let mut body = String::new();
|
||||
append_sse_event(
|
||||
&request_context,
|
||||
@@ -91,7 +101,13 @@ pub async fn stream_runtime_npc_chat_turn(
|
||||
"reply_delta",
|
||||
&json!({ "text": npc_reply }),
|
||||
)?;
|
||||
(body, npc_reply, suggestions)
|
||||
(
|
||||
body,
|
||||
npc_reply,
|
||||
suggestions,
|
||||
function_suggestions,
|
||||
force_exit,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -103,8 +119,9 @@ pub async fn stream_runtime_npc_chat_turn(
|
||||
"affinityDelta": affinity_delta,
|
||||
"affinityText": describe_affinity_shift(affinity_delta),
|
||||
"suggestions": suggestions,
|
||||
"functionSuggestions": function_suggestions,
|
||||
"pendingQuestOffer": null,
|
||||
"chatDirective": build_completion_directive(payload.chat_directive.as_ref()),
|
||||
"chatDirective": build_completion_directive(payload.chat_directive.as_ref(), force_exit),
|
||||
});
|
||||
|
||||
append_sse_event(&request_context, &mut body, "complete", &complete_payload)?;
|
||||
@@ -117,7 +134,7 @@ async fn generate_llm_npc_chat_turn(
|
||||
request_context: &RequestContext,
|
||||
payload: &NpcChatTurnRequest,
|
||||
npc_name: &str,
|
||||
) -> Option<(String, String, Vec<String>)> {
|
||||
) -> Option<(String, String, Vec<String>, Vec<Value>, bool)> {
|
||||
let llm_client = state.llm_client()?;
|
||||
let character = payload
|
||||
.character
|
||||
@@ -169,7 +186,7 @@ async fn generate_llm_npc_chat_turn(
|
||||
});
|
||||
|
||||
if should_force_chat_exit(payload.chat_directive.as_ref()) {
|
||||
return Some((body, npc_reply, Vec::new()));
|
||||
return Some((body, npc_reply, Vec::new(), Vec::new(), true));
|
||||
}
|
||||
|
||||
let suggestion_prompt =
|
||||
@@ -180,15 +197,37 @@ async fn generate_llm_npc_chat_turn(
|
||||
]);
|
||||
suggestion_request.max_tokens = Some(200);
|
||||
suggestion_request.enable_web_search = state.config.rpg_llm_web_search_enabled;
|
||||
let suggestions = llm_client
|
||||
let suggestion_text = llm_client
|
||||
.request_text(suggestion_request)
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| parse_line_list_content(response.content.as_str(), 3))
|
||||
.filter(|items| items.len() == 3)
|
||||
.unwrap_or_else(|| build_fallback_npc_chat_suggestions(payload.player_message.as_str()));
|
||||
.map(|response| response.content)
|
||||
.unwrap_or_default();
|
||||
let (mut suggestions, mut function_suggestions, should_end_chat) =
|
||||
parse_npc_chat_suggestion_resolution(
|
||||
suggestion_text.as_str(),
|
||||
payload.chat_directive.as_ref(),
|
||||
);
|
||||
let force_exit = should_end_chat
|
||||
|| should_hostile_chat_breakoff_deterministically(
|
||||
payload.player_message.as_str(),
|
||||
payload.chat_directive.as_ref(),
|
||||
);
|
||||
|
||||
Some((body, npc_reply, suggestions))
|
||||
if force_exit {
|
||||
suggestions.clear();
|
||||
function_suggestions.clear();
|
||||
} else if suggestions.is_empty() {
|
||||
suggestions = build_fallback_npc_chat_suggestions(payload.player_message.as_str());
|
||||
}
|
||||
|
||||
Some((
|
||||
body,
|
||||
npc_reply,
|
||||
suggestions,
|
||||
function_suggestions,
|
||||
force_exit,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_deterministic_npc_reply(
|
||||
@@ -206,12 +245,12 @@ fn build_deterministic_npc_reply(
|
||||
fn build_deterministic_chat_suggestions(npc_name: &str, player_message: &str) -> Vec<String> {
|
||||
// 建议只承载玩家可点选的行动意图,不在 UI 里额外塞说明文案。
|
||||
vec![
|
||||
format!("继续询问{npc_name}的近况"),
|
||||
"追问这里发生了什么".to_string(),
|
||||
format!("{npc_name},我想先听你说"),
|
||||
"这件事哪里不对劲".to_string(),
|
||||
if player_message.contains('帮') || player_message.contains('忙') {
|
||||
"请对方说清需要什么帮助".to_string()
|
||||
"先别绕,说清代价".to_string()
|
||||
} else {
|
||||
"换个轻松的话题".to_string()
|
||||
"你是不是还瞒着我".to_string()
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -225,33 +264,164 @@ fn build_fallback_npc_chat_suggestions(player_message: &str) -> Vec<String> {
|
||||
};
|
||||
|
||||
vec![
|
||||
"你刚才那句是什么意思".to_string(),
|
||||
"我愿意先听你说完".to_string(),
|
||||
format!("这事和{topic}有关吗"),
|
||||
"你愿意再说清楚点吗".to_string(),
|
||||
"你别再避重就轻".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn build_completion_directive(chat_directive: Option<&Value>) -> Value {
|
||||
fn build_fallback_function_suggestions(chat_directive: Option<&Value>) -> Vec<Value> {
|
||||
read_function_options(chat_directive)
|
||||
.into_iter()
|
||||
.filter(|option| {
|
||||
read_string_field(option, "functionId")
|
||||
.as_deref()
|
||||
.is_some_and(|function_id| function_id != "npc_chat")
|
||||
})
|
||||
.take(2)
|
||||
.filter_map(|option| {
|
||||
let function_id = read_string_field(option, "functionId")?;
|
||||
let action_text = read_string_field(option, "actionText")?;
|
||||
Some(json!({
|
||||
"functionId": function_id,
|
||||
"actionText": action_text,
|
||||
}))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_completion_directive(chat_directive: Option<&Value>, force_exit: bool) -> Value {
|
||||
let Some(directive) = chat_directive else {
|
||||
return Value::Null;
|
||||
};
|
||||
let closing_mode = read_string_field(directive, "closingMode")
|
||||
.filter(|value| value == "foreshadow_close")
|
||||
.unwrap_or_else(|| "free".to_string());
|
||||
let force_exit = closing_mode == "foreshadow_close"
|
||||
let force_exit = force_exit
|
||||
|| closing_mode == "foreshadow_close"
|
||||
|| directive
|
||||
.get("forceExitAfterTurn")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let termination_reason = if force_exit {
|
||||
read_string_field(directive, "terminationReason")
|
||||
.filter(|value| value == "player_exit" || value == "hostile_breakoff")
|
||||
.or_else(|| {
|
||||
if is_hostile_model_chat(chat_directive) {
|
||||
Some("hostile_breakoff".to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
json!({
|
||||
"turnLimit": directive.get("turnLimit").cloned().unwrap_or(Value::Null),
|
||||
"remainingTurns": directive.get("remainingTurns").cloned().unwrap_or(Value::Null),
|
||||
"forceExit": force_exit,
|
||||
"closingMode": closing_mode,
|
||||
"closingMode": if force_exit { "foreshadow_close" } else { closing_mode.as_str() },
|
||||
"terminationReason": termination_reason,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_npc_chat_suggestion_resolution(
|
||||
text: &str,
|
||||
chat_directive: Option<&Value>,
|
||||
) -> (Vec<String>, Vec<Value>, bool) {
|
||||
let normalized = text.trim();
|
||||
if normalized.is_empty() {
|
||||
return (
|
||||
Vec::new(),
|
||||
build_fallback_function_suggestions(chat_directive),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(value) = serde_json::from_str::<Value>(normalized) {
|
||||
let should_end_chat = value
|
||||
.get("shouldEndChat")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
&& is_hostile_model_chat(chat_directive);
|
||||
let suggestions = value
|
||||
.get("suggestions")
|
||||
.and_then(Value::as_array)
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|item| !item.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.take(3)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let function_suggestions =
|
||||
parse_function_suggestions(value.get("functionSuggestions"), chat_directive);
|
||||
|
||||
return (suggestions, function_suggestions, should_end_chat);
|
||||
}
|
||||
|
||||
(
|
||||
parse_line_list_content(normalized, 3),
|
||||
build_fallback_function_suggestions(chat_directive),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_function_suggestions(value: Option<&Value>, chat_directive: Option<&Value>) -> Vec<Value> {
|
||||
let allowed_options = read_function_options(chat_directive);
|
||||
let allowed_ids = allowed_options
|
||||
.iter()
|
||||
.filter_map(|item| read_string_field(item, "functionId"))
|
||||
.collect::<Vec<_>>();
|
||||
let mut used_ids: Vec<String> = Vec::new();
|
||||
|
||||
value
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|item| {
|
||||
let function_id = read_string_field(item, "functionId")?;
|
||||
if function_id == "npc_chat" {
|
||||
return None;
|
||||
}
|
||||
if !allowed_ids.is_empty() && !allowed_ids.contains(&function_id) {
|
||||
return None;
|
||||
}
|
||||
if used_ids.contains(&function_id) {
|
||||
return None;
|
||||
}
|
||||
let fallback_text = allowed_options
|
||||
.iter()
|
||||
.find(|option| {
|
||||
read_string_field(option, "functionId").as_deref() == Some(function_id.as_str())
|
||||
})
|
||||
.and_then(|option| read_string_field(option, "actionText"));
|
||||
let action_text = read_string_field(item, "actionText")
|
||||
.or(fallback_text)
|
||||
.filter(|text| !text.trim().is_empty())?;
|
||||
used_ids.push(function_id.clone());
|
||||
Some(json!({
|
||||
"functionId": function_id,
|
||||
"actionText": action_text,
|
||||
}))
|
||||
})
|
||||
.take(3)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_function_options(chat_directive: Option<&Value>) -> Vec<&Value> {
|
||||
chat_directive
|
||||
.and_then(|directive| directive.get("functionOptions"))
|
||||
.and_then(Value::as_array)
|
||||
.map(|items| items.iter().collect::<Vec<_>>())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn read_string_field(value: &Value, field: &str) -> Option<String> {
|
||||
value
|
||||
.get(field)
|
||||
@@ -268,18 +438,61 @@ fn read_number_field(value: &Value, field: &str) -> Option<f64> {
|
||||
.filter(|number| number.is_finite())
|
||||
}
|
||||
|
||||
fn read_bool_field(value: &Value, field: &str) -> Option<bool> {
|
||||
value.get(field).and_then(Value::as_bool)
|
||||
}
|
||||
|
||||
fn should_force_chat_exit(chat_directive: Option<&Value>) -> bool {
|
||||
let Some(directive) = chat_directive else {
|
||||
return false;
|
||||
};
|
||||
|
||||
read_string_field(directive, "closingMode").as_deref() == Some("foreshadow_close")
|
||||
|| read_string_field(directive, "terminationReason").as_deref() == Some("player_exit")
|
||||
|| directive
|
||||
.get("forceExitAfterTurn")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_hostile_model_chat(chat_directive: Option<&Value>) -> bool {
|
||||
let Some(directive) = chat_directive else {
|
||||
return false;
|
||||
};
|
||||
|
||||
read_string_field(directive, "terminationMode").as_deref() == Some("hostile_model")
|
||||
|| read_bool_field(directive, "isHostileChat").unwrap_or(false)
|
||||
}
|
||||
|
||||
fn should_hostile_chat_breakoff_deterministically(
|
||||
player_message: &str,
|
||||
chat_directive: Option<&Value>,
|
||||
) -> bool {
|
||||
if !is_hostile_model_chat(chat_directive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(directive) = chat_directive else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if read_string_field(directive, "terminationReason").as_deref() == Some("player_exit") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let hostile_break_words = [
|
||||
"动手",
|
||||
"开战",
|
||||
"拔刀",
|
||||
"杀",
|
||||
"滚",
|
||||
"闭嘴",
|
||||
"少废话",
|
||||
"别挡路",
|
||||
];
|
||||
count_keyword_matches(player_message, &hostile_break_words) > 0
|
||||
}
|
||||
|
||||
fn normalize_required_text(value: &str) -> Option<String> {
|
||||
let normalized = value.trim();
|
||||
if normalized.is_empty() {
|
||||
|
||||
@@ -6,10 +6,16 @@ pub(crate) const NPC_CHAT_TURN_REPLY_SYSTEM_PROMPT: &str = r#"你是角色扮演
|
||||
- 如果这是第一次真正接触中的首轮回复,第一句必须先用自然招呼或开场判断起手,不能写成第三人称占位旁白。
|
||||
回复长度控制在 1 到 3 句,必须紧接玩家刚说的话,自然推进气氛、情报或关系。"#;
|
||||
|
||||
pub(crate) const NPC_CHAT_TURN_SUGGESTION_SYSTEM_PROMPT: &str = r#"你要为玩家生成下一轮可直接点击的 3 条聊天续写候选。
|
||||
只输出纯文本,共 3 行,每行 1 条。
|
||||
不要加编号、项目符号、Markdown、JSON 或额外说明。
|
||||
三条候选必须明显不同,分别体现继续追问、表达态度、轻微拉近关系这三种不同方向。"#;
|
||||
pub(crate) const NPC_CHAT_TURN_SUGGESTION_SYSTEM_PROMPT: &str = r#"你要为 RPG NPC 聊天生成下一步候选,并判断敌对聊天是否已经收束。
|
||||
只输出 JSON,不要输出 Markdown 或解释。
|
||||
JSON 结构:
|
||||
{"shouldEndChat":false,"terminationReason":null,"suggestions":["温和共情台词","冷静追问台词","施压质疑台词"],"functionSuggestions":[{"functionId":"...","actionText":"玩家动作文本"}]}
|
||||
- suggestions 是玩家下一轮可直接说出口的中文短句,每条 20 字以内;三条必须按顺序导向不同氛围和好感结果。
|
||||
- suggestions 第 1 条温和共情,通常让气氛缓和、好感上升;第 2 条冷静追问或试探,通常保持中性但推进情报;第 3 条施压、质疑或立场冲突,通常让气氛变紧、好感下降或付出代价。
|
||||
- functionSuggestions 只能从用户提示提供的 functionOptions 中挑选,不要发明 functionId。
|
||||
- functionSuggestions 的 actionText 必须像玩家可点击动作,不暴露 functionId,不写规则说明。
|
||||
- 非敌对聊天 shouldEndChat 必须为 false。
|
||||
- 敌对聊天可以随时 shouldEndChat=true,且敌对 NPC 更偏好在话不投机、被威胁、玩家退出、底线被触碰时结束聊天。"#;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NpcChatTurnPromptInput<'a> {
|
||||
@@ -71,6 +77,17 @@ pub(crate) fn build_npc_chat_turn_reply_prompt(payload: &NpcChatTurnPromptInput<
|
||||
let closing_mode = chat_directive.and_then(|record| read_string(record.get("closingMode")));
|
||||
let is_limited_negative_affinity_chat =
|
||||
limit_reason.as_deref() == Some("negative_affinity") && turn_limit > 0.0;
|
||||
let is_hostile_model_chat = chat_directive
|
||||
.and_then(|record| read_string(record.get("terminationMode")))
|
||||
.as_deref()
|
||||
== Some("hostile_model")
|
||||
|| chat_directive
|
||||
.and_then(|record| read_bool(record.get("isHostileChat")))
|
||||
.unwrap_or(false);
|
||||
let is_player_exit_turn = chat_directive
|
||||
.and_then(|record| read_string(record.get("terminationReason")))
|
||||
.as_deref()
|
||||
== Some("player_exit");
|
||||
let is_foreshadow_close_turn = closing_mode.as_deref() == Some("foreshadow_close")
|
||||
|| chat_directive
|
||||
.and_then(|record| read_bool(record.get("forceExitAfterTurn")))
|
||||
@@ -142,6 +159,21 @@ pub(crate) fn build_npc_chat_turn_reply_prompt(payload: &NpcChatTurnPromptInput<
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if is_hostile_model_chat {
|
||||
Some("当前是敌对或负好感聊天。对方不受固定回合限制,但随时可能不耐烦、结束谈话并把局势推向战斗或驱逐。".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if is_hostile_model_chat {
|
||||
Some("敌对角色更偏好短促、戒备、带威胁的回应;如果玩家逼问、挑衅、退场或话题触到底线,回复应自然收束到对峙前一刻。".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if is_player_exit_turn {
|
||||
Some("玩家正在主动结束这轮聊天。请对这个收束动作作出回应,并留下自然的下一步入口。回复后聊天会结束。".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if is_limited_negative_affinity_chat {
|
||||
Some(format!(
|
||||
"在你回复完这一轮之后,还剩 {} 轮可以继续聊。",
|
||||
@@ -205,6 +237,22 @@ pub(crate) fn build_npc_chat_turn_suggestion_prompt(
|
||||
payload.dialogue
|
||||
};
|
||||
let combat_context_block = payload.combat_context.and_then(describe_npc_combat_context);
|
||||
let chat_directive = payload.chat_directive.and_then(as_record);
|
||||
let is_hostile_model_chat = chat_directive
|
||||
.and_then(|record| read_string(record.get("terminationMode")))
|
||||
.as_deref()
|
||||
== Some("hostile_model")
|
||||
|| chat_directive
|
||||
.and_then(|record| read_bool(record.get("isHostileChat")))
|
||||
.unwrap_or(false);
|
||||
let is_player_exit_turn = chat_directive
|
||||
.and_then(|record| read_string(record.get("terminationReason")))
|
||||
.as_deref()
|
||||
== Some("player_exit");
|
||||
let function_options_block = chat_directive
|
||||
.and_then(|record| record.get("functionOptions"))
|
||||
.map(describe_function_options)
|
||||
.filter(|text| !text.trim().is_empty());
|
||||
|
||||
[
|
||||
Some(build_npc_dialogue_prompt_base(payload)),
|
||||
@@ -213,11 +261,22 @@ pub(crate) fn build_npc_chat_turn_suggestion_prompt(
|
||||
encounter.npc_name.as_str(),
|
||||
)),
|
||||
combat_context_block,
|
||||
function_options_block,
|
||||
Some(format!("玩家刚刚说:{}", payload.player_message)),
|
||||
Some(format!("NPC 刚刚回复:{npc_reply}")),
|
||||
Some("请围绕刚刚这轮对话,为玩家生成 3 条下一轮可以直接说出口的中文接话短句。".to_string()),
|
||||
Some("每条都必须像玩家台词,不能写成行为描述、语气说明或策略建议。".to_string()),
|
||||
Some("每条都必须控制在 20 个字以内,不要加序号、引号、括号或解释。".to_string()),
|
||||
if is_hostile_model_chat {
|
||||
Some("这是敌对或负好感聊天。你需要判断这轮是否应该结束聊天;敌对角色更偏好随时终止并转入对峙。".to_string())
|
||||
} else {
|
||||
Some("这是非敌对聊天,shouldEndChat 必须为 false。".to_string())
|
||||
},
|
||||
if is_player_exit_turn {
|
||||
Some("玩家已经选择结束聊天,shouldEndChat 必须为 true,terminationReason 必须为 player_exit。".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Some("suggestions 必须按顺序生成三种明显不同的玩家台词:温和共情、冷静追问或试探、施压质疑;不要给出同一种态度的近义句。".to_string()),
|
||||
Some("functionSuggestions 从 functionOptions 中挑可触发动作并改写 actionText。".to_string()),
|
||||
Some("只输出 JSON:{\"shouldEndChat\":false,\"terminationReason\":null,\"suggestions\":[\"...\"],\"functionSuggestions\":[{\"functionId\":\"...\",\"actionText\":\"...\"}]}".to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -226,6 +285,38 @@ pub(crate) fn build_npc_chat_turn_suggestion_prompt(
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
fn describe_function_options(value: &Value) -> String {
|
||||
let lines = value
|
||||
.as_array()
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.take(8)
|
||||
.filter_map(|item| {
|
||||
let record = as_record(item)?;
|
||||
let function_id = read_string(record.get("functionId"))?;
|
||||
let action_text = read_string(record.get("actionText"))?;
|
||||
let detail_text = read_string(record.get("detailText"));
|
||||
let action = read_string(record.get("action"));
|
||||
Some(format!(
|
||||
"- functionId: {function_id}; actionText: {action_text}; action: {}; detail: {}",
|
||||
action.unwrap_or_else(|| "unknown".to_string()),
|
||||
detail_text.unwrap_or_else(|| "无".to_string()),
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if lines.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut result = vec!["当前聊天中可改写为动作候选的 functionOptions:".to_string()];
|
||||
result.extend(lines);
|
||||
result.join("\n")
|
||||
}
|
||||
|
||||
fn build_npc_dialogue_prompt_base(payload: &NpcChatTurnPromptInput<'_>) -> String {
|
||||
let encounter = describe_encounter(payload.encounter);
|
||||
|
||||
|
||||
@@ -7,15 +7,18 @@ use axum::{
|
||||
use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK, RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeProductRecord,
|
||||
RuntimeProfileRechargeProductRecord, RuntimeReferralInviteCenterRecord,
|
||||
RuntimeReferralRedeemRecord,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::runtime::{
|
||||
CreateProfileRechargeOrderRequest, CreateProfileRechargeOrderResponse,
|
||||
ProfileDashboardSummaryResponse, ProfileMembershipBenefitResponse, ProfileMembershipResponse,
|
||||
ProfilePlayStatsResponse, ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse,
|
||||
ProfileRechargeOrderResponse, ProfileRechargeProductResponse, ProfileWalletLedgerEntryResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
ProfileRechargeOrderResponse, ProfileRechargeProductResponse,
|
||||
ProfileReferralInviteCenterResponse, ProfileWalletLedgerEntryResponse,
|
||||
ProfileWalletLedgerResponse, RedeemProfileReferralInviteCodeRequest,
|
||||
RedeemProfileReferralInviteCodeResponse,
|
||||
};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
use time::OffsetDateTime;
|
||||
@@ -146,6 +149,54 @@ pub async fn create_profile_recharge_order(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_referral_invite_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_referral_invite_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_referral_invite_center_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn redeem_profile_referral_invite_code(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<RedeemProfileReferralInviteCodeRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.redeem_profile_referral_invite_code(user_id, payload.invite_code, 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_redeem_profile_referral_invite_code_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_profile_play_stats(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -284,6 +335,36 @@ fn build_profile_recharge_order_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_referral_invite_center_response(
|
||||
record: RuntimeReferralInviteCenterRecord,
|
||||
) -> ProfileReferralInviteCenterResponse {
|
||||
ProfileReferralInviteCenterResponse {
|
||||
invite_code: record.invite_code,
|
||||
invite_link_path: record.invite_link_path,
|
||||
invited_count: record.invited_count,
|
||||
rewarded_invite_count: record.rewarded_invite_count,
|
||||
today_inviter_reward_count: record.today_inviter_reward_count,
|
||||
today_inviter_reward_remaining: record.today_inviter_reward_remaining,
|
||||
reward_points: record.reward_points,
|
||||
has_redeemed_code: record.has_redeemed_code,
|
||||
bound_inviter_user_id: record.bound_inviter_user_id,
|
||||
bound_at: record.bound_at,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_redeem_profile_referral_invite_code_response(
|
||||
record: RuntimeReferralRedeemRecord,
|
||||
) -> RedeemProfileReferralInviteCodeResponse {
|
||||
RedeemProfileReferralInviteCodeResponse {
|
||||
center: build_profile_referral_invite_center_response(record.center),
|
||||
invitee_reward_granted: record.invitee_reward_granted,
|
||||
inviter_reward_granted: record.inviter_reward_granted,
|
||||
invitee_balance_after: record.invitee_balance_after,
|
||||
inviter_balance_after: record.inviter_balance_after,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::{
|
||||
@@ -391,6 +472,43 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_referral_invite_center_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/profile/referrals/invite-center")
|
||||
.body(Body::empty())
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_referral_redeem_code_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/profile/referrals/redeem-code")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"inviteCode":"SY12345678"}"#))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_dashboard_compat_route_matches_main_route_error_shape() {
|
||||
assert_compat_route_matches_main_route_error_shape(
|
||||
@@ -479,16 +597,7 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn seed_authenticated_state() -> AppState {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
state
|
||||
.password_entry_service()
|
||||
.execute(module_auth::PasswordEntryInput {
|
||||
username: "runtime_profile_user".to_string(),
|
||||
password: "secret123".to_string(),
|
||||
})
|
||||
.await
|
||||
.expect("seed login should succeed");
|
||||
state
|
||||
AppState::new(AppConfig::default()).expect("state should build")
|
||||
}
|
||||
|
||||
fn issue_access_token(state: &AppState) -> String {
|
||||
|
||||
@@ -1418,7 +1418,7 @@ pub fn build_custom_world_published_profile_compile_snapshot(
|
||||
}
|
||||
|
||||
pub fn empty_agent_anchor_content_json() -> String {
|
||||
r#"{"worldPromise":null,"playerFantasy":null,"themeBoundary":null,"playerEntryPoint":null,"coreConflict":null,"keyRelationships":[],"hiddenLines":null,"iconicElements":null}"#.to_string()
|
||||
r#"{"worldPromise":null,"playerFantasy":null,"themeBoundary":null,"playerEntryPoint":null,"coreConflict":null,"keyRelationships":null,"hiddenLines":null,"iconicElements":null}"#.to_string()
|
||||
}
|
||||
|
||||
pub fn empty_agent_creator_intent_readiness_json() -> String {
|
||||
|
||||
@@ -99,8 +99,6 @@ pub struct CharacterVisualGenerateRequest {
|
||||
pub source_mode: CharacterVisualSourceMode,
|
||||
pub prompt_text: String,
|
||||
#[serde(default)]
|
||||
pub character_brief_text: Option<String>,
|
||||
#[serde(default)]
|
||||
pub reference_image_data_urls: Vec<String>,
|
||||
pub candidate_count: u32,
|
||||
pub image_model: String,
|
||||
|
||||
@@ -5,6 +5,8 @@ pub const RUNTIME_PLATFORM_THEME_DARK: &str = "dark";
|
||||
pub const SAVE_SNAPSHOT_VERSION: u32 = 2;
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC: &str = "snapshot_sync";
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_POINTS_RECHARGE: &str = "points_recharge";
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITER_REWARD: &str = "invite_inviter_reward";
|
||||
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITEE_REWARD: &str = "invite_invitee_reward";
|
||||
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";
|
||||
@@ -220,6 +222,38 @@ pub struct CreateProfileRechargeOrderResponse {
|
||||
pub center: ProfileRechargeCenterResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileReferralInviteCenterResponse {
|
||||
pub invite_code: String,
|
||||
pub invite_link_path: String,
|
||||
pub invited_count: u32,
|
||||
pub rewarded_invite_count: u32,
|
||||
pub today_inviter_reward_count: u32,
|
||||
pub today_inviter_reward_remaining: u32,
|
||||
pub reward_points: u64,
|
||||
pub has_redeemed_code: bool,
|
||||
pub bound_inviter_user_id: Option<String>,
|
||||
pub bound_at: Option<String>,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RedeemProfileReferralInviteCodeRequest {
|
||||
pub invite_code: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RedeemProfileReferralInviteCodeResponse {
|
||||
pub center: ProfileReferralInviteCenterResponse,
|
||||
pub invitee_reward_granted: bool,
|
||||
pub inviter_reward_granted: bool,
|
||||
pub invitee_balance_after: u64,
|
||||
pub inviter_balance_after: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfilePlayedWorkSummaryResponse {
|
||||
|
||||
@@ -121,7 +121,8 @@ use module_runtime::{
|
||||
RuntimeBrowseHistoryRecord, RuntimePlatformTheme as DomainRuntimePlatformTheme,
|
||||
RuntimeProfileDashboardRecord, RuntimeProfilePlayStatsRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileSaveArchiveRecord, RuntimeProfileWalletLedgerEntryRecord, RuntimeSettingsRecord,
|
||||
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,
|
||||
@@ -132,7 +133,9 @@ use module_runtime::{
|
||||
build_runtime_profile_save_archive_list_input, build_runtime_profile_save_archive_record,
|
||||
build_runtime_profile_save_archive_resume_input,
|
||||
build_runtime_profile_wallet_ledger_entry_record,
|
||||
build_runtime_profile_wallet_ledger_list_input, build_runtime_setting_get_input,
|
||||
build_runtime_profile_wallet_ledger_list_input, build_runtime_referral_invite_center_get_input,
|
||||
build_runtime_referral_invite_center_record, build_runtime_referral_redeem_input,
|
||||
build_runtime_referral_redeem_record, build_runtime_setting_get_input,
|
||||
build_runtime_setting_record, build_runtime_setting_upsert_input,
|
||||
build_runtime_snapshot_delete_input, build_runtime_snapshot_get_input,
|
||||
build_runtime_snapshot_record, build_runtime_snapshot_upsert_input,
|
||||
|
||||
@@ -139,6 +139,26 @@ impl From<module_runtime::RuntimeProfileRechargeOrderCreateInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeReferralInviteCenterGetInput>
|
||||
for RuntimeReferralInviteCenterGetInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeReferralInviteCenterGetInput) -> Self {
|
||||
Self {
|
||||
user_id: input.user_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeReferralRedeemInput> for RuntimeReferralRedeemInput {
|
||||
fn from(input: module_runtime::RuntimeReferralRedeemInput) -> Self {
|
||||
Self {
|
||||
user_id: input.user_id,
|
||||
invite_code: input.invite_code,
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfilePlayStatsGetInput> for RuntimeProfilePlayStatsGetInput {
|
||||
fn from(input: module_runtime::RuntimeProfilePlayStatsGetInput) -> Self {
|
||||
Self {
|
||||
@@ -675,6 +695,50 @@ pub(crate) fn map_runtime_profile_recharge_order_procedure_result(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_referral_invite_center_procedure_result(
|
||||
result: RuntimeReferralInviteCenterProcedureResult,
|
||||
) -> Result<RuntimeReferralInviteCenterRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::Procedure(
|
||||
result
|
||||
.error_message
|
||||
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
|
||||
));
|
||||
}
|
||||
|
||||
let snapshot = result.record.ok_or_else(|| {
|
||||
SpacetimeClientError::Procedure(
|
||||
"SpacetimeDB procedure 未返回 referral invite center 快照".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(build_runtime_referral_invite_center_record(
|
||||
map_runtime_referral_invite_center_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_referral_redeem_procedure_result(
|
||||
result: RuntimeReferralRedeemProcedureResult,
|
||||
) -> Result<RuntimeReferralRedeemRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::Procedure(
|
||||
result
|
||||
.error_message
|
||||
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
|
||||
));
|
||||
}
|
||||
|
||||
let snapshot = result.record.ok_or_else(|| {
|
||||
SpacetimeClientError::Procedure(
|
||||
"SpacetimeDB procedure 未返回 referral redeem 快照".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(build_runtime_referral_redeem_record(
|
||||
map_runtime_referral_redeem_snapshot(snapshot),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_play_stats_procedure_result(
|
||||
result: RuntimeProfilePlayStatsProcedureResult,
|
||||
) -> Result<RuntimeProfilePlayStatsRecord, SpacetimeClientError> {
|
||||
@@ -1513,6 +1577,37 @@ pub(crate) fn map_runtime_profile_recharge_order_snapshot(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_referral_invite_center_snapshot(
|
||||
snapshot: RuntimeReferralInviteCenterSnapshot,
|
||||
) -> module_runtime::RuntimeReferralInviteCenterSnapshot {
|
||||
module_runtime::RuntimeReferralInviteCenterSnapshot {
|
||||
user_id: snapshot.user_id,
|
||||
invite_code: snapshot.invite_code,
|
||||
invite_link_path: snapshot.invite_link_path,
|
||||
invited_count: snapshot.invited_count,
|
||||
rewarded_invite_count: snapshot.rewarded_invite_count,
|
||||
today_inviter_reward_count: snapshot.today_inviter_reward_count,
|
||||
today_inviter_reward_remaining: snapshot.today_inviter_reward_remaining,
|
||||
reward_points: snapshot.reward_points,
|
||||
has_redeemed_code: snapshot.has_redeemed_code,
|
||||
bound_inviter_user_id: snapshot.bound_inviter_user_id,
|
||||
bound_at_micros: snapshot.bound_at_micros,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_referral_redeem_snapshot(
|
||||
snapshot: RuntimeReferralRedeemSnapshot,
|
||||
) -> module_runtime::RuntimeReferralRedeemSnapshot {
|
||||
module_runtime::RuntimeReferralRedeemSnapshot {
|
||||
center: map_runtime_referral_invite_center_snapshot(snapshot.center),
|
||||
invitee_reward_granted: snapshot.invitee_reward_granted,
|
||||
inviter_reward_granted: snapshot.inviter_reward_granted,
|
||||
invitee_balance_after: snapshot.invitee_balance_after,
|
||||
inviter_balance_after: snapshot.inviter_balance_after,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_played_world_snapshot(
|
||||
snapshot: RuntimeProfilePlayedWorldSnapshot,
|
||||
) -> module_runtime::RuntimeProfilePlayedWorldSnapshot {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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_referral_invite_center_get_input_type::RuntimeReferralInviteCenterGetInput;
|
||||
use super::runtime_referral_invite_center_procedure_result_type::RuntimeReferralInviteCenterProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct GetProfileReferralInviteCenterArgs {
|
||||
pub input: RuntimeReferralInviteCenterGetInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for GetProfileReferralInviteCenterArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `get_profile_referral_invite_center`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait get_profile_referral_invite_center {
|
||||
fn get_profile_referral_invite_center(&self, input: RuntimeReferralInviteCenterGetInput,
|
||||
) {
|
||||
self.get_profile_referral_invite_center_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn get_profile_referral_invite_center_then(
|
||||
&self,
|
||||
input: RuntimeReferralInviteCenterGetInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeReferralInviteCenterProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl get_profile_referral_invite_center for super::RemoteProcedures {
|
||||
fn get_profile_referral_invite_center_then(
|
||||
&self,
|
||||
input: RuntimeReferralInviteCenterGetInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeReferralInviteCenterProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, RuntimeReferralInviteCenterProcedureResult>(
|
||||
"get_profile_referral_invite_center",
|
||||
GetProfileReferralInviteCenterArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +189,11 @@ pub mod player_progression_grant_source_type;
|
||||
pub mod player_progression_procedure_result_type;
|
||||
pub mod player_progression_snapshot_type;
|
||||
pub mod profile_dashboard_state_type;
|
||||
pub mod profile_invite_code_type;
|
||||
pub mod profile_membership_type;
|
||||
pub mod profile_played_world_type;
|
||||
pub mod profile_recharge_order_type;
|
||||
pub mod profile_referral_relation_type;
|
||||
pub mod profile_save_archive_type;
|
||||
pub mod profile_wallet_ledger_type;
|
||||
pub mod puzzle_agent_message_finalize_input_type;
|
||||
@@ -305,6 +307,12 @@ pub mod runtime_profile_wallet_ledger_entry_snapshot_type;
|
||||
pub mod runtime_profile_wallet_ledger_list_input_type;
|
||||
pub mod runtime_profile_wallet_ledger_procedure_result_type;
|
||||
pub mod runtime_profile_wallet_ledger_source_type_type;
|
||||
pub mod runtime_referral_invite_center_get_input_type;
|
||||
pub mod runtime_referral_invite_center_procedure_result_type;
|
||||
pub mod runtime_referral_invite_center_snapshot_type;
|
||||
pub mod runtime_referral_redeem_input_type;
|
||||
pub mod runtime_referral_redeem_procedure_result_type;
|
||||
pub mod runtime_referral_redeem_snapshot_type;
|
||||
pub mod runtime_setting_type;
|
||||
pub mod runtime_setting_get_input_type;
|
||||
pub mod runtime_setting_procedure_result_type;
|
||||
@@ -359,7 +367,52 @@ pub mod unpublish_custom_world_profile_reducer;
|
||||
pub mod upsert_chapter_progression_reducer;
|
||||
pub mod upsert_custom_world_profile_reducer;
|
||||
pub mod upsert_npc_state_reducer;
|
||||
pub mod ai_result_reference_table;
|
||||
pub mod ai_task_table;
|
||||
pub mod ai_task_stage_table;
|
||||
pub mod ai_text_chunk_table;
|
||||
pub mod asset_entity_binding_table;
|
||||
pub mod asset_object_table;
|
||||
pub mod auth_identity_table;
|
||||
pub mod auth_store_snapshot_table;
|
||||
pub mod battle_state_table;
|
||||
pub mod big_fish_agent_message_table;
|
||||
pub mod big_fish_asset_slot_table;
|
||||
pub mod big_fish_creation_session_table;
|
||||
pub mod big_fish_runtime_run_table;
|
||||
pub mod chapter_progression_table;
|
||||
pub mod custom_world_agent_message_table;
|
||||
pub mod custom_world_agent_operation_table;
|
||||
pub mod custom_world_agent_session_table;
|
||||
pub mod custom_world_draft_card_table;
|
||||
pub mod custom_world_gallery_entry_table;
|
||||
pub mod custom_world_profile_table;
|
||||
pub mod custom_world_session_table;
|
||||
pub mod inventory_slot_table;
|
||||
pub mod npc_state_table;
|
||||
pub mod player_progression_table;
|
||||
pub mod profile_dashboard_state_table;
|
||||
pub mod profile_invite_code_table;
|
||||
pub mod profile_membership_table;
|
||||
pub mod profile_played_world_table;
|
||||
pub mod profile_recharge_order_table;
|
||||
pub mod profile_referral_relation_table;
|
||||
pub mod profile_save_archive_table;
|
||||
pub mod profile_wallet_ledger_table;
|
||||
pub mod puzzle_agent_message_table;
|
||||
pub mod puzzle_agent_session_table;
|
||||
pub mod puzzle_runtime_run_table;
|
||||
pub mod puzzle_work_profile_table;
|
||||
pub mod quest_log_table;
|
||||
pub mod quest_record_table;
|
||||
pub mod refresh_session_table;
|
||||
pub mod runtime_setting_table;
|
||||
pub mod runtime_snapshot_table;
|
||||
pub mod story_event_table;
|
||||
pub mod story_session_table;
|
||||
pub mod treasure_record_table;
|
||||
pub mod user_account_table;
|
||||
pub mod user_browse_history_table;
|
||||
pub mod advance_puzzle_next_level_procedure;
|
||||
pub mod append_ai_text_chunk_and_return_procedure;
|
||||
pub mod apply_chapter_progression_ledger_entry_and_return_procedure;
|
||||
@@ -409,6 +462,7 @@ pub mod get_player_progression_or_default_procedure;
|
||||
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_puzzle_agent_session_procedure;
|
||||
pub mod get_puzzle_gallery_detail_procedure;
|
||||
pub mod get_puzzle_run_procedure;
|
||||
@@ -432,6 +486,7 @@ pub mod publish_big_fish_game_procedure;
|
||||
pub mod publish_custom_world_profile_and_return_procedure;
|
||||
pub mod publish_custom_world_world_procedure;
|
||||
pub mod publish_puzzle_work_procedure;
|
||||
pub mod redeem_profile_referral_invite_code_procedure;
|
||||
pub mod resolve_combat_action_and_return_procedure;
|
||||
pub mod resolve_npc_battle_interaction_and_return_procedure;
|
||||
pub mod resolve_npc_interaction_and_return_procedure;
|
||||
@@ -636,9 +691,11 @@ pub use player_progression_grant_source_type::PlayerProgressionGrantSource;
|
||||
pub use player_progression_procedure_result_type::PlayerProgressionProcedureResult;
|
||||
pub use player_progression_snapshot_type::PlayerProgressionSnapshot;
|
||||
pub use profile_dashboard_state_type::ProfileDashboardState;
|
||||
pub use profile_invite_code_type::ProfileInviteCode;
|
||||
pub use profile_membership_type::ProfileMembership;
|
||||
pub use profile_played_world_type::ProfilePlayedWorld;
|
||||
pub use profile_recharge_order_type::ProfileRechargeOrder;
|
||||
pub use profile_referral_relation_type::ProfileReferralRelation;
|
||||
pub use profile_save_archive_type::ProfileSaveArchive;
|
||||
pub use profile_wallet_ledger_type::ProfileWalletLedger;
|
||||
pub use puzzle_agent_message_finalize_input_type::PuzzleAgentMessageFinalizeInput;
|
||||
@@ -752,6 +809,12 @@ pub use runtime_profile_wallet_ledger_entry_snapshot_type::RuntimeProfileWalletL
|
||||
pub use runtime_profile_wallet_ledger_list_input_type::RuntimeProfileWalletLedgerListInput;
|
||||
pub use runtime_profile_wallet_ledger_procedure_result_type::RuntimeProfileWalletLedgerProcedureResult;
|
||||
pub use runtime_profile_wallet_ledger_source_type_type::RuntimeProfileWalletLedgerSourceType;
|
||||
pub use runtime_referral_invite_center_get_input_type::RuntimeReferralInviteCenterGetInput;
|
||||
pub use runtime_referral_invite_center_procedure_result_type::RuntimeReferralInviteCenterProcedureResult;
|
||||
pub use runtime_referral_invite_center_snapshot_type::RuntimeReferralInviteCenterSnapshot;
|
||||
pub use runtime_referral_redeem_input_type::RuntimeReferralRedeemInput;
|
||||
pub use runtime_referral_redeem_procedure_result_type::RuntimeReferralRedeemProcedureResult;
|
||||
pub use runtime_referral_redeem_snapshot_type::RuntimeReferralRedeemSnapshot;
|
||||
pub use runtime_setting_type::RuntimeSetting;
|
||||
pub use runtime_setting_get_input_type::RuntimeSettingGetInput;
|
||||
pub use runtime_setting_procedure_result_type::RuntimeSettingProcedureResult;
|
||||
@@ -782,7 +845,52 @@ pub use treasure_resolve_input_type::TreasureResolveInput;
|
||||
pub use unequip_inventory_item_input_type::UnequipInventoryItemInput;
|
||||
pub use user_account_type::UserAccount;
|
||||
pub use user_browse_history_type::UserBrowseHistory;
|
||||
pub use ai_result_reference_table::*;
|
||||
pub use ai_task_table::*;
|
||||
pub use ai_task_stage_table::*;
|
||||
pub use ai_text_chunk_table::*;
|
||||
pub use asset_entity_binding_table::*;
|
||||
pub use asset_object_table::*;
|
||||
pub use auth_identity_table::*;
|
||||
pub use auth_store_snapshot_table::*;
|
||||
pub use battle_state_table::*;
|
||||
pub use big_fish_agent_message_table::*;
|
||||
pub use big_fish_asset_slot_table::*;
|
||||
pub use big_fish_creation_session_table::*;
|
||||
pub use big_fish_runtime_run_table::*;
|
||||
pub use chapter_progression_table::*;
|
||||
pub use custom_world_agent_message_table::*;
|
||||
pub use custom_world_agent_operation_table::*;
|
||||
pub use custom_world_agent_session_table::*;
|
||||
pub use custom_world_draft_card_table::*;
|
||||
pub use custom_world_gallery_entry_table::*;
|
||||
pub use custom_world_profile_table::*;
|
||||
pub use custom_world_session_table::*;
|
||||
pub use inventory_slot_table::*;
|
||||
pub use npc_state_table::*;
|
||||
pub use player_progression_table::*;
|
||||
pub use profile_dashboard_state_table::*;
|
||||
pub use profile_invite_code_table::*;
|
||||
pub use profile_membership_table::*;
|
||||
pub use profile_played_world_table::*;
|
||||
pub use profile_recharge_order_table::*;
|
||||
pub use profile_referral_relation_table::*;
|
||||
pub use profile_save_archive_table::*;
|
||||
pub use profile_wallet_ledger_table::*;
|
||||
pub use puzzle_agent_message_table::*;
|
||||
pub use puzzle_agent_session_table::*;
|
||||
pub use puzzle_runtime_run_table::*;
|
||||
pub use puzzle_work_profile_table::*;
|
||||
pub use quest_log_table::*;
|
||||
pub use quest_record_table::*;
|
||||
pub use refresh_session_table::*;
|
||||
pub use runtime_setting_table::*;
|
||||
pub use runtime_snapshot_table::*;
|
||||
pub use story_event_table::*;
|
||||
pub use story_session_table::*;
|
||||
pub use treasure_record_table::*;
|
||||
pub use user_account_table::*;
|
||||
pub use user_browse_history_table::*;
|
||||
pub use accept_quest_reducer::accept_quest;
|
||||
pub use acknowledge_quest_completion_reducer::acknowledge_quest_completion;
|
||||
pub use apply_chapter_progression_ledger_entry_reducer::apply_chapter_progression_ledger_entry;
|
||||
@@ -856,6 +964,7 @@ pub use get_player_progression_or_default_procedure::get_player_progression_or_d
|
||||
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_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;
|
||||
@@ -879,6 +988,7 @@ pub use publish_big_fish_game_procedure::publish_big_fish_game;
|
||||
pub use publish_custom_world_profile_and_return_procedure::publish_custom_world_profile_and_return;
|
||||
pub use publish_custom_world_world_procedure::publish_custom_world_world;
|
||||
pub use publish_puzzle_work_procedure::publish_puzzle_work;
|
||||
pub use redeem_profile_referral_invite_code_procedure::redeem_profile_referral_invite_code;
|
||||
pub use resolve_combat_action_and_return_procedure::resolve_combat_action_and_return;
|
||||
pub use resolve_npc_battle_interaction_and_return_procedure::resolve_npc_battle_interaction_and_return;
|
||||
pub use resolve_npc_interaction_and_return_procedure::resolve_npc_interaction_and_return;
|
||||
@@ -1154,7 +1264,52 @@ fn args_bsatn(&self) -> Result<Vec<u8>, __sats::bsatn::EncodeError> {
|
||||
#[allow(non_snake_case)]
|
||||
#[doc(hidden)]
|
||||
pub struct DbUpdate {
|
||||
custom_world_gallery_entry: __sdk::TableUpdate<CustomWorldGalleryEntry>,
|
||||
ai_result_reference: __sdk::TableUpdate<AiResultReference>,
|
||||
ai_task: __sdk::TableUpdate<AiTask>,
|
||||
ai_task_stage: __sdk::TableUpdate<AiTaskStage>,
|
||||
ai_text_chunk: __sdk::TableUpdate<AiTextChunk>,
|
||||
asset_entity_binding: __sdk::TableUpdate<AssetEntityBinding>,
|
||||
asset_object: __sdk::TableUpdate<AssetObject>,
|
||||
auth_identity: __sdk::TableUpdate<AuthIdentity>,
|
||||
auth_store_snapshot: __sdk::TableUpdate<AuthStoreSnapshot>,
|
||||
battle_state: __sdk::TableUpdate<BattleState>,
|
||||
big_fish_agent_message: __sdk::TableUpdate<BigFishAgentMessage>,
|
||||
big_fish_asset_slot: __sdk::TableUpdate<BigFishAssetSlot>,
|
||||
big_fish_creation_session: __sdk::TableUpdate<BigFishCreationSession>,
|
||||
big_fish_runtime_run: __sdk::TableUpdate<BigFishRuntimeRun>,
|
||||
chapter_progression: __sdk::TableUpdate<ChapterProgression>,
|
||||
custom_world_agent_message: __sdk::TableUpdate<CustomWorldAgentMessage>,
|
||||
custom_world_agent_operation: __sdk::TableUpdate<CustomWorldAgentOperation>,
|
||||
custom_world_agent_session: __sdk::TableUpdate<CustomWorldAgentSession>,
|
||||
custom_world_draft_card: __sdk::TableUpdate<CustomWorldDraftCard>,
|
||||
custom_world_gallery_entry: __sdk::TableUpdate<CustomWorldGalleryEntry>,
|
||||
custom_world_profile: __sdk::TableUpdate<CustomWorldProfile>,
|
||||
custom_world_session: __sdk::TableUpdate<CustomWorldSession>,
|
||||
inventory_slot: __sdk::TableUpdate<InventorySlot>,
|
||||
npc_state: __sdk::TableUpdate<NpcState>,
|
||||
player_progression: __sdk::TableUpdate<PlayerProgression>,
|
||||
profile_dashboard_state: __sdk::TableUpdate<ProfileDashboardState>,
|
||||
profile_invite_code: __sdk::TableUpdate<ProfileInviteCode>,
|
||||
profile_membership: __sdk::TableUpdate<ProfileMembership>,
|
||||
profile_played_world: __sdk::TableUpdate<ProfilePlayedWorld>,
|
||||
profile_recharge_order: __sdk::TableUpdate<ProfileRechargeOrder>,
|
||||
profile_referral_relation: __sdk::TableUpdate<ProfileReferralRelation>,
|
||||
profile_save_archive: __sdk::TableUpdate<ProfileSaveArchive>,
|
||||
profile_wallet_ledger: __sdk::TableUpdate<ProfileWalletLedger>,
|
||||
puzzle_agent_message: __sdk::TableUpdate<PuzzleAgentMessageRow>,
|
||||
puzzle_agent_session: __sdk::TableUpdate<PuzzleAgentSessionRow>,
|
||||
puzzle_runtime_run: __sdk::TableUpdate<PuzzleRuntimeRunRow>,
|
||||
puzzle_work_profile: __sdk::TableUpdate<PuzzleWorkProfileRow>,
|
||||
quest_log: __sdk::TableUpdate<QuestLog>,
|
||||
quest_record: __sdk::TableUpdate<QuestRecord>,
|
||||
refresh_session: __sdk::TableUpdate<RefreshSession>,
|
||||
runtime_setting: __sdk::TableUpdate<RuntimeSetting>,
|
||||
runtime_snapshot: __sdk::TableUpdate<RuntimeSnapshotRow>,
|
||||
story_event: __sdk::TableUpdate<StoryEvent>,
|
||||
story_session: __sdk::TableUpdate<StorySession>,
|
||||
treasure_record: __sdk::TableUpdate<TreasureRecord>,
|
||||
user_account: __sdk::TableUpdate<UserAccount>,
|
||||
user_browse_history: __sdk::TableUpdate<UserBrowseHistory>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1165,7 +1320,52 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
|
||||
for table_update in __sdk::transaction_update_iter_table_updates(raw) {
|
||||
match &table_update.table_name[..] {
|
||||
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(custom_world_gallery_entry_table::parse_table_update(table_update)?),
|
||||
"ai_result_reference" => db_update.ai_result_reference.append(ai_result_reference_table::parse_table_update(table_update)?),
|
||||
"ai_task" => db_update.ai_task.append(ai_task_table::parse_table_update(table_update)?),
|
||||
"ai_task_stage" => db_update.ai_task_stage.append(ai_task_stage_table::parse_table_update(table_update)?),
|
||||
"ai_text_chunk" => db_update.ai_text_chunk.append(ai_text_chunk_table::parse_table_update(table_update)?),
|
||||
"asset_entity_binding" => db_update.asset_entity_binding.append(asset_entity_binding_table::parse_table_update(table_update)?),
|
||||
"asset_object" => db_update.asset_object.append(asset_object_table::parse_table_update(table_update)?),
|
||||
"auth_identity" => db_update.auth_identity.append(auth_identity_table::parse_table_update(table_update)?),
|
||||
"auth_store_snapshot" => db_update.auth_store_snapshot.append(auth_store_snapshot_table::parse_table_update(table_update)?),
|
||||
"battle_state" => db_update.battle_state.append(battle_state_table::parse_table_update(table_update)?),
|
||||
"big_fish_agent_message" => db_update.big_fish_agent_message.append(big_fish_agent_message_table::parse_table_update(table_update)?),
|
||||
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(big_fish_asset_slot_table::parse_table_update(table_update)?),
|
||||
"big_fish_creation_session" => db_update.big_fish_creation_session.append(big_fish_creation_session_table::parse_table_update(table_update)?),
|
||||
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(big_fish_runtime_run_table::parse_table_update(table_update)?),
|
||||
"chapter_progression" => db_update.chapter_progression.append(chapter_progression_table::parse_table_update(table_update)?),
|
||||
"custom_world_agent_message" => db_update.custom_world_agent_message.append(custom_world_agent_message_table::parse_table_update(table_update)?),
|
||||
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(custom_world_agent_operation_table::parse_table_update(table_update)?),
|
||||
"custom_world_agent_session" => db_update.custom_world_agent_session.append(custom_world_agent_session_table::parse_table_update(table_update)?),
|
||||
"custom_world_draft_card" => db_update.custom_world_draft_card.append(custom_world_draft_card_table::parse_table_update(table_update)?),
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(custom_world_gallery_entry_table::parse_table_update(table_update)?),
|
||||
"custom_world_profile" => db_update.custom_world_profile.append(custom_world_profile_table::parse_table_update(table_update)?),
|
||||
"custom_world_session" => db_update.custom_world_session.append(custom_world_session_table::parse_table_update(table_update)?),
|
||||
"inventory_slot" => db_update.inventory_slot.append(inventory_slot_table::parse_table_update(table_update)?),
|
||||
"npc_state" => db_update.npc_state.append(npc_state_table::parse_table_update(table_update)?),
|
||||
"player_progression" => db_update.player_progression.append(player_progression_table::parse_table_update(table_update)?),
|
||||
"profile_dashboard_state" => db_update.profile_dashboard_state.append(profile_dashboard_state_table::parse_table_update(table_update)?),
|
||||
"profile_invite_code" => db_update.profile_invite_code.append(profile_invite_code_table::parse_table_update(table_update)?),
|
||||
"profile_membership" => db_update.profile_membership.append(profile_membership_table::parse_table_update(table_update)?),
|
||||
"profile_played_world" => db_update.profile_played_world.append(profile_played_world_table::parse_table_update(table_update)?),
|
||||
"profile_recharge_order" => db_update.profile_recharge_order.append(profile_recharge_order_table::parse_table_update(table_update)?),
|
||||
"profile_referral_relation" => db_update.profile_referral_relation.append(profile_referral_relation_table::parse_table_update(table_update)?),
|
||||
"profile_save_archive" => db_update.profile_save_archive.append(profile_save_archive_table::parse_table_update(table_update)?),
|
||||
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(profile_wallet_ledger_table::parse_table_update(table_update)?),
|
||||
"puzzle_agent_message" => db_update.puzzle_agent_message.append(puzzle_agent_message_table::parse_table_update(table_update)?),
|
||||
"puzzle_agent_session" => db_update.puzzle_agent_session.append(puzzle_agent_session_table::parse_table_update(table_update)?),
|
||||
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(puzzle_runtime_run_table::parse_table_update(table_update)?),
|
||||
"puzzle_work_profile" => db_update.puzzle_work_profile.append(puzzle_work_profile_table::parse_table_update(table_update)?),
|
||||
"quest_log" => db_update.quest_log.append(quest_log_table::parse_table_update(table_update)?),
|
||||
"quest_record" => db_update.quest_record.append(quest_record_table::parse_table_update(table_update)?),
|
||||
"refresh_session" => db_update.refresh_session.append(refresh_session_table::parse_table_update(table_update)?),
|
||||
"runtime_setting" => db_update.runtime_setting.append(runtime_setting_table::parse_table_update(table_update)?),
|
||||
"runtime_snapshot" => db_update.runtime_snapshot.append(runtime_snapshot_table::parse_table_update(table_update)?),
|
||||
"story_event" => db_update.story_event.append(story_event_table::parse_table_update(table_update)?),
|
||||
"story_session" => db_update.story_session.append(story_session_table::parse_table_update(table_update)?),
|
||||
"treasure_record" => db_update.treasure_record.append(treasure_record_table::parse_table_update(table_update)?),
|
||||
"user_account" => db_update.user_account.append(user_account_table::parse_table_update(table_update)?),
|
||||
"user_browse_history" => db_update.user_browse_history.append(user_browse_history_table::parse_table_update(table_update)?),
|
||||
|
||||
unknown => {
|
||||
return Err(__sdk::InternalError::unknown_name(
|
||||
@@ -1188,7 +1388,52 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache<RemoteModule>) -> AppliedDiff<'_> {
|
||||
let mut diff = AppliedDiff::default();
|
||||
|
||||
diff.custom_world_gallery_entry = cache.apply_diff_to_table::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry).with_updates_by_pk(|row| &row.profile_id);
|
||||
diff.ai_result_reference = cache.apply_diff_to_table::<AiResultReference>("ai_result_reference", &self.ai_result_reference).with_updates_by_pk(|row| &row.result_reference_row_id);
|
||||
diff.ai_task = cache.apply_diff_to_table::<AiTask>("ai_task", &self.ai_task).with_updates_by_pk(|row| &row.task_id);
|
||||
diff.ai_task_stage = cache.apply_diff_to_table::<AiTaskStage>("ai_task_stage", &self.ai_task_stage).with_updates_by_pk(|row| &row.task_stage_id);
|
||||
diff.ai_text_chunk = cache.apply_diff_to_table::<AiTextChunk>("ai_text_chunk", &self.ai_text_chunk).with_updates_by_pk(|row| &row.text_chunk_row_id);
|
||||
diff.asset_entity_binding = cache.apply_diff_to_table::<AssetEntityBinding>("asset_entity_binding", &self.asset_entity_binding).with_updates_by_pk(|row| &row.binding_id);
|
||||
diff.asset_object = cache.apply_diff_to_table::<AssetObject>("asset_object", &self.asset_object).with_updates_by_pk(|row| &row.asset_object_id);
|
||||
diff.auth_identity = cache.apply_diff_to_table::<AuthIdentity>("auth_identity", &self.auth_identity).with_updates_by_pk(|row| &row.identity_id);
|
||||
diff.auth_store_snapshot = cache.apply_diff_to_table::<AuthStoreSnapshot>("auth_store_snapshot", &self.auth_store_snapshot).with_updates_by_pk(|row| &row.snapshot_id);
|
||||
diff.battle_state = cache.apply_diff_to_table::<BattleState>("battle_state", &self.battle_state).with_updates_by_pk(|row| &row.battle_state_id);
|
||||
diff.big_fish_agent_message = cache.apply_diff_to_table::<BigFishAgentMessage>("big_fish_agent_message", &self.big_fish_agent_message).with_updates_by_pk(|row| &row.message_id);
|
||||
diff.big_fish_asset_slot = cache.apply_diff_to_table::<BigFishAssetSlot>("big_fish_asset_slot", &self.big_fish_asset_slot).with_updates_by_pk(|row| &row.slot_id);
|
||||
diff.big_fish_creation_session = cache.apply_diff_to_table::<BigFishCreationSession>("big_fish_creation_session", &self.big_fish_creation_session).with_updates_by_pk(|row| &row.session_id);
|
||||
diff.big_fish_runtime_run = cache.apply_diff_to_table::<BigFishRuntimeRun>("big_fish_runtime_run", &self.big_fish_runtime_run).with_updates_by_pk(|row| &row.run_id);
|
||||
diff.chapter_progression = cache.apply_diff_to_table::<ChapterProgression>("chapter_progression", &self.chapter_progression).with_updates_by_pk(|row| &row.chapter_progression_id);
|
||||
diff.custom_world_agent_message = cache.apply_diff_to_table::<CustomWorldAgentMessage>("custom_world_agent_message", &self.custom_world_agent_message).with_updates_by_pk(|row| &row.message_id);
|
||||
diff.custom_world_agent_operation = cache.apply_diff_to_table::<CustomWorldAgentOperation>("custom_world_agent_operation", &self.custom_world_agent_operation).with_updates_by_pk(|row| &row.operation_id);
|
||||
diff.custom_world_agent_session = cache.apply_diff_to_table::<CustomWorldAgentSession>("custom_world_agent_session", &self.custom_world_agent_session).with_updates_by_pk(|row| &row.session_id);
|
||||
diff.custom_world_draft_card = cache.apply_diff_to_table::<CustomWorldDraftCard>("custom_world_draft_card", &self.custom_world_draft_card).with_updates_by_pk(|row| &row.card_id);
|
||||
diff.custom_world_gallery_entry = cache.apply_diff_to_table::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry).with_updates_by_pk(|row| &row.profile_id);
|
||||
diff.custom_world_profile = cache.apply_diff_to_table::<CustomWorldProfile>("custom_world_profile", &self.custom_world_profile).with_updates_by_pk(|row| &row.profile_id);
|
||||
diff.custom_world_session = cache.apply_diff_to_table::<CustomWorldSession>("custom_world_session", &self.custom_world_session).with_updates_by_pk(|row| &row.session_id);
|
||||
diff.inventory_slot = cache.apply_diff_to_table::<InventorySlot>("inventory_slot", &self.inventory_slot).with_updates_by_pk(|row| &row.slot_id);
|
||||
diff.npc_state = cache.apply_diff_to_table::<NpcState>("npc_state", &self.npc_state).with_updates_by_pk(|row| &row.npc_state_id);
|
||||
diff.player_progression = cache.apply_diff_to_table::<PlayerProgression>("player_progression", &self.player_progression).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.profile_dashboard_state = cache.apply_diff_to_table::<ProfileDashboardState>("profile_dashboard_state", &self.profile_dashboard_state).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.profile_invite_code = cache.apply_diff_to_table::<ProfileInviteCode>("profile_invite_code", &self.profile_invite_code).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.profile_membership = cache.apply_diff_to_table::<ProfileMembership>("profile_membership", &self.profile_membership).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.profile_played_world = cache.apply_diff_to_table::<ProfilePlayedWorld>("profile_played_world", &self.profile_played_world).with_updates_by_pk(|row| &row.played_world_id);
|
||||
diff.profile_recharge_order = cache.apply_diff_to_table::<ProfileRechargeOrder>("profile_recharge_order", &self.profile_recharge_order).with_updates_by_pk(|row| &row.order_id);
|
||||
diff.profile_referral_relation = cache.apply_diff_to_table::<ProfileReferralRelation>("profile_referral_relation", &self.profile_referral_relation).with_updates_by_pk(|row| &row.invitee_user_id);
|
||||
diff.profile_save_archive = cache.apply_diff_to_table::<ProfileSaveArchive>("profile_save_archive", &self.profile_save_archive).with_updates_by_pk(|row| &row.archive_id);
|
||||
diff.profile_wallet_ledger = cache.apply_diff_to_table::<ProfileWalletLedger>("profile_wallet_ledger", &self.profile_wallet_ledger).with_updates_by_pk(|row| &row.wallet_ledger_id);
|
||||
diff.puzzle_agent_message = cache.apply_diff_to_table::<PuzzleAgentMessageRow>("puzzle_agent_message", &self.puzzle_agent_message).with_updates_by_pk(|row| &row.message_id);
|
||||
diff.puzzle_agent_session = cache.apply_diff_to_table::<PuzzleAgentSessionRow>("puzzle_agent_session", &self.puzzle_agent_session).with_updates_by_pk(|row| &row.session_id);
|
||||
diff.puzzle_runtime_run = cache.apply_diff_to_table::<PuzzleRuntimeRunRow>("puzzle_runtime_run", &self.puzzle_runtime_run).with_updates_by_pk(|row| &row.run_id);
|
||||
diff.puzzle_work_profile = cache.apply_diff_to_table::<PuzzleWorkProfileRow>("puzzle_work_profile", &self.puzzle_work_profile).with_updates_by_pk(|row| &row.profile_id);
|
||||
diff.quest_log = cache.apply_diff_to_table::<QuestLog>("quest_log", &self.quest_log).with_updates_by_pk(|row| &row.log_id);
|
||||
diff.quest_record = cache.apply_diff_to_table::<QuestRecord>("quest_record", &self.quest_record).with_updates_by_pk(|row| &row.quest_id);
|
||||
diff.refresh_session = cache.apply_diff_to_table::<RefreshSession>("refresh_session", &self.refresh_session).with_updates_by_pk(|row| &row.session_id);
|
||||
diff.runtime_setting = cache.apply_diff_to_table::<RuntimeSetting>("runtime_setting", &self.runtime_setting).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.runtime_snapshot = cache.apply_diff_to_table::<RuntimeSnapshotRow>("runtime_snapshot", &self.runtime_snapshot).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.story_event = cache.apply_diff_to_table::<StoryEvent>("story_event", &self.story_event).with_updates_by_pk(|row| &row.event_id);
|
||||
diff.story_session = cache.apply_diff_to_table::<StorySession>("story_session", &self.story_session).with_updates_by_pk(|row| &row.story_session_id);
|
||||
diff.treasure_record = cache.apply_diff_to_table::<TreasureRecord>("treasure_record", &self.treasure_record).with_updates_by_pk(|row| &row.treasure_record_id);
|
||||
diff.user_account = cache.apply_diff_to_table::<UserAccount>("user_account", &self.user_account).with_updates_by_pk(|row| &row.user_id);
|
||||
diff.user_browse_history = cache.apply_diff_to_table::<UserBrowseHistory>("user_browse_history", &self.user_browse_history).with_updates_by_pk(|row| &row.browse_history_id);
|
||||
|
||||
diff
|
||||
}
|
||||
@@ -1196,7 +1441,52 @@ fn parse_initial_rows(raw: __ws::v2::QueryRows) -> __sdk::Result<Self> {
|
||||
let mut db_update = DbUpdate::default();
|
||||
for table_rows in raw.tables {
|
||||
match &table_rows.table[..] {
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"ai_result_reference" => db_update.ai_result_reference.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"ai_task" => db_update.ai_task.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"ai_task_stage" => db_update.ai_task_stage.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"ai_text_chunk" => db_update.ai_text_chunk.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"asset_entity_binding" => db_update.asset_entity_binding.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"asset_object" => db_update.asset_object.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"auth_identity" => db_update.auth_identity.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"auth_store_snapshot" => db_update.auth_store_snapshot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"battle_state" => db_update.battle_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"big_fish_agent_message" => db_update.big_fish_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"big_fish_creation_session" => db_update.big_fish_creation_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"chapter_progression" => db_update.chapter_progression.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_agent_message" => db_update.custom_world_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_agent_session" => db_update.custom_world_agent_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_draft_card" => db_update.custom_world_draft_card.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_profile" => db_update.custom_world_profile.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"custom_world_session" => db_update.custom_world_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"inventory_slot" => db_update.inventory_slot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"npc_state" => db_update.npc_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"player_progression" => db_update.player_progression.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_dashboard_state" => db_update.profile_dashboard_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_invite_code" => db_update.profile_invite_code.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_membership" => db_update.profile_membership.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_played_world" => db_update.profile_played_world.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_recharge_order" => db_update.profile_recharge_order.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_referral_relation" => db_update.profile_referral_relation.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_save_archive" => db_update.profile_save_archive.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"puzzle_agent_message" => db_update.puzzle_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"puzzle_agent_session" => db_update.puzzle_agent_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"puzzle_work_profile" => db_update.puzzle_work_profile.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"quest_log" => db_update.quest_log.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"quest_record" => db_update.quest_record.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"refresh_session" => db_update.refresh_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"runtime_setting" => db_update.runtime_setting.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"runtime_snapshot" => db_update.runtime_snapshot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"story_event" => db_update.story_event.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"story_session" => db_update.story_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"treasure_record" => db_update.treasure_record.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"user_account" => db_update.user_account.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"user_browse_history" => db_update.user_browse_history.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); }
|
||||
}} Ok(db_update)
|
||||
}
|
||||
@@ -1204,7 +1494,52 @@ fn parse_unsubscribe_rows(raw: __ws::v2::QueryRows) -> __sdk::Result<Self> {
|
||||
let mut db_update = DbUpdate::default();
|
||||
for table_rows in raw.tables {
|
||||
match &table_rows.table[..] {
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"ai_result_reference" => db_update.ai_result_reference.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"ai_task" => db_update.ai_task.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"ai_task_stage" => db_update.ai_task_stage.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"ai_text_chunk" => db_update.ai_text_chunk.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"asset_entity_binding" => db_update.asset_entity_binding.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"asset_object" => db_update.asset_object.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"auth_identity" => db_update.auth_identity.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"auth_store_snapshot" => db_update.auth_store_snapshot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"battle_state" => db_update.battle_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"big_fish_agent_message" => db_update.big_fish_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"big_fish_creation_session" => db_update.big_fish_creation_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"chapter_progression" => db_update.chapter_progression.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_agent_message" => db_update.custom_world_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_agent_session" => db_update.custom_world_agent_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_draft_card" => db_update.custom_world_draft_card.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_profile" => db_update.custom_world_profile.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"custom_world_session" => db_update.custom_world_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"inventory_slot" => db_update.inventory_slot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"npc_state" => db_update.npc_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"player_progression" => db_update.player_progression.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_dashboard_state" => db_update.profile_dashboard_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_invite_code" => db_update.profile_invite_code.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_membership" => db_update.profile_membership.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_played_world" => db_update.profile_played_world.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_recharge_order" => db_update.profile_recharge_order.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_referral_relation" => db_update.profile_referral_relation.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_save_archive" => db_update.profile_save_archive.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"puzzle_agent_message" => db_update.puzzle_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"puzzle_agent_session" => db_update.puzzle_agent_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"puzzle_work_profile" => db_update.puzzle_work_profile.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"quest_log" => db_update.quest_log.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"quest_record" => db_update.quest_record.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"refresh_session" => db_update.refresh_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"runtime_setting" => db_update.runtime_setting.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"runtime_snapshot" => db_update.runtime_snapshot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"story_event" => db_update.story_event.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"story_session" => db_update.story_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"treasure_record" => db_update.treasure_record.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"user_account" => db_update.user_account.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"user_browse_history" => db_update.user_browse_history.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); }
|
||||
}} Ok(db_update)
|
||||
}
|
||||
@@ -1214,7 +1549,52 @@ for table_rows in raw.tables {
|
||||
#[allow(non_snake_case)]
|
||||
#[doc(hidden)]
|
||||
pub struct AppliedDiff<'r> {
|
||||
custom_world_gallery_entry: __sdk::TableAppliedDiff<'r, CustomWorldGalleryEntry>,
|
||||
ai_result_reference: __sdk::TableAppliedDiff<'r, AiResultReference>,
|
||||
ai_task: __sdk::TableAppliedDiff<'r, AiTask>,
|
||||
ai_task_stage: __sdk::TableAppliedDiff<'r, AiTaskStage>,
|
||||
ai_text_chunk: __sdk::TableAppliedDiff<'r, AiTextChunk>,
|
||||
asset_entity_binding: __sdk::TableAppliedDiff<'r, AssetEntityBinding>,
|
||||
asset_object: __sdk::TableAppliedDiff<'r, AssetObject>,
|
||||
auth_identity: __sdk::TableAppliedDiff<'r, AuthIdentity>,
|
||||
auth_store_snapshot: __sdk::TableAppliedDiff<'r, AuthStoreSnapshot>,
|
||||
battle_state: __sdk::TableAppliedDiff<'r, BattleState>,
|
||||
big_fish_agent_message: __sdk::TableAppliedDiff<'r, BigFishAgentMessage>,
|
||||
big_fish_asset_slot: __sdk::TableAppliedDiff<'r, BigFishAssetSlot>,
|
||||
big_fish_creation_session: __sdk::TableAppliedDiff<'r, BigFishCreationSession>,
|
||||
big_fish_runtime_run: __sdk::TableAppliedDiff<'r, BigFishRuntimeRun>,
|
||||
chapter_progression: __sdk::TableAppliedDiff<'r, ChapterProgression>,
|
||||
custom_world_agent_message: __sdk::TableAppliedDiff<'r, CustomWorldAgentMessage>,
|
||||
custom_world_agent_operation: __sdk::TableAppliedDiff<'r, CustomWorldAgentOperation>,
|
||||
custom_world_agent_session: __sdk::TableAppliedDiff<'r, CustomWorldAgentSession>,
|
||||
custom_world_draft_card: __sdk::TableAppliedDiff<'r, CustomWorldDraftCard>,
|
||||
custom_world_gallery_entry: __sdk::TableAppliedDiff<'r, CustomWorldGalleryEntry>,
|
||||
custom_world_profile: __sdk::TableAppliedDiff<'r, CustomWorldProfile>,
|
||||
custom_world_session: __sdk::TableAppliedDiff<'r, CustomWorldSession>,
|
||||
inventory_slot: __sdk::TableAppliedDiff<'r, InventorySlot>,
|
||||
npc_state: __sdk::TableAppliedDiff<'r, NpcState>,
|
||||
player_progression: __sdk::TableAppliedDiff<'r, PlayerProgression>,
|
||||
profile_dashboard_state: __sdk::TableAppliedDiff<'r, ProfileDashboardState>,
|
||||
profile_invite_code: __sdk::TableAppliedDiff<'r, ProfileInviteCode>,
|
||||
profile_membership: __sdk::TableAppliedDiff<'r, ProfileMembership>,
|
||||
profile_played_world: __sdk::TableAppliedDiff<'r, ProfilePlayedWorld>,
|
||||
profile_recharge_order: __sdk::TableAppliedDiff<'r, ProfileRechargeOrder>,
|
||||
profile_referral_relation: __sdk::TableAppliedDiff<'r, ProfileReferralRelation>,
|
||||
profile_save_archive: __sdk::TableAppliedDiff<'r, ProfileSaveArchive>,
|
||||
profile_wallet_ledger: __sdk::TableAppliedDiff<'r, ProfileWalletLedger>,
|
||||
puzzle_agent_message: __sdk::TableAppliedDiff<'r, PuzzleAgentMessageRow>,
|
||||
puzzle_agent_session: __sdk::TableAppliedDiff<'r, PuzzleAgentSessionRow>,
|
||||
puzzle_runtime_run: __sdk::TableAppliedDiff<'r, PuzzleRuntimeRunRow>,
|
||||
puzzle_work_profile: __sdk::TableAppliedDiff<'r, PuzzleWorkProfileRow>,
|
||||
quest_log: __sdk::TableAppliedDiff<'r, QuestLog>,
|
||||
quest_record: __sdk::TableAppliedDiff<'r, QuestRecord>,
|
||||
refresh_session: __sdk::TableAppliedDiff<'r, RefreshSession>,
|
||||
runtime_setting: __sdk::TableAppliedDiff<'r, RuntimeSetting>,
|
||||
runtime_snapshot: __sdk::TableAppliedDiff<'r, RuntimeSnapshotRow>,
|
||||
story_event: __sdk::TableAppliedDiff<'r, StoryEvent>,
|
||||
story_session: __sdk::TableAppliedDiff<'r, StorySession>,
|
||||
treasure_record: __sdk::TableAppliedDiff<'r, TreasureRecord>,
|
||||
user_account: __sdk::TableAppliedDiff<'r, UserAccount>,
|
||||
user_browse_history: __sdk::TableAppliedDiff<'r, UserBrowseHistory>,
|
||||
__unused: std::marker::PhantomData<&'r ()>,
|
||||
}
|
||||
|
||||
@@ -1225,7 +1605,52 @@ impl __sdk::InModule for AppliedDiff<'_> {
|
||||
|
||||
impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
|
||||
fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks<RemoteModule>) {
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry, event);
|
||||
callbacks.invoke_table_row_callbacks::<AiResultReference>("ai_result_reference", &self.ai_result_reference, event);
|
||||
callbacks.invoke_table_row_callbacks::<AiTask>("ai_task", &self.ai_task, event);
|
||||
callbacks.invoke_table_row_callbacks::<AiTaskStage>("ai_task_stage", &self.ai_task_stage, event);
|
||||
callbacks.invoke_table_row_callbacks::<AiTextChunk>("ai_text_chunk", &self.ai_text_chunk, event);
|
||||
callbacks.invoke_table_row_callbacks::<AssetEntityBinding>("asset_entity_binding", &self.asset_entity_binding, event);
|
||||
callbacks.invoke_table_row_callbacks::<AssetObject>("asset_object", &self.asset_object, event);
|
||||
callbacks.invoke_table_row_callbacks::<AuthIdentity>("auth_identity", &self.auth_identity, event);
|
||||
callbacks.invoke_table_row_callbacks::<AuthStoreSnapshot>("auth_store_snapshot", &self.auth_store_snapshot, event);
|
||||
callbacks.invoke_table_row_callbacks::<BattleState>("battle_state", &self.battle_state, event);
|
||||
callbacks.invoke_table_row_callbacks::<BigFishAgentMessage>("big_fish_agent_message", &self.big_fish_agent_message, event);
|
||||
callbacks.invoke_table_row_callbacks::<BigFishAssetSlot>("big_fish_asset_slot", &self.big_fish_asset_slot, event);
|
||||
callbacks.invoke_table_row_callbacks::<BigFishCreationSession>("big_fish_creation_session", &self.big_fish_creation_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<BigFishRuntimeRun>("big_fish_runtime_run", &self.big_fish_runtime_run, event);
|
||||
callbacks.invoke_table_row_callbacks::<ChapterProgression>("chapter_progression", &self.chapter_progression, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldAgentMessage>("custom_world_agent_message", &self.custom_world_agent_message, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldAgentOperation>("custom_world_agent_operation", &self.custom_world_agent_operation, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldAgentSession>("custom_world_agent_session", &self.custom_world_agent_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldDraftCard>("custom_world_draft_card", &self.custom_world_draft_card, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldProfile>("custom_world_profile", &self.custom_world_profile, event);
|
||||
callbacks.invoke_table_row_callbacks::<CustomWorldSession>("custom_world_session", &self.custom_world_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<InventorySlot>("inventory_slot", &self.inventory_slot, event);
|
||||
callbacks.invoke_table_row_callbacks::<NpcState>("npc_state", &self.npc_state, event);
|
||||
callbacks.invoke_table_row_callbacks::<PlayerProgression>("player_progression", &self.player_progression, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileDashboardState>("profile_dashboard_state", &self.profile_dashboard_state, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileInviteCode>("profile_invite_code", &self.profile_invite_code, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileMembership>("profile_membership", &self.profile_membership, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfilePlayedWorld>("profile_played_world", &self.profile_played_world, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileRechargeOrder>("profile_recharge_order", &self.profile_recharge_order, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileReferralRelation>("profile_referral_relation", &self.profile_referral_relation, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileSaveArchive>("profile_save_archive", &self.profile_save_archive, event);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileWalletLedger>("profile_wallet_ledger", &self.profile_wallet_ledger, event);
|
||||
callbacks.invoke_table_row_callbacks::<PuzzleAgentMessageRow>("puzzle_agent_message", &self.puzzle_agent_message, event);
|
||||
callbacks.invoke_table_row_callbacks::<PuzzleAgentSessionRow>("puzzle_agent_session", &self.puzzle_agent_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<PuzzleRuntimeRunRow>("puzzle_runtime_run", &self.puzzle_runtime_run, event);
|
||||
callbacks.invoke_table_row_callbacks::<PuzzleWorkProfileRow>("puzzle_work_profile", &self.puzzle_work_profile, event);
|
||||
callbacks.invoke_table_row_callbacks::<QuestLog>("quest_log", &self.quest_log, event);
|
||||
callbacks.invoke_table_row_callbacks::<QuestRecord>("quest_record", &self.quest_record, event);
|
||||
callbacks.invoke_table_row_callbacks::<RefreshSession>("refresh_session", &self.refresh_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<RuntimeSetting>("runtime_setting", &self.runtime_setting, event);
|
||||
callbacks.invoke_table_row_callbacks::<RuntimeSnapshotRow>("runtime_snapshot", &self.runtime_snapshot, event);
|
||||
callbacks.invoke_table_row_callbacks::<StoryEvent>("story_event", &self.story_event, event);
|
||||
callbacks.invoke_table_row_callbacks::<StorySession>("story_session", &self.story_session, event);
|
||||
callbacks.invoke_table_row_callbacks::<TreasureRecord>("treasure_record", &self.treasure_record, event);
|
||||
callbacks.invoke_table_row_callbacks::<UserAccount>("user_account", &self.user_account, event);
|
||||
callbacks.invoke_table_row_callbacks::<UserBrowseHistory>("user_browse_history", &self.user_browse_history, event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1877,9 +2302,99 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
type QueryBuilder = __sdk::QueryBuilder;
|
||||
|
||||
fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {
|
||||
custom_world_gallery_entry_table::register_table(client_cache);
|
||||
ai_result_reference_table::register_table(client_cache);
|
||||
ai_task_table::register_table(client_cache);
|
||||
ai_task_stage_table::register_table(client_cache);
|
||||
ai_text_chunk_table::register_table(client_cache);
|
||||
asset_entity_binding_table::register_table(client_cache);
|
||||
asset_object_table::register_table(client_cache);
|
||||
auth_identity_table::register_table(client_cache);
|
||||
auth_store_snapshot_table::register_table(client_cache);
|
||||
battle_state_table::register_table(client_cache);
|
||||
big_fish_agent_message_table::register_table(client_cache);
|
||||
big_fish_asset_slot_table::register_table(client_cache);
|
||||
big_fish_creation_session_table::register_table(client_cache);
|
||||
big_fish_runtime_run_table::register_table(client_cache);
|
||||
chapter_progression_table::register_table(client_cache);
|
||||
custom_world_agent_message_table::register_table(client_cache);
|
||||
custom_world_agent_operation_table::register_table(client_cache);
|
||||
custom_world_agent_session_table::register_table(client_cache);
|
||||
custom_world_draft_card_table::register_table(client_cache);
|
||||
custom_world_gallery_entry_table::register_table(client_cache);
|
||||
custom_world_profile_table::register_table(client_cache);
|
||||
custom_world_session_table::register_table(client_cache);
|
||||
inventory_slot_table::register_table(client_cache);
|
||||
npc_state_table::register_table(client_cache);
|
||||
player_progression_table::register_table(client_cache);
|
||||
profile_dashboard_state_table::register_table(client_cache);
|
||||
profile_invite_code_table::register_table(client_cache);
|
||||
profile_membership_table::register_table(client_cache);
|
||||
profile_played_world_table::register_table(client_cache);
|
||||
profile_recharge_order_table::register_table(client_cache);
|
||||
profile_referral_relation_table::register_table(client_cache);
|
||||
profile_save_archive_table::register_table(client_cache);
|
||||
profile_wallet_ledger_table::register_table(client_cache);
|
||||
puzzle_agent_message_table::register_table(client_cache);
|
||||
puzzle_agent_session_table::register_table(client_cache);
|
||||
puzzle_runtime_run_table::register_table(client_cache);
|
||||
puzzle_work_profile_table::register_table(client_cache);
|
||||
quest_log_table::register_table(client_cache);
|
||||
quest_record_table::register_table(client_cache);
|
||||
refresh_session_table::register_table(client_cache);
|
||||
runtime_setting_table::register_table(client_cache);
|
||||
runtime_snapshot_table::register_table(client_cache);
|
||||
story_event_table::register_table(client_cache);
|
||||
story_session_table::register_table(client_cache);
|
||||
treasure_record_table::register_table(client_cache);
|
||||
user_account_table::register_table(client_cache);
|
||||
user_browse_history_table::register_table(client_cache);
|
||||
}
|
||||
const ALL_TABLE_NAMES: &'static [&'static str] = &[
|
||||
"custom_world_gallery_entry",
|
||||
"ai_result_reference",
|
||||
"ai_task",
|
||||
"ai_task_stage",
|
||||
"ai_text_chunk",
|
||||
"asset_entity_binding",
|
||||
"asset_object",
|
||||
"auth_identity",
|
||||
"auth_store_snapshot",
|
||||
"battle_state",
|
||||
"big_fish_agent_message",
|
||||
"big_fish_asset_slot",
|
||||
"big_fish_creation_session",
|
||||
"big_fish_runtime_run",
|
||||
"chapter_progression",
|
||||
"custom_world_agent_message",
|
||||
"custom_world_agent_operation",
|
||||
"custom_world_agent_session",
|
||||
"custom_world_draft_card",
|
||||
"custom_world_gallery_entry",
|
||||
"custom_world_profile",
|
||||
"custom_world_session",
|
||||
"inventory_slot",
|
||||
"npc_state",
|
||||
"player_progression",
|
||||
"profile_dashboard_state",
|
||||
"profile_invite_code",
|
||||
"profile_membership",
|
||||
"profile_played_world",
|
||||
"profile_recharge_order",
|
||||
"profile_referral_relation",
|
||||
"profile_save_archive",
|
||||
"profile_wallet_ledger",
|
||||
"puzzle_agent_message",
|
||||
"puzzle_agent_session",
|
||||
"puzzle_runtime_run",
|
||||
"puzzle_work_profile",
|
||||
"quest_log",
|
||||
"quest_record",
|
||||
"refresh_session",
|
||||
"runtime_setting",
|
||||
"runtime_snapshot",
|
||||
"story_event",
|
||||
"story_session",
|
||||
"treasure_record",
|
||||
"user_account",
|
||||
"user_browse_history",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
// 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::profile_invite_code_type::ProfileInviteCode;
|
||||
|
||||
/// Table handle for the table `profile_invite_code`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfileInviteCodeTableAccess::profile_invite_code`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profile_invite_code()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_invite_code().on_insert(...)`.
|
||||
pub struct ProfileInviteCodeTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<ProfileInviteCode>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profile_invite_code`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfileInviteCodeTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfileInviteCodeTableHandle`], which mediates access to the table `profile_invite_code`.
|
||||
fn profile_invite_code(&self) -> ProfileInviteCodeTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfileInviteCodeTableAccess for super::RemoteTables {
|
||||
fn profile_invite_code(&self) -> ProfileInviteCodeTableHandle<'_> {
|
||||
ProfileInviteCodeTableHandle {
|
||||
imp: self.imp.get_table::<ProfileInviteCode>("profile_invite_code"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileInviteCodeInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfileInviteCodeDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfileInviteCodeTableHandle<'ctx> {
|
||||
type Row = ProfileInviteCode;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 { self.imp.count() }
|
||||
fn iter(&self) -> impl Iterator<Item = ProfileInviteCode> + '_ { self.imp.iter() }
|
||||
|
||||
type InsertCallbackId = ProfileInviteCodeInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileInviteCodeInsertCallbackId {
|
||||
ProfileInviteCodeInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfileInviteCodeInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfileInviteCodeDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileInviteCodeDeleteCallbackId {
|
||||
ProfileInviteCodeDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfileInviteCodeDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileInviteCodeUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfileInviteCodeTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfileInviteCodeUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfileInviteCodeUpdateCallbackId {
|
||||
ProfileInviteCodeUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfileInviteCodeUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `user_id` unique index on the table `profile_invite_code`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileInviteCodeUserIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_invite_code().user_id().find(...)`.
|
||||
pub struct ProfileInviteCodeUserIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileInviteCode, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileInviteCodeTableHandle<'ctx> {
|
||||
/// Get a handle on the `user_id` unique index on the table `profile_invite_code`.
|
||||
pub fn user_id(&self) -> ProfileInviteCodeUserIdUnique<'ctx> {
|
||||
ProfileInviteCodeUserIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("user_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileInviteCodeUserIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `user_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileInviteCode> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `invite_code` unique index on the table `profile_invite_code`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileInviteCodeInviteCodeUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_invite_code().invite_code().find(...)`.
|
||||
pub struct ProfileInviteCodeInviteCodeUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileInviteCode, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileInviteCodeTableHandle<'ctx> {
|
||||
/// Get a handle on the `invite_code` unique index on the table `profile_invite_code`.
|
||||
pub fn invite_code(&self) -> ProfileInviteCodeInviteCodeUnique<'ctx> {
|
||||
ProfileInviteCodeInviteCodeUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("invite_code"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileInviteCodeInviteCodeUnique<'ctx> {
|
||||
/// Find the subscribed row whose `invite_code` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileInviteCode> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
|
||||
let _table = client_cache.get_or_make_table::<ProfileInviteCode>("profile_invite_code");
|
||||
_table.add_unique_constraint::<String>("user_id", |row| &row.user_id);
|
||||
_table.add_unique_constraint::<String>("invite_code", |row| &row.invite_code);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<ProfileInviteCode>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<ProfileInviteCode>",
|
||||
"TableUpdate",
|
||||
).with_cause(e).into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `ProfileInviteCode`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profile_invite_codeQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `ProfileInviteCode`.
|
||||
fn profile_invite_code(&self) -> __sdk::__query_builder::Table<ProfileInviteCode>;
|
||||
}
|
||||
|
||||
impl profile_invite_codeQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profile_invite_code(&self) -> __sdk::__query_builder::Table<ProfileInviteCode> {
|
||||
__sdk::__query_builder::Table::new("profile_invite_code")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 ProfileInviteCode {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for ProfileInviteCode {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `ProfileInviteCode`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileInviteCodeCols {
|
||||
pub user_id: __sdk::__query_builder::Col<ProfileInviteCode, String>,
|
||||
pub invite_code: __sdk::__query_builder::Col<ProfileInviteCode, String>,
|
||||
pub created_at: __sdk::__query_builder::Col<ProfileInviteCode, __sdk::Timestamp>,
|
||||
pub updated_at: __sdk::__query_builder::Col<ProfileInviteCode, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileInviteCode {
|
||||
type Cols = ProfileInviteCodeCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileInviteCodeCols {
|
||||
user_id: __sdk::__query_builder::Col::new(table_name, "user_id"),
|
||||
invite_code: __sdk::__query_builder::Col::new(table_name, "invite_code"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileInviteCode`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileInviteCodeIxCols {
|
||||
pub invite_code: __sdk::__query_builder::IxCol<ProfileInviteCode, String>,
|
||||
pub user_id: __sdk::__query_builder::IxCol<ProfileInviteCode, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileInviteCode {
|
||||
type IxCols = ProfileInviteCodeIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileInviteCodeIxCols {
|
||||
invite_code: __sdk::__query_builder::IxCol::new(table_name, "invite_code"),
|
||||
user_id: __sdk::__query_builder::IxCol::new(table_name, "user_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileInviteCode {}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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::profile_membership_type::ProfileMembership;
|
||||
use super::runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
|
||||
use super::runtime_profile_membership_tier_type::RuntimeProfileMembershipTier;
|
||||
|
||||
/// Table handle for the table `profile_membership`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfileMembershipTableAccess::profile_membership`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profile_membership()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_membership().on_insert(...)`.
|
||||
pub struct ProfileMembershipTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<ProfileMembership>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profile_membership`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfileMembershipTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfileMembershipTableHandle`], which mediates access to the table `profile_membership`.
|
||||
fn profile_membership(&self) -> ProfileMembershipTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfileMembershipTableAccess for super::RemoteTables {
|
||||
fn profile_membership(&self) -> ProfileMembershipTableHandle<'_> {
|
||||
ProfileMembershipTableHandle {
|
||||
imp: self.imp.get_table::<ProfileMembership>("profile_membership"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileMembershipInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfileMembershipDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfileMembershipTableHandle<'ctx> {
|
||||
type Row = ProfileMembership;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 { self.imp.count() }
|
||||
fn iter(&self) -> impl Iterator<Item = ProfileMembership> + '_ { self.imp.iter() }
|
||||
|
||||
type InsertCallbackId = ProfileMembershipInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileMembershipInsertCallbackId {
|
||||
ProfileMembershipInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfileMembershipInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfileMembershipDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileMembershipDeleteCallbackId {
|
||||
ProfileMembershipDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfileMembershipDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileMembershipUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfileMembershipTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfileMembershipUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfileMembershipUpdateCallbackId {
|
||||
ProfileMembershipUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfileMembershipUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `user_id` unique index on the table `profile_membership`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileMembershipUserIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_membership().user_id().find(...)`.
|
||||
pub struct ProfileMembershipUserIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileMembership, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileMembershipTableHandle<'ctx> {
|
||||
/// Get a handle on the `user_id` unique index on the table `profile_membership`.
|
||||
pub fn user_id(&self) -> ProfileMembershipUserIdUnique<'ctx> {
|
||||
ProfileMembershipUserIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("user_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileMembershipUserIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `user_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileMembership> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
|
||||
let _table = client_cache.get_or_make_table::<ProfileMembership>("profile_membership");
|
||||
_table.add_unique_constraint::<String>("user_id", |row| &row.user_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<ProfileMembership>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<ProfileMembership>",
|
||||
"TableUpdate",
|
||||
).with_cause(e).into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `ProfileMembership`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profile_membershipQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `ProfileMembership`.
|
||||
fn profile_membership(&self) -> __sdk::__query_builder::Table<ProfileMembership>;
|
||||
}
|
||||
|
||||
impl profile_membershipQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profile_membership(&self) -> __sdk::__query_builder::Table<ProfileMembership> {
|
||||
__sdk::__query_builder::Table::new("profile_membership")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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::profile_recharge_order_type::ProfileRechargeOrder;
|
||||
use super::runtime_profile_recharge_product_kind_type::RuntimeProfileRechargeProductKind;
|
||||
use super::runtime_profile_recharge_order_status_type::RuntimeProfileRechargeOrderStatus;
|
||||
|
||||
/// Table handle for the table `profile_recharge_order`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfileRechargeOrderTableAccess::profile_recharge_order`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profile_recharge_order()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_recharge_order().on_insert(...)`.
|
||||
pub struct ProfileRechargeOrderTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<ProfileRechargeOrder>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profile_recharge_order`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfileRechargeOrderTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfileRechargeOrderTableHandle`], which mediates access to the table `profile_recharge_order`.
|
||||
fn profile_recharge_order(&self) -> ProfileRechargeOrderTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfileRechargeOrderTableAccess for super::RemoteTables {
|
||||
fn profile_recharge_order(&self) -> ProfileRechargeOrderTableHandle<'_> {
|
||||
ProfileRechargeOrderTableHandle {
|
||||
imp: self.imp.get_table::<ProfileRechargeOrder>("profile_recharge_order"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileRechargeOrderInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfileRechargeOrderDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfileRechargeOrderTableHandle<'ctx> {
|
||||
type Row = ProfileRechargeOrder;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 { self.imp.count() }
|
||||
fn iter(&self) -> impl Iterator<Item = ProfileRechargeOrder> + '_ { self.imp.iter() }
|
||||
|
||||
type InsertCallbackId = ProfileRechargeOrderInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileRechargeOrderInsertCallbackId {
|
||||
ProfileRechargeOrderInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfileRechargeOrderInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfileRechargeOrderDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileRechargeOrderDeleteCallbackId {
|
||||
ProfileRechargeOrderDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfileRechargeOrderDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileRechargeOrderUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfileRechargeOrderTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfileRechargeOrderUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfileRechargeOrderUpdateCallbackId {
|
||||
ProfileRechargeOrderUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfileRechargeOrderUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `order_id` unique index on the table `profile_recharge_order`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileRechargeOrderOrderIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_recharge_order().order_id().find(...)`.
|
||||
pub struct ProfileRechargeOrderOrderIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileRechargeOrder, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileRechargeOrderTableHandle<'ctx> {
|
||||
/// Get a handle on the `order_id` unique index on the table `profile_recharge_order`.
|
||||
pub fn order_id(&self) -> ProfileRechargeOrderOrderIdUnique<'ctx> {
|
||||
ProfileRechargeOrderOrderIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("order_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileRechargeOrderOrderIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `order_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileRechargeOrder> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
|
||||
let _table = client_cache.get_or_make_table::<ProfileRechargeOrder>("profile_recharge_order");
|
||||
_table.add_unique_constraint::<String>("order_id", |row| &row.order_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<ProfileRechargeOrder>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<ProfileRechargeOrder>",
|
||||
"TableUpdate",
|
||||
).with_cause(e).into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `ProfileRechargeOrder`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profile_recharge_orderQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `ProfileRechargeOrder`.
|
||||
fn profile_recharge_order(&self) -> __sdk::__query_builder::Table<ProfileRechargeOrder>;
|
||||
}
|
||||
|
||||
impl profile_recharge_orderQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profile_recharge_order(&self) -> __sdk::__query_builder::Table<ProfileRechargeOrder> {
|
||||
__sdk::__query_builder::Table::new("profile_recharge_order")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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::profile_referral_relation_type::ProfileReferralRelation;
|
||||
|
||||
/// Table handle for the table `profile_referral_relation`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfileReferralRelationTableAccess::profile_referral_relation`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profile_referral_relation()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_referral_relation().on_insert(...)`.
|
||||
pub struct ProfileReferralRelationTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<ProfileReferralRelation>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profile_referral_relation`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfileReferralRelationTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfileReferralRelationTableHandle`], which mediates access to the table `profile_referral_relation`.
|
||||
fn profile_referral_relation(&self) -> ProfileReferralRelationTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfileReferralRelationTableAccess for super::RemoteTables {
|
||||
fn profile_referral_relation(&self) -> ProfileReferralRelationTableHandle<'_> {
|
||||
ProfileReferralRelationTableHandle {
|
||||
imp: self.imp.get_table::<ProfileReferralRelation>("profile_referral_relation"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileReferralRelationInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfileReferralRelationDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfileReferralRelationTableHandle<'ctx> {
|
||||
type Row = ProfileReferralRelation;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 { self.imp.count() }
|
||||
fn iter(&self) -> impl Iterator<Item = ProfileReferralRelation> + '_ { self.imp.iter() }
|
||||
|
||||
type InsertCallbackId = ProfileReferralRelationInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileReferralRelationInsertCallbackId {
|
||||
ProfileReferralRelationInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfileReferralRelationInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfileReferralRelationDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileReferralRelationDeleteCallbackId {
|
||||
ProfileReferralRelationDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfileReferralRelationDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileReferralRelationUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfileReferralRelationTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfileReferralRelationUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfileReferralRelationUpdateCallbackId {
|
||||
ProfileReferralRelationUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfileReferralRelationUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `invitee_user_id` unique index on the table `profile_referral_relation`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileReferralRelationInviteeUserIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_referral_relation().invitee_user_id().find(...)`.
|
||||
pub struct ProfileReferralRelationInviteeUserIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileReferralRelation, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileReferralRelationTableHandle<'ctx> {
|
||||
/// Get a handle on the `invitee_user_id` unique index on the table `profile_referral_relation`.
|
||||
pub fn invitee_user_id(&self) -> ProfileReferralRelationInviteeUserIdUnique<'ctx> {
|
||||
ProfileReferralRelationInviteeUserIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("invitee_user_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileReferralRelationInviteeUserIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `invitee_user_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileReferralRelation> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
|
||||
let _table = client_cache.get_or_make_table::<ProfileReferralRelation>("profile_referral_relation");
|
||||
_table.add_unique_constraint::<String>("invitee_user_id", |row| &row.invitee_user_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<ProfileReferralRelation>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<ProfileReferralRelation>",
|
||||
"TableUpdate",
|
||||
).with_cause(e).into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `ProfileReferralRelation`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profile_referral_relationQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `ProfileReferralRelation`.
|
||||
fn profile_referral_relation(&self) -> __sdk::__query_builder::Table<ProfileReferralRelation>;
|
||||
}
|
||||
|
||||
impl profile_referral_relationQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profile_referral_relation(&self) -> __sdk::__query_builder::Table<ProfileReferralRelation> {
|
||||
__sdk::__query_builder::Table::new("profile_referral_relation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 ProfileReferralRelation {
|
||||
pub invitee_user_id: String,
|
||||
pub inviter_user_id: String,
|
||||
pub invite_code: String,
|
||||
pub inviter_reward_granted: bool,
|
||||
pub invitee_reward_granted: bool,
|
||||
pub bound_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for ProfileReferralRelation {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `ProfileReferralRelation`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileReferralRelationCols {
|
||||
pub invitee_user_id: __sdk::__query_builder::Col<ProfileReferralRelation, String>,
|
||||
pub inviter_user_id: __sdk::__query_builder::Col<ProfileReferralRelation, String>,
|
||||
pub invite_code: __sdk::__query_builder::Col<ProfileReferralRelation, String>,
|
||||
pub inviter_reward_granted: __sdk::__query_builder::Col<ProfileReferralRelation, bool>,
|
||||
pub invitee_reward_granted: __sdk::__query_builder::Col<ProfileReferralRelation, bool>,
|
||||
pub bound_at: __sdk::__query_builder::Col<ProfileReferralRelation, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileReferralRelation {
|
||||
type Cols = ProfileReferralRelationCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileReferralRelationCols {
|
||||
invitee_user_id: __sdk::__query_builder::Col::new(table_name, "invitee_user_id"),
|
||||
inviter_user_id: __sdk::__query_builder::Col::new(table_name, "inviter_user_id"),
|
||||
invite_code: __sdk::__query_builder::Col::new(table_name, "invite_code"),
|
||||
inviter_reward_granted: __sdk::__query_builder::Col::new(table_name, "inviter_reward_granted"),
|
||||
invitee_reward_granted: __sdk::__query_builder::Col::new(table_name, "invitee_reward_granted"),
|
||||
bound_at: __sdk::__query_builder::Col::new(table_name, "bound_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileReferralRelation`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileReferralRelationIxCols {
|
||||
pub invitee_user_id: __sdk::__query_builder::IxCol<ProfileReferralRelation, String>,
|
||||
pub inviter_user_id: __sdk::__query_builder::IxCol<ProfileReferralRelation, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileReferralRelation {
|
||||
type IxCols = ProfileReferralRelationIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileReferralRelationIxCols {
|
||||
invitee_user_id: __sdk::__query_builder::IxCol::new(table_name, "invitee_user_id"),
|
||||
inviter_user_id: __sdk::__query_builder::IxCol::new(table_name, "inviter_user_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileReferralRelation {}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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_referral_redeem_input_type::RuntimeReferralRedeemInput;
|
||||
use super::runtime_referral_redeem_procedure_result_type::RuntimeReferralRedeemProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct RedeemProfileReferralInviteCodeArgs {
|
||||
pub input: RuntimeReferralRedeemInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RedeemProfileReferralInviteCodeArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `redeem_profile_referral_invite_code`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait redeem_profile_referral_invite_code {
|
||||
fn redeem_profile_referral_invite_code(&self, input: RuntimeReferralRedeemInput,
|
||||
) {
|
||||
self.redeem_profile_referral_invite_code_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn redeem_profile_referral_invite_code_then(
|
||||
&self,
|
||||
input: RuntimeReferralRedeemInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeReferralRedeemProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl redeem_profile_referral_invite_code for super::RemoteProcedures {
|
||||
fn redeem_profile_referral_invite_code_then(
|
||||
&self,
|
||||
input: RuntimeReferralRedeemInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeReferralRedeemProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, RuntimeReferralRedeemProcedureResult>(
|
||||
"redeem_profile_referral_invite_code",
|
||||
RedeemProfileReferralInviteCodeArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 RuntimeReferralInviteCenterGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralInviteCenterGetInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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_referral_invite_center_snapshot_type::RuntimeReferralInviteCenterSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeReferralInviteCenterProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<RuntimeReferralInviteCenterSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralInviteCenterProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 RuntimeReferralInviteCenterSnapshot {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub invite_link_path: String,
|
||||
pub invited_count: u32,
|
||||
pub rewarded_invite_count: u32,
|
||||
pub today_inviter_reward_count: u32,
|
||||
pub today_inviter_reward_remaining: u32,
|
||||
pub reward_points: u64,
|
||||
pub has_redeemed_code: bool,
|
||||
pub bound_inviter_user_id: Option::<String>,
|
||||
pub bound_at_micros: Option::<i64>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralInviteCenterSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 RuntimeReferralRedeemInput {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralRedeemInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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_referral_redeem_snapshot_type::RuntimeReferralRedeemSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeReferralRedeemProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<RuntimeReferralRedeemSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralRedeemProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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_referral_invite_center_snapshot_type::RuntimeReferralInviteCenterSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeReferralRedeemSnapshot {
|
||||
pub center: RuntimeReferralInviteCenterSnapshot,
|
||||
pub invitee_reward_granted: bool,
|
||||
pub inviter_reward_granted: bool,
|
||||
pub invitee_balance_after: u64,
|
||||
pub inviter_balance_after: u64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for RuntimeReferralRedeemSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,51 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_referral_invite_center(
|
||||
&self,
|
||||
user_id: String,
|
||||
) -> Result<RuntimeReferralInviteCenterRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_referral_invite_center_get_input(user_id)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.get_profile_referral_invite_center_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
|
||||
.and_then(map_runtime_referral_invite_center_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn redeem_profile_referral_invite_code(
|
||||
&self,
|
||||
user_id: String,
|
||||
invite_code: String,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeReferralRedeemRecord, SpacetimeClientError> {
|
||||
let procedure_input =
|
||||
build_runtime_referral_redeem_input(user_id, invite_code, updated_at_micros)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.redeem_profile_referral_invite_code_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
|
||||
.and_then(map_runtime_referral_redeem_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_play_stats(
|
||||
&self,
|
||||
user_id: String,
|
||||
|
||||
@@ -2653,6 +2653,7 @@ fn summarize_publish_gate_from_json(
|
||||
&[
|
||||
"worldHook",
|
||||
"creatorIntent.worldHook",
|
||||
"anchorContent.worldPromise",
|
||||
"anchorContent.worldPromise.hook",
|
||||
"settingText",
|
||||
],
|
||||
@@ -2670,6 +2671,7 @@ fn summarize_publish_gate_from_json(
|
||||
&[
|
||||
"playerPremise",
|
||||
"creatorIntent.playerPremise",
|
||||
"anchorContent.playerEntryPoint",
|
||||
"anchorContent.playerEntryPoint.openingIdentity",
|
||||
"anchorContent.playerEntryPoint.openingProblem",
|
||||
"anchorContent.playerEntryPoint.entryMotivation",
|
||||
|
||||
@@ -3766,6 +3766,7 @@ fn summarize_publish_gate_from_json(
|
||||
&[
|
||||
"worldHook",
|
||||
"creatorIntent.worldHook",
|
||||
"anchorContent.worldPromise",
|
||||
"anchorContent.worldPromise.hook",
|
||||
"settingText",
|
||||
],
|
||||
@@ -3783,6 +3784,7 @@ fn summarize_publish_gate_from_json(
|
||||
&[
|
||||
"playerPremise",
|
||||
"creatorIntent.playerPremise",
|
||||
"anchorContent.playerEntryPoint",
|
||||
"anchorContent.playerEntryPoint.openingIdentity",
|
||||
"anchorContent.playerEntryPoint.openingProblem",
|
||||
"anchorContent.playerEntryPoint.entryMotivation",
|
||||
|
||||
@@ -28,6 +28,34 @@ pub struct ProfileWalletLedger {
|
||||
pub(crate) created_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = profile_invite_code)]
|
||||
pub struct ProfileInviteCode {
|
||||
#[primary_key]
|
||||
pub(crate) user_id: String,
|
||||
#[unique]
|
||||
pub(crate) invite_code: String,
|
||||
pub(crate) created_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = profile_referral_relation,
|
||||
index(accessor = by_profile_referral_inviter_user_id, btree(columns = [inviter_user_id])),
|
||||
index(
|
||||
accessor = by_profile_referral_inviter_bound_at,
|
||||
btree(columns = [inviter_user_id, bound_at])
|
||||
)
|
||||
)]
|
||||
pub struct ProfileReferralRelation {
|
||||
#[primary_key]
|
||||
pub(crate) invitee_user_id: String,
|
||||
pub(crate) inviter_user_id: String,
|
||||
pub(crate) invite_code: String,
|
||||
pub(crate) inviter_reward_granted: bool,
|
||||
pub(crate) invitee_reward_granted: bool,
|
||||
pub(crate) bound_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = profile_played_world,
|
||||
index(accessor = by_profile_played_world_user_id, btree(columns = [user_id])),
|
||||
@@ -274,6 +302,46 @@ pub fn create_profile_recharge_order_and_return(
|
||||
}
|
||||
}
|
||||
|
||||
// 邀请中心会在首次打开时为账号创建稳定邀请码,前端只展示这里返回的后端状态。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_profile_referral_invite_center(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeReferralInviteCenterGetInput,
|
||||
) -> RuntimeReferralInviteCenterProcedureResult {
|
||||
match ctx.try_with_tx(|tx| get_profile_referral_invite_center_snapshot(tx, input.clone())) {
|
||||
Ok(record) => RuntimeReferralInviteCenterProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeReferralInviteCenterProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 填码绑定、每日邀请者奖励上限和双方积分发放都在同一事务内完成。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn redeem_profile_referral_invite_code(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeReferralRedeemInput,
|
||||
) -> RuntimeReferralRedeemProcedureResult {
|
||||
match ctx.try_with_tx(|tx| redeem_profile_referral_invite_code_record(tx, input.clone())) {
|
||||
Ok(record) => RuntimeReferralRedeemProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeReferralRedeemProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn list_profile_save_archive_rows(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileSaveArchiveListInput,
|
||||
@@ -948,6 +1016,215 @@ fn create_profile_recharge_order_record(
|
||||
))
|
||||
}
|
||||
|
||||
fn get_profile_referral_invite_center_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeReferralInviteCenterGetInput,
|
||||
) -> Result<RuntimeReferralInviteCenterSnapshot, String> {
|
||||
let validated_input = build_runtime_referral_invite_center_get_input(input.user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(build_profile_referral_invite_center_snapshot(
|
||||
ctx,
|
||||
&validated_input.user_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn redeem_profile_referral_invite_code_record(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeReferralRedeemInput,
|
||||
) -> Result<RuntimeReferralRedeemSnapshot, String> {
|
||||
let validated_input = build_runtime_referral_redeem_input(
|
||||
input.user_id,
|
||||
input.invite_code,
|
||||
input.updated_at_micros,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let bound_at = Timestamp::from_micros_since_unix_epoch(validated_input.updated_at_micros);
|
||||
let invitee_user_id = validated_input.user_id;
|
||||
let invite_code = validated_input.invite_code;
|
||||
|
||||
if ctx
|
||||
.db
|
||||
.profile_referral_relation()
|
||||
.invitee_user_id()
|
||||
.find(&invitee_user_id)
|
||||
.is_some()
|
||||
{
|
||||
return Err("每个用户最多只能填写一个邀请码".to_string());
|
||||
}
|
||||
|
||||
let inviter_code = ctx
|
||||
.db
|
||||
.profile_invite_code()
|
||||
.invite_code()
|
||||
.find(&invite_code)
|
||||
.ok_or_else(|| "邀请码不存在".to_string())?;
|
||||
if inviter_code.user_id == invitee_user_id {
|
||||
return Err("不能填写自己的邀请码".to_string());
|
||||
}
|
||||
|
||||
let invitee_balance_after = apply_profile_wallet_delta(
|
||||
ctx,
|
||||
&invitee_user_id,
|
||||
PROFILE_REFERRAL_REWARD_POINTS,
|
||||
RuntimeProfileWalletLedgerSourceType::InviteInviteeReward,
|
||||
&format!(
|
||||
"invitee:{}:{}",
|
||||
invitee_user_id, validated_input.updated_at_micros
|
||||
),
|
||||
bound_at,
|
||||
)?;
|
||||
let today_inviter_reward_count =
|
||||
count_today_profile_referral_inviter_rewards(ctx, &inviter_code.user_id, bound_at);
|
||||
let inviter_reward_granted =
|
||||
today_inviter_reward_count < PROFILE_REFERRAL_DAILY_INVITER_REWARD_LIMIT;
|
||||
let inviter_balance_after = if inviter_reward_granted {
|
||||
apply_profile_wallet_delta(
|
||||
ctx,
|
||||
&inviter_code.user_id,
|
||||
PROFILE_REFERRAL_REWARD_POINTS,
|
||||
RuntimeProfileWalletLedgerSourceType::InviteInviterReward,
|
||||
&format!(
|
||||
"inviter:{}:{}",
|
||||
inviter_code.user_id, validated_input.updated_at_micros
|
||||
),
|
||||
bound_at,
|
||||
)?
|
||||
} else {
|
||||
profile_wallet_balance(ctx, &inviter_code.user_id)
|
||||
};
|
||||
|
||||
ctx.db
|
||||
.profile_referral_relation()
|
||||
.insert(ProfileReferralRelation {
|
||||
invitee_user_id: invitee_user_id.clone(),
|
||||
inviter_user_id: inviter_code.user_id,
|
||||
invite_code,
|
||||
inviter_reward_granted,
|
||||
invitee_reward_granted: true,
|
||||
bound_at,
|
||||
});
|
||||
|
||||
Ok(RuntimeReferralRedeemSnapshot {
|
||||
center: build_profile_referral_invite_center_snapshot(ctx, &invitee_user_id),
|
||||
invitee_reward_granted: true,
|
||||
inviter_reward_granted,
|
||||
invitee_balance_after,
|
||||
inviter_balance_after,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_profile_referral_invite_center_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
) -> RuntimeReferralInviteCenterSnapshot {
|
||||
let code = ensure_profile_invite_code(ctx, user_id);
|
||||
let today_inviter_reward_count =
|
||||
count_today_profile_referral_inviter_rewards(ctx, user_id, ctx.timestamp);
|
||||
let invited_count = ctx
|
||||
.db
|
||||
.profile_referral_relation()
|
||||
.iter()
|
||||
.filter(|row| row.inviter_user_id == user_id)
|
||||
.count() as u32;
|
||||
let rewarded_invite_count = ctx
|
||||
.db
|
||||
.profile_referral_relation()
|
||||
.iter()
|
||||
.filter(|row| row.inviter_user_id == user_id && row.inviter_reward_granted)
|
||||
.count() as u32;
|
||||
let bound_relation = ctx
|
||||
.db
|
||||
.profile_referral_relation()
|
||||
.invitee_user_id()
|
||||
.find(&user_id.to_string());
|
||||
|
||||
RuntimeReferralInviteCenterSnapshot {
|
||||
user_id: user_id.to_string(),
|
||||
invite_code: code.invite_code.clone(),
|
||||
invite_link_path: format!("/?inviteCode={}", code.invite_code),
|
||||
invited_count,
|
||||
rewarded_invite_count,
|
||||
today_inviter_reward_count,
|
||||
today_inviter_reward_remaining: PROFILE_REFERRAL_DAILY_INVITER_REWARD_LIMIT
|
||||
.saturating_sub(today_inviter_reward_count),
|
||||
reward_points: PROFILE_REFERRAL_REWARD_POINTS,
|
||||
has_redeemed_code: bound_relation.is_some(),
|
||||
bound_inviter_user_id: bound_relation
|
||||
.as_ref()
|
||||
.map(|relation| relation.inviter_user_id.clone()),
|
||||
bound_at_micros: bound_relation
|
||||
.as_ref()
|
||||
.map(|relation| relation.bound_at.to_micros_since_unix_epoch()),
|
||||
updated_at_micros: code.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_profile_invite_code(ctx: &ReducerContext, user_id: &str) -> ProfileInviteCode {
|
||||
if let Some(row) = ctx
|
||||
.db
|
||||
.profile_invite_code()
|
||||
.user_id()
|
||||
.find(&user_id.to_string())
|
||||
{
|
||||
return row;
|
||||
}
|
||||
|
||||
let mut invite_code = build_profile_invite_code(user_id, 0);
|
||||
let mut salt = 1;
|
||||
while ctx
|
||||
.db
|
||||
.profile_invite_code()
|
||||
.invite_code()
|
||||
.find(&invite_code)
|
||||
.is_some()
|
||||
{
|
||||
invite_code = build_profile_invite_code(user_id, salt);
|
||||
salt += 1;
|
||||
}
|
||||
|
||||
ctx.db.profile_invite_code().insert(ProfileInviteCode {
|
||||
user_id: user_id.to_string(),
|
||||
invite_code,
|
||||
created_at: ctx.timestamp,
|
||||
updated_at: ctx.timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_profile_invite_code(user_id: &str, salt: u32) -> String {
|
||||
let mut hash = 14_695_981_039_346_656_037u64;
|
||||
for byte in user_id.as_bytes().iter().copied().chain(salt.to_le_bytes()) {
|
||||
hash ^= byte as u64;
|
||||
hash = hash.wrapping_mul(1_099_511_628_211);
|
||||
}
|
||||
format!("SY{:08X}", hash as u32)
|
||||
}
|
||||
|
||||
fn count_today_profile_referral_inviter_rewards(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
now: Timestamp,
|
||||
) -> u32 {
|
||||
let day_start_micros = (now.to_micros_since_unix_epoch() / 86_400_000_000) * 86_400_000_000;
|
||||
ctx.db
|
||||
.profile_wallet_ledger()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.user_id == user_id
|
||||
&& row.source_type == RuntimeProfileWalletLedgerSourceType::InviteInviterReward
|
||||
&& row.created_at.to_micros_since_unix_epoch() >= day_start_micros
|
||||
})
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
fn profile_wallet_balance(ctx: &ReducerContext, user_id: &str) -> u64 {
|
||||
ctx.db
|
||||
.profile_dashboard_state()
|
||||
.user_id()
|
||||
.find(&user_id.to_string())
|
||||
.map(|row| row.wallet_balance)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn build_profile_recharge_center_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user