1
This commit is contained in:
@@ -23,6 +23,8 @@ pub struct CustomWorldProfile {
|
||||
landmark_count: u32,
|
||||
author_display_name: String,
|
||||
published_at: Option<Timestamp>,
|
||||
// 软删除后保留 profile 真相,供审计与幂等删除使用。
|
||||
deleted_at: Option<Timestamp>,
|
||||
created_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
@@ -701,6 +703,34 @@ pub fn unpublish_custom_world_profile_and_return(
|
||||
}
|
||||
}
|
||||
|
||||
// 删除入口继续走 owner-only 软删除,不直接物理删除 profile 真相。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn delete_custom_world_profile_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: CustomWorldProfileDeleteInput,
|
||||
) -> CustomWorldProfileListResult {
|
||||
match ctx.try_with_tx(|tx| {
|
||||
delete_custom_world_profile_record(tx, input.clone())?;
|
||||
list_custom_world_profile_snapshots(
|
||||
tx,
|
||||
CustomWorldProfileListInput {
|
||||
owner_user_id: input.owner_user_id.clone(),
|
||||
},
|
||||
)
|
||||
}) {
|
||||
Ok(entries) => CustomWorldProfileListResult {
|
||||
ok: true,
|
||||
entries,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => CustomWorldProfileListResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn list_custom_world_profiles(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -986,6 +1016,7 @@ fn upsert_custom_world_profile_record(
|
||||
landmark_count: input.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: existing.published_at,
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
}
|
||||
@@ -1005,6 +1036,7 @@ fn upsert_custom_world_profile_record(
|
||||
landmark_count: input.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: None,
|
||||
created_at: updated_at,
|
||||
updated_at,
|
||||
},
|
||||
@@ -1139,6 +1171,7 @@ fn publish_custom_world_profile_record(
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: Some(published_at),
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at: published_at,
|
||||
};
|
||||
@@ -1199,6 +1232,7 @@ fn unpublish_custom_world_profile_record(
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
};
|
||||
@@ -1208,6 +1242,62 @@ fn unpublish_custom_world_profile_record(
|
||||
Ok((build_custom_world_profile_snapshot(&inserted), None))
|
||||
}
|
||||
|
||||
fn delete_custom_world_profile_record(
|
||||
ctx: &ReducerContext,
|
||||
input: CustomWorldProfileDeleteInput,
|
||||
) -> Result<(), String> {
|
||||
validate_custom_world_profile_delete_input(&input).map_err(|error| error.to_string())?;
|
||||
|
||||
let Some(existing) = ctx
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if existing.deleted_at.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let deleted_at = Timestamp::from_micros_since_unix_epoch(input.deleted_at_micros);
|
||||
|
||||
ctx.db
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.delete(&existing.profile_id);
|
||||
|
||||
ctx.db
|
||||
.custom_world_gallery_entry()
|
||||
.profile_id()
|
||||
.delete(&existing.profile_id);
|
||||
|
||||
let next_row = CustomWorldProfile {
|
||||
profile_id: existing.profile_id.clone(),
|
||||
owner_user_id: existing.owner_user_id.clone(),
|
||||
source_agent_session_id: existing.source_agent_session_id.clone(),
|
||||
publication_status: CustomWorldPublicationStatus::Draft,
|
||||
world_name: existing.world_name.clone(),
|
||||
subtitle: existing.subtitle.clone(),
|
||||
summary_text: existing.summary_text.clone(),
|
||||
theme_mode: existing.theme_mode,
|
||||
cover_image_src: existing.cover_image_src.clone(),
|
||||
profile_payload_json: existing.profile_payload_json.clone(),
|
||||
playable_npc_count: existing.playable_npc_count,
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: existing.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: Some(deleted_at),
|
||||
created_at: existing.created_at,
|
||||
updated_at: deleted_at,
|
||||
};
|
||||
|
||||
let _ = ctx.db.custom_world_profile().insert(next_row);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_custom_world_profile_snapshots(
|
||||
ctx: &ReducerContext,
|
||||
input: CustomWorldProfileListInput,
|
||||
@@ -1218,7 +1308,7 @@ fn list_custom_world_profile_snapshots(
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.iter()
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none())
|
||||
.map(|row| build_custom_world_profile_snapshot(&row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1264,7 +1354,7 @@ fn get_custom_world_library_detail_record(
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id);
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none());
|
||||
|
||||
let gallery_entry = profile
|
||||
.as_ref()
|
||||
@@ -1305,6 +1395,7 @@ fn get_custom_world_gallery_detail_record(
|
||||
.filter(|row| {
|
||||
row.owner_user_id == input.owner_user_id
|
||||
&& row.publication_status == CustomWorldPublicationStatus::Published
|
||||
&& row.deleted_at.is_none()
|
||||
});
|
||||
|
||||
let gallery_entry = ctx
|
||||
@@ -1377,7 +1468,7 @@ fn list_custom_world_work_snapshots(
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.iter()
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none())
|
||||
{
|
||||
items.push(CustomWorldWorkSummarySnapshot {
|
||||
work_id: format!("published:{}", profile.profile_id),
|
||||
@@ -3107,6 +3198,9 @@ fn build_custom_world_profile_snapshot(row: &CustomWorldProfile) -> CustomWorldP
|
||||
published_at_micros: row
|
||||
.published_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
deleted_at_micros: row
|
||||
.deleted_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ use module_custom_world::{
|
||||
validate_custom_world_agent_operation_get_input,
|
||||
validate_custom_world_agent_session_create_input,
|
||||
validate_custom_world_agent_session_get_input, validate_custom_world_gallery_detail_input,
|
||||
validate_custom_world_library_detail_input, validate_custom_world_profile_list_input,
|
||||
validate_custom_world_profile_publish_input, validate_custom_world_profile_unpublish_input,
|
||||
validate_custom_world_profile_upsert_input, validate_custom_world_publish_world_input,
|
||||
validate_custom_world_works_list_input,
|
||||
validate_custom_world_library_detail_input, validate_custom_world_profile_delete_input,
|
||||
validate_custom_world_profile_list_input, validate_custom_world_profile_publish_input,
|
||||
validate_custom_world_profile_unpublish_input, validate_custom_world_profile_upsert_input,
|
||||
validate_custom_world_publish_world_input, validate_custom_world_works_list_input,
|
||||
};
|
||||
use module_inventory::{
|
||||
GrantInventoryItemInput, INVENTORY_MUTATION_ID_PREFIX, INVENTORY_SLOT_ID_PREFIX,
|
||||
@@ -799,6 +799,8 @@ pub struct CustomWorldProfile {
|
||||
landmark_count: u32,
|
||||
author_display_name: String,
|
||||
published_at: Option<Timestamp>,
|
||||
// 软删除后保留 profile 真相,供审计与幂等删除使用。
|
||||
deleted_at: Option<Timestamp>,
|
||||
created_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
}
|
||||
@@ -3353,6 +3355,34 @@ pub fn unpublish_custom_world_profile_and_return(
|
||||
}
|
||||
}
|
||||
|
||||
// 删除入口继续走 owner-only 软删除,不直接物理删除 profile 真相。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn delete_custom_world_profile_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: module_custom_world::CustomWorldProfileDeleteInput,
|
||||
) -> CustomWorldProfileListResult {
|
||||
match ctx.try_with_tx(|tx| {
|
||||
delete_custom_world_profile_record(tx, input.clone())?;
|
||||
list_custom_world_profile_snapshots(
|
||||
tx,
|
||||
CustomWorldProfileListInput {
|
||||
owner_user_id: input.owner_user_id.clone(),
|
||||
},
|
||||
)
|
||||
}) {
|
||||
Ok(entries) => CustomWorldProfileListResult {
|
||||
ok: true,
|
||||
entries,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => CustomWorldProfileListResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn list_custom_world_profiles(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -3894,6 +3924,7 @@ fn upsert_custom_world_profile_record(
|
||||
landmark_count: input.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: existing.published_at,
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
}
|
||||
@@ -3913,6 +3944,7 @@ fn upsert_custom_world_profile_record(
|
||||
landmark_count: input.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: None,
|
||||
created_at: updated_at,
|
||||
updated_at,
|
||||
},
|
||||
@@ -4047,6 +4079,7 @@ fn publish_custom_world_profile_record(
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: Some(published_at),
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at: published_at,
|
||||
};
|
||||
@@ -4107,6 +4140,7 @@ fn unpublish_custom_world_profile_record(
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: input.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
};
|
||||
@@ -4116,6 +4150,62 @@ fn unpublish_custom_world_profile_record(
|
||||
Ok((build_custom_world_profile_snapshot(&inserted), None))
|
||||
}
|
||||
|
||||
fn delete_custom_world_profile_record(
|
||||
ctx: &ReducerContext,
|
||||
input: module_custom_world::CustomWorldProfileDeleteInput,
|
||||
) -> Result<(), String> {
|
||||
validate_custom_world_profile_delete_input(&input).map_err(|error| error.to_string())?;
|
||||
|
||||
let Some(existing) = ctx
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if existing.deleted_at.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let deleted_at = Timestamp::from_micros_since_unix_epoch(input.deleted_at_micros);
|
||||
|
||||
ctx.db
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.delete(&existing.profile_id);
|
||||
|
||||
ctx.db
|
||||
.custom_world_gallery_entry()
|
||||
.profile_id()
|
||||
.delete(&existing.profile_id);
|
||||
|
||||
let next_row = CustomWorldProfile {
|
||||
profile_id: existing.profile_id.clone(),
|
||||
owner_user_id: existing.owner_user_id.clone(),
|
||||
source_agent_session_id: existing.source_agent_session_id.clone(),
|
||||
publication_status: CustomWorldPublicationStatus::Draft,
|
||||
world_name: existing.world_name.clone(),
|
||||
subtitle: existing.subtitle.clone(),
|
||||
summary_text: existing.summary_text.clone(),
|
||||
theme_mode: existing.theme_mode,
|
||||
cover_image_src: existing.cover_image_src.clone(),
|
||||
profile_payload_json: existing.profile_payload_json.clone(),
|
||||
playable_npc_count: existing.playable_npc_count,
|
||||
landmark_count: existing.landmark_count,
|
||||
author_display_name: existing.author_display_name.clone(),
|
||||
published_at: None,
|
||||
deleted_at: Some(deleted_at),
|
||||
created_at: existing.created_at,
|
||||
updated_at: deleted_at,
|
||||
};
|
||||
|
||||
let _ = ctx.db.custom_world_profile().insert(next_row);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_custom_world_profile_snapshots(
|
||||
ctx: &ReducerContext,
|
||||
input: CustomWorldProfileListInput,
|
||||
@@ -4126,7 +4216,7 @@ fn list_custom_world_profile_snapshots(
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.iter()
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none())
|
||||
.map(|row| build_custom_world_profile_snapshot(&row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -4172,7 +4262,7 @@ fn get_custom_world_library_detail_record(
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id);
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none());
|
||||
|
||||
let gallery_entry = profile
|
||||
.as_ref()
|
||||
@@ -4213,6 +4303,7 @@ fn get_custom_world_gallery_detail_record(
|
||||
.filter(|row| {
|
||||
row.owner_user_id == input.owner_user_id
|
||||
&& row.publication_status == CustomWorldPublicationStatus::Published
|
||||
&& row.deleted_at.is_none()
|
||||
});
|
||||
|
||||
let gallery_entry = ctx
|
||||
@@ -4283,7 +4374,7 @@ fn list_custom_world_work_snapshots(
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.iter()
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none())
|
||||
{
|
||||
items.push(CustomWorldWorkSummarySnapshot {
|
||||
work_id: format!("published:{}", profile.profile_id),
|
||||
@@ -6104,6 +6195,9 @@ fn build_custom_world_profile_snapshot(row: &CustomWorldProfile) -> CustomWorldP
|
||||
published_at_micros: row
|
||||
.published_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
deleted_at_micros: row
|
||||
.deleted_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user