This commit is contained in:
2026-04-30 17:49:07 +08:00
parent 805d6f8cae
commit 9d684cb7b3
615 changed files with 15368 additions and 6172 deletions

View File

@@ -38,10 +38,10 @@ use spacetime_client::{
CustomWorldAgentSessionRecord, CustomWorldDraftCardDetailRecord,
CustomWorldDraftCardDetailSectionRecord, CustomWorldDraftCardRecord,
CustomWorldGalleryEntryRecord, CustomWorldLibraryEntryRecord,
CustomWorldProfilePlayReportRecordInput, CustomWorldProfileRemixRecordInput,
CustomWorldProfileUpsertRecordInput, CustomWorldPublishGateRecord,
CustomWorldResultPreviewBlockerRecord, CustomWorldSupportedActionRecord,
CustomWorldWorkSummaryRecord, SpacetimeClientError,
CustomWorldProfileLikeReportRecordInput, CustomWorldProfilePlayReportRecordInput,
CustomWorldProfileRemixRecordInput, CustomWorldProfileUpsertRecordInput,
CustomWorldPublishGateRecord, CustomWorldResultPreviewBlockerRecord,
CustomWorldSupportedActionRecord, CustomWorldWorkSummaryRecord, SpacetimeClientError,
};
use std::{collections::BTreeSet, convert::Infallible, sync::Arc, time::Instant};
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
@@ -73,6 +73,7 @@ use crate::{
},
request_context::RequestContext,
state::AppState,
work_author::resolve_work_author_by_user_id,
};
const DRAFT_ASSET_GENERATION_MAX_ATTEMPTS: u32 = 3;
@@ -414,7 +415,6 @@ pub async fn get_custom_world_library(
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, Response> {
let owner_user_id = authenticated.claims().user_id().to_string();
let author_display_name = resolve_author_display_name(&state, &authenticated);
let entries = state
.spacetime_client()
.list_custom_world_works(owner_user_id.clone())
@@ -430,9 +430,9 @@ pub async fn get_custom_world_library(
.into_iter()
.filter_map(|item| {
map_custom_world_library_entry_response_from_work_summary(
&state,
item,
&owner_user_id,
&author_display_name,
)
})
.collect(),
@@ -467,7 +467,7 @@ pub async fn get_custom_world_library_detail(
Ok(json_success_body(
Some(&request_context),
CustomWorldGalleryDetailResponse {
entry: map_custom_world_library_entry_response(detail.entry),
entry: map_custom_world_library_entry_response(&state, detail.entry),
},
))
}
@@ -548,8 +548,11 @@ pub async fn put_custom_world_library_profile(
Ok(json_success_body(
Some(&request_context),
CustomWorldLibraryMutationResponse {
entry: map_custom_world_library_entry_response(mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(mutation.entry)],
entry: map_custom_world_library_entry_response(&state, mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(
&state,
mutation.entry,
)],
},
))
}
@@ -584,7 +587,7 @@ pub async fn delete_custom_world_library_profile(
CustomWorldLibraryResponse {
entries: entries
.into_iter()
.map(map_custom_world_library_entry_response)
.map(|entry| map_custom_world_library_entry_response(&state, entry))
.collect(),
},
))
@@ -636,8 +639,11 @@ pub async fn publish_custom_world_library_profile(
Ok(json_success_body(
Some(&request_context),
CustomWorldLibraryMutationResponse {
entry: map_custom_world_library_entry_response(mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(mutation.entry)],
entry: map_custom_world_library_entry_response(&state, mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(
&state,
mutation.entry,
)],
},
))
}
@@ -675,8 +681,11 @@ pub async fn unpublish_custom_world_library_profile(
Ok(json_success_body(
Some(&request_context),
CustomWorldLibraryMutationResponse {
entry: map_custom_world_library_entry_response(mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(mutation.entry)],
entry: map_custom_world_library_entry_response(&state, mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(
&state,
mutation.entry,
)],
},
))
}
@@ -698,7 +707,7 @@ pub async fn list_custom_world_gallery(
CustomWorldGalleryResponse {
entries: entries
.into_iter()
.map(map_custom_world_gallery_card_response)
.map(|entry| map_custom_world_gallery_card_response(&state, entry))
.collect(),
},
))
@@ -730,7 +739,7 @@ pub async fn get_custom_world_gallery_detail(
Ok(json_success_body(
Some(&request_context),
CustomWorldGalleryDetailResponse {
entry: map_custom_world_library_entry_response(detail.entry),
entry: map_custom_world_library_entry_response(&state, detail.entry),
},
))
}
@@ -761,7 +770,7 @@ pub async fn get_custom_world_gallery_detail_by_code(
Ok(json_success_body(
Some(&request_context),
CustomWorldGalleryDetailResponse {
entry: map_custom_world_library_entry_response(detail.entry),
entry: map_custom_world_library_entry_response(&state, detail.entry),
},
))
}
@@ -800,8 +809,11 @@ pub async fn remix_custom_world_gallery_profile(
Ok(json_success_body(
Some(&request_context),
CustomWorldLibraryMutationResponse {
entry: map_custom_world_library_entry_response(mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(mutation.entry)],
entry: map_custom_world_library_entry_response(&state, mutation.entry.clone()),
entries: vec![map_custom_world_library_entry_response(
&state,
mutation.entry,
)],
},
))
}
@@ -837,7 +849,44 @@ pub async fn record_custom_world_gallery_play(
Ok(json_success_body(
Some(&request_context),
CustomWorldGalleryDetailResponse {
entry: map_custom_world_library_entry_response(mutation.entry),
entry: map_custom_world_library_entry_response(&state, mutation.entry),
},
))
}
pub async fn record_custom_world_gallery_like(
State(state): State<AppState>,
Path((owner_user_id, profile_id)): Path<(String, String)>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, Response> {
if owner_user_id.trim().is_empty() || profile_id.trim().is_empty() {
return Err(custom_world_error_response(
&request_context,
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "custom-world-gallery",
"message": "ownerUserId and profileId are required",
})),
));
}
let mutation = state
.spacetime_client()
.record_custom_world_profile_like(CustomWorldProfileLikeReportRecordInput {
owner_user_id,
profile_id,
user_id: authenticated.claims().user_id().to_string(),
liked_at_micros: current_utc_micros(),
})
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
Ok(json_success_body(
Some(&request_context),
CustomWorldGalleryDetailResponse {
entry: map_custom_world_library_entry_response(&state, mutation.entry),
},
))
}
@@ -2697,18 +2746,25 @@ async fn upsert_custom_world_draft_foundation_progress(
}
fn map_custom_world_library_entry_response(
state: &AppState,
entry: CustomWorldLibraryEntryRecord,
) -> CustomWorldLibraryEntryResponse {
let author = resolve_work_author_by_user_id(
state,
&entry.owner_user_id,
Some(&entry.author_display_name),
entry.author_public_user_code.as_deref(),
);
CustomWorldLibraryEntryResponse {
owner_user_id: entry.owner_user_id,
profile_id: entry.profile_id,
public_work_code: entry.public_work_code,
author_public_user_code: entry.author_public_user_code,
author_public_user_code: author.public_user_code.or(entry.author_public_user_code),
profile: entry.profile,
visibility: entry.visibility,
published_at: entry.published_at,
updated_at: entry.updated_at,
author_display_name: entry.author_display_name,
author_display_name: author.display_name,
world_name: entry.world_name,
subtitle: entry.subtitle,
summary_text: entry.summary_text,
@@ -2724,23 +2780,24 @@ fn map_custom_world_library_entry_response(
}
fn map_custom_world_library_entry_response_from_work_summary(
state: &AppState,
item: CustomWorldWorkSummaryRecord,
owner_user_id: &str,
author_display_name: &str,
) -> Option<CustomWorldLibraryEntryResponse> {
let profile_id = item.profile_id.as_ref()?.clone();
let profile = build_custom_world_library_list_profile_payload(&item, &profile_id);
let author = resolve_work_author_by_user_id(state, owner_user_id, None, None);
Some(CustomWorldLibraryEntryResponse {
owner_user_id: owner_user_id.to_string(),
public_work_code: (item.status == "published")
.then(|| build_public_work_code_from_profile_id(&profile_id)),
profile_id,
author_public_user_code: None,
author_public_user_code: author.public_user_code,
profile,
visibility: item.status,
published_at: item.published_at,
updated_at: item.updated_at,
author_display_name: author_display_name.to_string(),
author_display_name: author.display_name,
world_name: item.title,
subtitle: item.subtitle,
summary_text: item.summary,
@@ -2803,17 +2860,26 @@ fn build_custom_world_library_list_profile_payload(
}
fn map_custom_world_gallery_card_response(
state: &AppState,
entry: CustomWorldGalleryEntryRecord,
) -> CustomWorldGalleryCardResponse {
let author = resolve_work_author_by_user_id(
state,
&entry.owner_user_id,
Some(&entry.author_display_name),
Some(&entry.author_public_user_code),
);
CustomWorldGalleryCardResponse {
owner_user_id: entry.owner_user_id,
profile_id: entry.profile_id,
public_work_code: entry.public_work_code,
author_public_user_code: entry.author_public_user_code,
author_public_user_code: author
.public_user_code
.unwrap_or(entry.author_public_user_code),
visibility: entry.visibility,
published_at: entry.published_at,
updated_at: entry.updated_at,
author_display_name: entry.author_display_name,
author_display_name: author.display_name,
world_name: entry.world_name,
subtitle: entry.subtitle,
summary_text: entry.summary_text,