diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 41551490..e441d494 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -76,11 +76,12 @@ use crate::{ logout::logout, logout_all::logout_all, match3d::{ - click_match3d_item, create_match3d_agent_session, delete_match3d_work, - execute_match3d_agent_action, finish_match3d_time_up, get_match3d_agent_session, - get_match3d_run, get_match3d_work_detail, get_match3d_works, list_match3d_gallery, - publish_match3d_work, put_match3d_work, restart_match3d_run, start_match3d_run, - stop_match3d_run, stream_match3d_agent_message, submit_match3d_agent_message, + click_match3d_item, compile_match3d_agent_draft, create_match3d_agent_session, + delete_match3d_work, execute_match3d_agent_action, finish_match3d_time_up, + get_match3d_agent_session, get_match3d_run, get_match3d_work_detail, get_match3d_works, + list_match3d_gallery, publish_match3d_work, put_match3d_work, restart_match3d_run, + start_match3d_run, stop_match3d_run, stream_match3d_agent_message, + submit_match3d_agent_message, }, password_entry::password_entry, password_management::{change_password, reset_password}, @@ -749,7 +750,7 @@ pub fn build_router(state: AppState) -> Router { ) .route( "/api/creation/match3d/sessions/{session_id}/compile", - post(execute_match3d_agent_action).route_layer(middleware::from_fn_with_state( + post(compile_match3d_agent_draft).route_layer(middleware::from_fn_with_state( state.clone(), require_bearer_auth, )), diff --git a/server-rs/crates/api-server/src/match3d.rs b/server-rs/crates/api-server/src/match3d.rs index cf6762e3..00968841 100644 --- a/server-rs/crates/api-server/src/match3d.rs +++ b/server-rs/crates/api-server/src/match3d.rs @@ -68,6 +68,19 @@ struct Match3DConfigJson { difficulty: u32, } +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CompileMatch3DDraftRequest { + #[serde(default)] + game_name: Option, + #[serde(default)] + summary: Option, + #[serde(default)] + tags: Option>, + #[serde(default)] + cover_image_src: Option, +} + pub async fn create_match3d_agent_session( State(state): State, Extension(request_context): Extension, @@ -245,47 +258,59 @@ pub async fn execute_match3d_agent_action( )); } - let owner_user_id = authenticated.claims().user_id().to_string(); - let session = state - .spacetime_client() - .get_match3d_agent_session(session_id.clone(), owner_user_id.clone()) - .await - .map_err(|error| { - match3d_error_response( - &request_context, - MATCH3D_AGENT_PROVIDER, - map_match3d_client_error(error), - ) - })?; - let config = resolve_config_or_default(session.config.as_ref()); - let tags_json = payload - .tags - .as_ref() - .map(|tags| serde_json::to_string(&normalize_tags(tags.clone())).unwrap_or_default()); - let session = state - .spacetime_client() - .compile_match3d_draft(Match3DCompileDraftRecordInput { - session_id, - owner_user_id, - profile_id: build_prefixed_uuid_id(MATCH3D_PROFILE_ID_PREFIX), - author_display_name: resolve_author_display_name(&state, &authenticated), - game_name: payload - .game_name - .or_else(|| Some(format!("{}抓大鹅", config.theme_text))), - summary_text: payload.summary, - tags_json, - cover_image_src: payload.cover_image_src, - cover_asset_id: None, - compiled_at_micros: current_utc_micros(), - }) - .await - .map_err(|error| { - match3d_error_response( - &request_context, - MATCH3D_AGENT_PROVIDER, - map_match3d_client_error(error), - ) - })?; + let session = compile_match3d_draft_for_session( + &state, + &request_context, + &authenticated, + session_id, + payload.game_name, + payload.summary, + payload.tags, + payload.cover_image_src, + ) + .await?; + + Ok(json_success_body( + Some(&request_context), + Match3DAgentActionResponse { + session: map_match3d_agent_session_response(session), + }, + )) +} + +pub async fn compile_match3d_agent_draft( + State(state): State, + Path(session_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let payload = payload + .map(|Json(payload)| payload) + .unwrap_or(CompileMatch3DDraftRequest { + game_name: None, + summary: None, + tags: None, + cover_image_src: None, + }); + ensure_non_empty( + &request_context, + MATCH3D_AGENT_PROVIDER, + &session_id, + "sessionId", + )?; + + let session = compile_match3d_draft_for_session( + &state, + &request_context, + &authenticated, + session_id, + payload.game_name, + payload.summary, + payload.tags, + payload.cover_image_src, + ) + .await?; Ok(json_success_body( Some(&request_context), @@ -818,6 +843,57 @@ async fn submit_and_finalize_match3d_message( }) } +async fn compile_match3d_draft_for_session( + state: &AppState, + request_context: &RequestContext, + authenticated: &AuthenticatedAccessToken, + session_id: String, + game_name: Option, + summary: Option, + tags: Option>, + cover_image_src: Option, +) -> Result { + let owner_user_id = authenticated.claims().user_id().to_string(); + let session = state + .spacetime_client() + .get_match3d_agent_session(session_id.clone(), owner_user_id.clone()) + .await + .map_err(|error| { + match3d_error_response( + request_context, + MATCH3D_AGENT_PROVIDER, + map_match3d_client_error(error), + ) + })?; + let config = resolve_config_or_default(session.config.as_ref()); + let tags_json = tags + .as_ref() + .map(|tags| serde_json::to_string(&normalize_tags(tags.clone())).unwrap_or_default()); + + state + .spacetime_client() + .compile_match3d_draft(Match3DCompileDraftRecordInput { + session_id, + owner_user_id, + profile_id: build_prefixed_uuid_id(MATCH3D_PROFILE_ID_PREFIX), + author_display_name: resolve_author_display_name(state, authenticated), + game_name: game_name.or_else(|| Some(format!("{}抓大鹅", config.theme_text))), + summary_text: summary, + tags_json, + cover_image_src, + cover_asset_id: None, + compiled_at_micros: current_utc_micros(), + }) + .await + .map_err(|error| { + match3d_error_response( + request_context, + MATCH3D_AGENT_PROVIDER, + map_match3d_client_error(error), + ) + }) +} + fn map_match3d_agent_session_response( session: Match3DAgentSessionRecord, ) -> Match3DAgentSessionSnapshotResponse {