Merge branch 'master' of http://82.157.175.59:3000/GenarrativeAI/Genarrative
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-08 22:12:10 +08:00
38 changed files with 1515 additions and 135 deletions

View File

@@ -185,6 +185,7 @@ macro_rules! migration_tables {
public_work_like,
profile_membership,
profile_recharge_order,
profile_feedback_submission,
profile_save_archive,
player_progression,
chapter_progression,

View File

@@ -351,6 +351,27 @@ pub struct ProfileRechargeOrder {
pub(crate) membership_expires_at: Option<Timestamp>,
}
#[spacetimedb::table(
accessor = profile_feedback_submission,
index(accessor = by_profile_feedback_user_id, btree(columns = [user_id])),
index(
accessor = by_profile_feedback_user_created_at,
btree(columns = [user_id, created_at])
)
)]
pub struct ProfileFeedbackSubmission {
#[primary_key]
pub(crate) feedback_id: String,
pub(crate) user_id: String,
pub(crate) description: String,
pub(crate) contact_phone: Option<String>,
// 中文注释:首版凭证以 Data URL 写入私有表HTTP 回包只返回元数据,后续迁 OSS 不改变外部契约。
pub(crate) evidence_json: String,
pub(crate) status: RuntimeProfileFeedbackStatus,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
}
#[spacetimedb::table(
accessor = profile_save_archive,
index(accessor = by_profile_save_archive_user_id, btree(columns = [user_id])),
@@ -749,6 +770,25 @@ pub fn create_profile_recharge_order_and_return(
}
}
#[spacetimedb::procedure]
pub fn submit_profile_feedback_and_return(
ctx: &mut ProcedureContext,
input: RuntimeProfileFeedbackSubmissionInput,
) -> RuntimeProfileFeedbackSubmissionProcedureResult {
match ctx.try_with_tx(|tx| submit_profile_feedback_record(tx, input.clone())) {
Ok(record) => RuntimeProfileFeedbackSubmissionProcedureResult {
ok: true,
record: Some(record),
error_message: None,
},
Err(message) => RuntimeProfileFeedbackSubmissionProcedureResult {
ok: false,
record: None,
error_message: Some(message),
},
}
}
// 邀请中心会在首次打开时为账号创建稳定邀请码,前端只展示这里返回的后端状态。
#[spacetimedb::procedure]
pub fn get_profile_referral_invite_center(
@@ -1906,6 +1946,47 @@ fn create_profile_recharge_order_record(
))
}
fn submit_profile_feedback_record(
ctx: &ReducerContext,
input: RuntimeProfileFeedbackSubmissionInput,
) -> Result<RuntimeProfileFeedbackSubmissionSnapshot, String> {
let validated_input = build_runtime_profile_feedback_submission_input(
input.user_id,
input.description,
input.contact_phone,
input.evidence_items,
input.created_at_micros,
)
.map_err(|error| error.to_string())?;
let created_at = Timestamp::from_micros_since_unix_epoch(validated_input.created_at_micros);
let feedback_id = build_runtime_profile_feedback_submission_id(
&validated_input.user_id,
validated_input.created_at_micros,
);
let evidence_json = serde_json::to_string(&validated_input.evidence_items)
.map_err(|error| format!("反馈凭证序列化失败: {error}"))?;
let row = ProfileFeedbackSubmission {
feedback_id: feedback_id.clone(),
user_id: validated_input.user_id,
description: validated_input.description,
contact_phone: validated_input.contact_phone,
evidence_json,
status: RuntimeProfileFeedbackStatus::Open,
created_at,
updated_at: created_at,
};
ctx.db.profile_feedback_submission().insert(row);
let latest = ctx
.db
.profile_feedback_submission()
.feedback_id()
.find(&feedback_id)
.ok_or_else(|| "profile_feedback_submission 写入后未能读取".to_string())?;
Ok(build_profile_feedback_submission_snapshot_from_row(&latest))
}
fn get_profile_referral_invite_center_snapshot(
ctx: &ReducerContext,
input: RuntimeReferralInviteCenterGetInput,
@@ -3440,6 +3521,21 @@ fn build_profile_recharge_order_snapshot_from_row(
}
}
fn build_profile_feedback_submission_snapshot_from_row(
row: &ProfileFeedbackSubmission,
) -> RuntimeProfileFeedbackSubmissionSnapshot {
RuntimeProfileFeedbackSubmissionSnapshot {
feedback_id: row.feedback_id.clone(),
user_id: row.user_id.clone(),
description: row.description.clone(),
contact_phone: row.contact_phone.clone(),
evidence_json: row.evidence_json.clone(),
status: row.status,
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
}
}
fn build_profile_played_world_snapshot_from_row(
row: &ProfilePlayedWorld,
) -> RuntimeProfilePlayedWorldSnapshot {