use axum::{ Json, extract::{Extension, Path, State}, http::{HeaderName, StatusCode, header}, response::Response, }; use serde_json::{Value, json}; use shared_contracts::public_work::{ PublicWorkDetailEntryResponse, PublicWorkDetailResponse, PublicWorkGalleryEntryResponse, PublicWorkGalleryResponse, }; use spacetime_client::{ PublicWorkDetailEntryRecord, PublicWorkGalleryEntryRecord, SpacetimeClientError, }; use crate::{ api_response::json_success_body, http_error::AppError, request_context::RequestContext, state::AppState, work_author::resolve_work_author_by_user_id, }; const PUBLIC_WORK_PROVIDER: &str = "public-work"; pub async fn list_public_works( State(state): State, Extension(request_context): Extension, ) -> Result, Response> { let items = state .spacetime_client() .list_public_work_gallery_entries() .await .map_err(|error| { public_work_error_response(&request_context, map_public_work_client_error(error)) })? .into_iter() .map(|entry| map_public_work_gallery_entry_response(&state, entry)) .collect::>(); let total_count = items.len().min(u32::MAX as usize) as u32; Ok(json_success_body( Some(&request_context), PublicWorkGalleryResponse { items, has_more: false, next_cursor: None, total_count, }, )) } pub async fn get_public_work_detail( State(state): State, Path(public_work_code): Path, Extension(request_context): Extension, ) -> Result, Response> { if public_work_code.trim().is_empty() { return Err(public_work_error_response( &request_context, AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ "provider": PUBLIC_WORK_PROVIDER, "message": "publicWorkCode is required", })), )); } let item = state .spacetime_client() .get_public_work_detail_by_code(public_work_code) .await .map_err(|error| { public_work_error_response(&request_context, map_public_work_client_error(error)) })?; Ok(json_success_body( Some(&request_context), PublicWorkDetailResponse { item: map_public_work_detail_entry_response(&state, item), }, )) } pub(crate) fn map_public_work_gallery_entry_response( state: &AppState, entry: PublicWorkGalleryEntryRecord, ) -> PublicWorkGalleryEntryResponse { let author = resolve_work_author_by_user_id( state, &entry.owner_user_id, Some(&entry.author_display_name), None, ); PublicWorkGalleryEntryResponse { source_type: entry.source_type, work_id: entry.work_id, profile_id: entry.profile_id, source_session_id: entry.source_session_id, public_work_code: entry.public_work_code, owner_user_id: entry.owner_user_id, author_display_name: author.display_name, world_name: entry.world_name, subtitle: entry.subtitle, summary_text: entry.summary_text, cover_image_src: entry.cover_image_src, cover_asset_id: entry.cover_asset_id, theme_tags: entry.theme_tags, play_count: entry.play_count, remix_count: entry.remix_count, like_count: entry.like_count, recent_play_count_7d: entry.recent_play_count_7d, published_at: entry.published_at, updated_at: entry.updated_at, sort_time_micros: entry.sort_time_micros, } } pub(crate) fn map_public_work_detail_entry_response( state: &AppState, entry: PublicWorkDetailEntryRecord, ) -> PublicWorkDetailEntryResponse { PublicWorkDetailEntryResponse { entry: map_public_work_gallery_entry_response(state, entry.entry), detail_payload_json: entry.detail_payload_json, } } fn map_public_work_client_error(error: SpacetimeClientError) -> AppError { let status = match &error { SpacetimeClientError::Runtime(_) => StatusCode::BAD_REQUEST, SpacetimeClientError::Procedure(message) if message.contains("不存在") || message.contains("not found") || message.contains("does not exist") => { StatusCode::NOT_FOUND } SpacetimeClientError::Timeout => StatusCode::GATEWAY_TIMEOUT, SpacetimeClientError::ConnectDropped => StatusCode::SERVICE_UNAVAILABLE, _ => StatusCode::BAD_GATEWAY, }; AppError::from_status(status).with_details(json!({ "provider": "spacetimedb", "message": error.to_string(), })) } fn public_work_error_response(request_context: &RequestContext, error: AppError) -> Response { let mut response = error.into_response_with_context(Some(request_context)); response.headers_mut().insert( HeaderName::from_static("x-genarrative-provider"), header::HeaderValue::from_static(PUBLIC_WORK_PROVIDER), ); response }