Update spacetime-client bindings and frontend
Large update across server and web clients: regenerated/added many spacetime-client module bindings and input types (including new delete/work_delete input types and numerous procedure/reducer files), updates to server-rs API modules (bark_battle, jump_hop, wooden_fish, auth, module-runtime and shared contracts), and fixes in module-runtime behavior and domain logic. Frontend changes include new/updated components and tests (creative audio helpers, bark-battle/jump-hop/wooden-fish clients and views, unified generation pages, RPG entry views, and runtime shells), plus CSS and service updates. Documentation and operational notes updated (.hermes pitfalls and multiple PRD/docs) to cover daily-task refresh, banner asset fallback, recommend-key bug, and other platform behaviors. Tests and verification steps added/updated alongside these changes.
This commit is contained in:
@@ -75,6 +75,17 @@ pub fn publish_bark_battle_work(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn delete_bark_battle_work(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: BarkBattleWorkDeleteInput,
|
||||
) -> BarkBattleProcedureResult {
|
||||
match ctx.try_with_tx(|tx| delete_bark_battle_work_tx(tx, input.clone())) {
|
||||
Ok(()) => bark_battle_empty_result(),
|
||||
Err(error) => bark_battle_error_result(error),
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_bark_battle_runtime_config(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -286,6 +297,111 @@ fn publish_bark_battle_work_tx(
|
||||
Ok(runtime_config_snapshot(&published))
|
||||
}
|
||||
|
||||
fn delete_bark_battle_work_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: BarkBattleWorkDeleteInput,
|
||||
) -> Result<(), String> {
|
||||
require_non_empty(&input.work_id, "bark_battle work_id")?;
|
||||
require_non_empty(&input.owner_user_id, "bark_battle owner_user_id")?;
|
||||
let drafts = ctx
|
||||
.db
|
||||
.bark_battle_draft_config()
|
||||
.by_bark_battle_draft_work_id()
|
||||
.filter(input.work_id.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let published = ctx
|
||||
.db
|
||||
.bark_battle_published_config()
|
||||
.work_id()
|
||||
.find(&input.work_id);
|
||||
if drafts.is_empty() && published.is_none() {
|
||||
return Err("bark_battle work 不存在".to_string());
|
||||
}
|
||||
if drafts
|
||||
.iter()
|
||||
.any(|draft| draft.owner_user_id != input.owner_user_id)
|
||||
|| published
|
||||
.as_ref()
|
||||
.is_some_and(|row| row.owner_user_id != input.owner_user_id)
|
||||
{
|
||||
return Err("bark_battle work owner 不匹配".to_string());
|
||||
}
|
||||
|
||||
for draft in drafts {
|
||||
ctx.db
|
||||
.bark_battle_draft_config()
|
||||
.draft_id()
|
||||
.delete(&draft.draft_id);
|
||||
}
|
||||
if let Some(published) = published {
|
||||
ctx.db
|
||||
.bark_battle_published_config()
|
||||
.work_id()
|
||||
.delete(&published.work_id);
|
||||
}
|
||||
for run in ctx
|
||||
.db
|
||||
.bark_battle_runtime_run()
|
||||
.by_bark_battle_run_work_id()
|
||||
.filter(input.work_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.bark_battle_runtime_run()
|
||||
.run_id()
|
||||
.delete(&run.run_id);
|
||||
}
|
||||
for score in ctx
|
||||
.db
|
||||
.bark_battle_score_record()
|
||||
.by_bark_battle_score_work_id()
|
||||
.filter(input.work_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.bark_battle_score_record()
|
||||
.score_id()
|
||||
.delete(&score.score_id);
|
||||
}
|
||||
for entry in ctx
|
||||
.db
|
||||
.bark_battle_leaderboard_entry()
|
||||
.iter()
|
||||
.filter(|entry| entry.work_id == input.work_id)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.bark_battle_leaderboard_entry()
|
||||
.leaderboard_entry_id()
|
||||
.delete(&entry.leaderboard_entry_id);
|
||||
}
|
||||
for personal_best in ctx
|
||||
.db
|
||||
.bark_battle_personal_best_projection()
|
||||
.by_bark_battle_personal_best_work_id()
|
||||
.filter(input.work_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.bark_battle_personal_best_projection()
|
||||
.personal_best_id()
|
||||
.delete(&personal_best.personal_best_id);
|
||||
}
|
||||
if ctx
|
||||
.db
|
||||
.bark_battle_work_stats_projection()
|
||||
.work_id()
|
||||
.find(&input.work_id)
|
||||
.is_some()
|
||||
{
|
||||
ctx.db
|
||||
.bark_battle_work_stats_projection()
|
||||
.work_id()
|
||||
.delete(&input.work_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_bark_battle_runtime_config_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: BarkBattleRuntimeConfigGetInput,
|
||||
@@ -763,6 +879,16 @@ fn bark_battle_run_result(run: BarkBattleRunSnapshot) -> BarkBattleProcedureResu
|
||||
}
|
||||
}
|
||||
|
||||
fn bark_battle_empty_result() -> BarkBattleProcedureResult {
|
||||
BarkBattleProcedureResult {
|
||||
ok: true,
|
||||
draft_config: None,
|
||||
runtime_config: None,
|
||||
run: None,
|
||||
error_message: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn bark_battle_error_result(error: String) -> BarkBattleProcedureResult {
|
||||
BarkBattleProcedureResult {
|
||||
ok: false,
|
||||
@@ -1043,6 +1169,17 @@ mod tests {
|
||||
assert!(result.ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bark_battle_delete_input_carries_owner_and_work() {
|
||||
let input = BarkBattleWorkDeleteInput {
|
||||
work_id: "BB-12345678".to_string(),
|
||||
owner_user_id: "user-1".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(input.work_id, "BB-12345678");
|
||||
assert_eq!(input.owner_user_id, "user-1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_light_editor_config_before_publish() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -53,6 +53,12 @@ pub struct BarkBattleWorkPublishInput {
|
||||
pub published_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct BarkBattleWorkDeleteInput {
|
||||
pub work_id: String,
|
||||
pub owner_user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct BarkBattleRuntimeConfigGetInput {
|
||||
pub work_id: String,
|
||||
|
||||
@@ -201,6 +201,25 @@ pub fn list_jump_hop_works(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn delete_jump_hop_work(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: JumpHopWorkDeleteInput,
|
||||
) -> JumpHopWorksProcedureResult {
|
||||
match ctx.try_with_tx(|tx| delete_jump_hop_work_tx(tx, input.clone())) {
|
||||
Ok(items) => JumpHopWorksProcedureResult {
|
||||
ok: true,
|
||||
items,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => JumpHopWorksProcedureResult {
|
||||
ok: false,
|
||||
items: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn start_jump_hop_run(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -537,6 +556,56 @@ fn list_jump_hop_works_tx(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn delete_jump_hop_work_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: JumpHopWorkDeleteInput,
|
||||
) -> Result<Vec<JumpHopWorkSnapshot>, String> {
|
||||
let work = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?;
|
||||
ctx.db
|
||||
.jump_hop_work_profile()
|
||||
.profile_id()
|
||||
.delete(&work.profile_id);
|
||||
if !work.source_session_id.trim().is_empty() {
|
||||
if let Some(session) = ctx
|
||||
.db
|
||||
.jump_hop_agent_session()
|
||||
.session_id()
|
||||
.find(&work.source_session_id)
|
||||
.filter(|session| session.owner_user_id == input.owner_user_id)
|
||||
{
|
||||
ctx.db
|
||||
.jump_hop_agent_session()
|
||||
.session_id()
|
||||
.delete(&session.session_id);
|
||||
}
|
||||
}
|
||||
for run in ctx
|
||||
.db
|
||||
.jump_hop_runtime_run()
|
||||
.by_jump_hop_run_profile_id()
|
||||
.filter(input.profile_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db.jump_hop_runtime_run().run_id().delete(&run.run_id);
|
||||
}
|
||||
for event in ctx
|
||||
.db
|
||||
.jump_hop_event()
|
||||
.by_jump_hop_event_profile_id()
|
||||
.filter(input.profile_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db.jump_hop_event().event_id().delete(&event.event_id);
|
||||
}
|
||||
list_jump_hop_works_tx(
|
||||
ctx,
|
||||
JumpHopWorksListInput {
|
||||
owner_user_id: input.owner_user_id,
|
||||
published_only: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn start_jump_hop_run_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: JumpHopRunStartInput,
|
||||
@@ -1185,3 +1254,19 @@ fn clone_run(row: &JumpHopRuntimeRunRow) -> JumpHopRuntimeRunRow {
|
||||
updated_at: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn jump_hop_delete_input_carries_owner_and_profile() {
|
||||
let input = JumpHopWorkDeleteInput {
|
||||
profile_id: "jump-hop-profile-1".to_string(),
|
||||
owner_user_id: "user-1".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(input.profile_id, "jump-hop-profile-1");
|
||||
assert_eq!(input.owner_user_id, "user-1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ pub struct JumpHopWorkPublishInput {
|
||||
pub published_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct JumpHopWorkDeleteInput {
|
||||
pub profile_id: String,
|
||||
pub owner_user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct JumpHopWorksListInput {
|
||||
pub owner_user_id: String,
|
||||
|
||||
@@ -202,6 +202,25 @@ pub fn list_wooden_fish_works(
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn delete_wooden_fish_work(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: WoodenFishWorkDeleteInput,
|
||||
) -> WoodenFishWorksProcedureResult {
|
||||
match ctx.try_with_tx(|tx| delete_wooden_fish_work_tx(tx, input.clone())) {
|
||||
Ok(items) => WoodenFishWorksProcedureResult {
|
||||
ok: true,
|
||||
items,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => WoodenFishWorksProcedureResult {
|
||||
ok: false,
|
||||
items: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn start_wooden_fish_run(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -598,6 +617,62 @@ fn list_wooden_fish_works_tx(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn delete_wooden_fish_work_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: WoodenFishWorkDeleteInput,
|
||||
) -> Result<Vec<WoodenFishWorkSnapshot>, String> {
|
||||
let work = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?;
|
||||
ctx.db
|
||||
.wooden_fish_work_profile()
|
||||
.profile_id()
|
||||
.delete(&work.profile_id);
|
||||
if !work.source_session_id.trim().is_empty() {
|
||||
if let Some(session) = ctx
|
||||
.db
|
||||
.wooden_fish_agent_session()
|
||||
.session_id()
|
||||
.find(&work.source_session_id)
|
||||
.filter(|session| session.owner_user_id == input.owner_user_id)
|
||||
{
|
||||
ctx.db
|
||||
.wooden_fish_agent_session()
|
||||
.session_id()
|
||||
.delete(&session.session_id);
|
||||
}
|
||||
}
|
||||
for run in ctx
|
||||
.db
|
||||
.wooden_fish_runtime_run()
|
||||
.by_wooden_fish_run_profile_id()
|
||||
.filter(input.profile_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.wooden_fish_runtime_run()
|
||||
.run_id()
|
||||
.delete(&run.run_id);
|
||||
}
|
||||
for event in ctx
|
||||
.db
|
||||
.wooden_fish_event()
|
||||
.by_wooden_fish_event_profile_id()
|
||||
.filter(input.profile_id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
ctx.db
|
||||
.wooden_fish_event()
|
||||
.event_id()
|
||||
.delete(&event.event_id);
|
||||
}
|
||||
list_wooden_fish_works_tx(
|
||||
ctx,
|
||||
WoodenFishWorksListInput {
|
||||
owner_user_id: input.owner_user_id,
|
||||
published_only: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn start_wooden_fish_run_tx(
|
||||
ctx: &ReducerContext,
|
||||
input: WoodenFishRunStartInput,
|
||||
@@ -1363,6 +1438,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wooden_fish_delete_input_carries_owner_and_profile() {
|
||||
let input = WoodenFishWorkDeleteInput {
|
||||
profile_id: "wooden-fish-profile-1".to_string(),
|
||||
owner_user_id: "user-1".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(input.profile_id, "wooden-fish-profile-1");
|
||||
assert_eq!(input.owner_user_id, "user-1");
|
||||
}
|
||||
|
||||
fn published_ready_work_without_back_button() -> WoodenFishWorkProfileRow {
|
||||
let now = Timestamp::from_micros_since_unix_epoch(1_770_000_000_000_000);
|
||||
WoodenFishWorkProfileRow {
|
||||
|
||||
@@ -84,6 +84,12 @@ pub struct WoodenFishWorkPublishInput {
|
||||
pub published_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct WoodenFishWorkDeleteInput {
|
||||
pub profile_id: String,
|
||||
pub owner_user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct WoodenFishWorksListInput {
|
||||
pub owner_user_id: String,
|
||||
|
||||
Reference in New Issue
Block a user