155 lines
5.0 KiB
Rust
155 lines
5.0 KiB
Rust
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<AppState>,
|
|
Extension(request_context): Extension<RequestContext>,
|
|
) -> Result<Json<Value>, 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::<Vec<_>>();
|
|
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<AppState>,
|
|
Path(public_work_code): Path<String>,
|
|
Extension(request_context): Extension<RequestContext>,
|
|
) -> Result<Json<Value>, 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
|
|
}
|