From 65fce3fb9c8add1047f45a3a8b82ff5d29aa0e79 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 23 Aug 2024 20:44:22 -0400 Subject: [PATCH] use Json for upload endpoints --- .../endpoints/media_files/upload/mod.rs | 1 + .../upload/upload_audio_media_file_handler.rs | 14 +- .../upload/upload_image_media_file_handler.rs | 14 +- ...oad_new_engine_asset_media_file_handler.rs | 14 +- .../upload_new_scene_media_file_handler.rs | 14 +- .../upload_new_video_media_file_handler.rs | 14 +- .../upload_pmx_media_file_handler.rs | 14 +- .../upload_saved_scene_media_file_handler.rs | 15 +- ...pload_scene_snapshot_media_file_handler.rs | 14 +- .../upload/upload_studio_shot/mod.rs | 1 + .../upload_studio_shot_media_file_handler.rs | 300 ++++++++++++++++++ 11 files changed, 334 insertions(+), 81 deletions(-) create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/mod.rs index e61128808d..f1f4f36f37 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/mod.rs @@ -9,4 +9,5 @@ pub mod upload_new_video_media_file_handler; pub mod upload_pmx; pub mod upload_saved_scene_media_file_handler; pub mod upload_scene_snapshot_media_file_handler; +pub mod upload_studio_shot; pub mod upload_video; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_audio_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_audio_media_file_handler.rs index 0bd68b5f85..f37edd58bc 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_audio_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_audio_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -110,7 +111,7 @@ pub async fn upload_audio_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -349,15 +350,8 @@ pub async fn upload_audio_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadAudioMediaFileSuccessResponse { + Ok(Json(UploadAudioMediaFileSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_image_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_image_media_file_handler.rs index 8197426b55..cd02accddd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_image_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_image_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -106,7 +107,7 @@ pub async fn upload_image_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -293,15 +294,8 @@ pub async fn upload_image_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadImageMediaFileSuccessResponse { + Ok(Json(UploadImageMediaFileSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_engine_asset_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_engine_asset_media_file_handler.rs index 97fbb80ca1..bcf2ff9fd7 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_engine_asset_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_engine_asset_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use utoipa::ToSchema; @@ -112,7 +113,7 @@ pub async fn upload_new_engine_asset_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { validate_request(&form)?; @@ -235,17 +236,10 @@ pub async fn upload_new_engine_asset_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadNewEngineAssetSuccessResponse { + Ok(Json(UploadNewEngineAssetSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } fn validate_request(form: &UploadNewEngineAssetFileForm) -> Result<(), MediaFileUploadError> { diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_scene_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_scene_media_file_handler.rs index d1aaa48c5d..587d861b13 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_scene_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_scene_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -97,7 +98,7 @@ pub async fn upload_new_scene_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -261,15 +262,8 @@ pub async fn upload_new_scene_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadNewSceneMediaFileSuccessResponse { + Ok(Json(UploadNewSceneMediaFileSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_video_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_video_media_file_handler.rs index 64d3fd707b..04a26f9f8b 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_video_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_new_video_media_file_handler.rs @@ -10,6 +10,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{debug, error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -124,7 +125,7 @@ pub async fn upload_new_video_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -348,15 +349,8 @@ pub async fn upload_new_video_media_file_handler( } } - let response = UploadNewVideoMediaFileSuccessResponse { + Ok(Json(UploadNewVideoMediaFileSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs index 44689d23a3..8fccedb294 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_pmx/upload_pmx_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use utoipa::ToSchema; @@ -112,7 +113,7 @@ pub async fn upload_pmx_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -294,15 +295,8 @@ pub async fn upload_pmx_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadPmxSuccessResponse { + Ok(Json(UploadPmxSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_saved_scene_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_saved_scene_media_file_handler.rs index 8882f9567a..a17d38b21e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_saved_scene_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_saved_scene_media_file_handler.rs @@ -8,7 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; -use actix_web::web::Path; +use actix_web::web::{Json, Path}; use log::{error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -99,7 +99,7 @@ pub async fn upload_saved_scene_media_file_handler( server_state: web::Data>, MultipartForm(mut form): MultipartForm, path: Path, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -271,15 +271,8 @@ pub async fn upload_saved_scene_media_file_handler( MediaFileUploadError::ServerError })?; - let response = UploadSavedSceneMediaFileSuccessResponse { + Ok(Json(UploadSavedSceneMediaFileSuccessResponse { success: true, media_file_token: path.token.clone(), - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_scene_snapshot_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_scene_snapshot_media_file_handler.rs index d161c30013..68453232ad 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_scene_snapshot_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_scene_snapshot_media_file_handler.rs @@ -8,6 +8,7 @@ use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::text::Text; use actix_multipart::Multipart; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; use log::{error, info, warn}; use once_cell::sync::Lazy; use stripe::CreatePaymentLinkShippingAddressCollectionAllowedCountries::Mf; @@ -96,7 +97,7 @@ pub async fn upload_scene_snapshot_media_file_handler( http_request: HttpRequest, server_state: web::Data>, MultipartForm(mut form): MultipartForm, -) -> Result { +) -> Result, MediaFileUploadError> { let mut mysql_connection = server_state.mysql_pool .acquire() @@ -254,15 +255,8 @@ pub async fn upload_scene_snapshot_media_file_handler( info!("new media file id: {} token: {:?}", record_id, &token); - let response = UploadSceneSnapshotMediaFileSuccessResponse { + Ok(Json(UploadSceneSnapshotMediaFileSuccessResponse { success: true, media_file_token: token, - }; - - let body = serde_json::to_string(&response) - .map_err(|e| MediaFileUploadError::ServerError)?; - - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/mod.rs new file mode 100644 index 0000000000..855c21048a --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/mod.rs @@ -0,0 +1 @@ +pub mod upload_studio_shot_media_file_handler; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs new file mode 100644 index 0000000000..50e256c014 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/upload/upload_studio_shot/upload_studio_shot_media_file_handler.rs @@ -0,0 +1,300 @@ +use std::collections::HashSet; +use std::io::{BufReader, Cursor, Read}; +use std::path::PathBuf; +use std::sync::Arc; + +use actix_multipart::form::MultipartForm; +use actix_multipart::form::tempfile::TempFile; +use actix_multipart::form::text::Text; +use actix_multipart::Multipart; +use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::web::Json; +use log::{error, info, warn}; +use once_cell::sync::Lazy; +use utoipa::ToSchema; + +use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType; +use enums::by_table::media_files::media_file_class::MediaFileClass; +use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory; +use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::common::visibility::Visibility; +use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes; +use http_server_common::request::get_request_ip::get_request_ip; +use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes; +use mimetypes::mimetype_to_extension::mimetype_to_extension; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::create::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType}; +use tokens::tokens::media_files::MediaFileToken; +use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes, get_mp4_info_for_bytes_and_len}; + +use crate::http_server::endpoints::beta_keys::create_beta_keys_handler::CreateBetaKeysError; +use crate::http_server::endpoints::media_files::upload::upload_engine_asset::drain_multipart_request::drain_multipart_request; +use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; +use crate::http_server::endpoints::media_files::upload::upload_new_scene_media_file_handler::UploadNewSceneMediaFileForm; +use crate::http_server::endpoints::media_files::upload::upload_pmx::extract_and_upload_pmx_files::{extract_and_upload_pmx_files, PmxError}; +use crate::http_server::web_utils::require_moderator::{require_moderator, RequireModeratorError, UseDatabase}; +use crate::state::server_state::ServerState; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; + +/// Form-multipart request fields. +/// +/// IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `UploadStudioShotFileForm` (Under "Schema") FOR DETAILS ON FIELDS AND NULLABILITY. +#[derive(MultipartForm, ToSchema)] +#[multipart(duplicate_field = "deny")] +pub struct UploadStudioShotFileForm { + /// UUID for request idempotency + #[multipart(limit = "2 KiB")] + #[schema(value_type = String, format = Binary)] + uuid_idempotency_token: Text, + + // TODO: is MultipartBytes better than TempFile ? + /// The uploaded file + #[multipart(limit = "512 MiB")] + #[schema(value_type = Vec, format = Binary)] + file: TempFile, + + /// The category of engine asset: character, animation, etc. + /// See the documentation on `MediaFileEngineCategory`. + #[multipart(limit = "2 KiB")] + #[schema(value_type = String, format = Binary)] + engine_category: Text, + + /// Optional: Title (name) of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_title: Option>, + + /// Optional: Visibility of the scene + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_visibility: Option>, + + /// Optional: the type of animation (if this is a character or animation) + /// See the documentation on `MediaFileAnimationType`. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_animation_type: Option>, + + /// Optional: The duration, for files that are animations. + #[multipart(limit = "2 KiB")] + #[schema(value_type = Option, format = Binary)] + maybe_duration_millis: Option>, +} + +#[derive(Serialize, ToSchema)] +pub struct UploadStudioShotSuccessResponse { + pub success: bool, + pub media_file_token: MediaFileToken, +} + +/// Upload a studio render in a zip file. +#[utoipa::path( + post, + tag = "Media Files (Upload)", + path = "/v1/media_files/upload/pmx", + responses( + (status = 200, description = "Success Update", body = UploadStudioShotSuccessResponse), + (status = 400, description = "Bad input", body = MediaFileUploadError), + (status = 401, description = "Not authorized", body = MediaFileUploadError), + (status = 429, description = "Too many requests", body = MediaFileUploadError), + (status = 500, description = "Server error", body = MediaFileUploadError), + ), + params( + ( + "request" = UploadStudioShotFileForm, + description = "IF VIEWING DOCS, PLEASE SEE BOTTOM OF PAGE `UploadStudioShotFileForm` (Under 'Schema') FOR DETAILS ON FIELDS AND NULLABILITY." + ), + ) +)] +pub async fn upload_studio_shot_media_file_handler( + http_request: HttpRequest, + server_state: web::Data>, + MultipartForm(mut form): MultipartForm, +) -> Result, MediaFileUploadError> { + + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await + .map_err(|err| { + error!("MySql pool error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + // ==================== READ SESSION ==================== // + + // NB: We require a moderator to upload PMX files. + let user_session = require_moderator(&http_request, &server_state, UseDatabase::FromPool(&mut mysql_connection)) + .await + .map_err(|err| match err { + RequireModeratorError::ServerError => MediaFileUploadError::ServerError, + RequireModeratorError::NotAuthorized => MediaFileUploadError::NotAuthorized, + })?; + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + +// // ==================== READ SESSION ==================== // +// +// let maybe_user_session = server_state +// .session_checker +// .maybe_get_user_session_from_connection(&http_request, &mut mysql_connection) +// .await +// .map_err(|e| { +// error!("Session checker error: {:?}", e); +// MediaFileUploadError::ServerError +// })?; +// +// // ==================== BANNED USERS ==================== // +// +// if let Some(ref user) = maybe_user_session { +// if user.is_banned { +// return Err(MediaFileUploadError::NotAuthorized); +// } +// } + +// // ==================== RATE LIMIT ==================== // +// +// let rate_limiter = match maybe_user_session { +// None => &server_state.redis_rate_limiters.file_upload_logged_out, +// Some(ref _session) => &server_state.redis_rate_limiters.file_upload_logged_in, +// }; +// +// if let Err(_err) = rate_limiter.rate_limit_request(&http_request) { +// return Err(MediaFileUploadError::RateLimited); +// } + + // ==================== HANDLE IDEMPOTENCY ==================== // + + // TODO(bt, 2024-02-26): This should be a transaction. + let uuid_idempotency_token = form.uuid_idempotency_token.as_ref(); + + if let Err(reason) = validate_idempotency_token_format(uuid_idempotency_token) { + return Err(MediaFileUploadError::BadInput(reason)); + } + + insert_idempotency_token(uuid_idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + MediaFileUploadError::BadInput("invalid idempotency token".to_string()) + })?; + + // ==================== UPLOAD METADATA ==================== // + + let engine_category = form.engine_category.0; + + let maybe_duration_millis = form.maybe_duration_millis + .map(|duration| duration.0); + + let mut maybe_animation_type = form.maybe_animation_type + .map(|t| t.0); + + if engine_category == MediaFileEngineCategory::Expression { + // NB: Expressions are exclusively ArKit for now (and probably well into the future). + maybe_animation_type = Some(MediaFileAnimationType::ArKit); + } + + let maybe_title = form.maybe_title + .map(|title| title.trim().to_string()) + .filter(|title| !title.is_empty()); + + let creator_set_visibility = form.maybe_visibility + .map(|visibility| visibility.0) + .or_else(|| { + //maybe_user_session + // .as_ref() + // .map(|user_session| user_session.preferred_tts_result_visibility) + Some(user_session.preferred_tts_result_visibility) + }) + .unwrap_or(Visibility::default()); + + // ==================== USER DATA ==================== // + + let ip_address = get_request_ip(&http_request); + + //let maybe_user_token = maybe_user_session + // .map(|session| session.get_strongly_typed_user_token()); + + // ==================== FILE DATA ==================== // + + let maybe_filename = form.file.file_name.as_deref() + .as_deref() + .map(|filename| PathBuf::from(filename)); + + let maybe_file_extension = maybe_filename + .as_ref() + .and_then(|filename| filename.extension()) + .and_then(|ext| ext.to_str()); + + let mut file_bytes = Vec::new(); + form.file.file.read_to_end(&mut file_bytes) + .map_err(|e| { + error!("Problem reading file: {:?}", e); + MediaFileUploadError::ServerError + })?; + + match maybe_file_extension { + Some("zip") => {}, + _ => { + return Err(MediaFileUploadError::BadInput( + "unsupported file extension. Must be zip or pmx.".to_string())); + } + } + + // ==================== UPLOAD AND SAVE ==================== // + + const PREFIX : Option<&str> = Some("upload_"); + const SUFFIX : Option<&str> = Some(".pmx"); + + let pmx_details = extract_and_upload_pmx_files(&file_bytes, &server_state.public_bucket_client, PREFIX, SUFFIX) + .await + .map_err(|err| { + warn!("Extract and upload pmx error: {:?}", err); + match err { + PmxError::InvalidArchive => MediaFileUploadError::ServerErrorVerbose("invalid archive file".to_string()), + PmxError::TooManyFiles => MediaFileUploadError::ServerErrorVerbose("too many files".to_string()), + PmxError::NoPmxFile => MediaFileUploadError::ServerErrorVerbose("no pmx files".to_string()), + PmxError::UploadError => MediaFileUploadError::ServerErrorVerbose("upload error".to_string()), + PmxError::FileError => MediaFileUploadError::ServerErrorVerbose("file error".to_string()), + PmxError::ExtractionError => MediaFileUploadError::ServerErrorVerbose("zip extraction error".to_string()), + } + })?; + + // TODO(bt, 2024-02-22): This should be a transaction. + let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs { + maybe_media_class: Some(MediaFileClass::Dimensional), + media_file_type: MediaFileType::Pmx, + maybe_creator_user_token: Some(&user_session.get_strongly_typed_user_token()), + maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility, + upload_type: UploadType::Filesystem, + maybe_engine_category: Some(engine_category), + maybe_animation_type, + maybe_mime_type: Some("application/octet-stream"), + file_size_bytes: pmx_details.file_size_bytes, + maybe_duration_millis, + sha256_checksum: &pmx_details.sha256_checksum, + maybe_scene_source_media_file_token: None, + is_intermediate_system_file: false, + maybe_title: maybe_title.as_deref(), + public_bucket_directory_hash: pmx_details.pmx_public_upload_path.get_object_hash(), + maybe_public_bucket_prefix: PREFIX, + maybe_public_bucket_extension: SUFFIX, + pool: &server_state.mysql_pool, + }) + .await + .map_err(|err| { + warn!("New file creation DB error: {:?}", err); + MediaFileUploadError::ServerError + })?; + + info!("new media file id: {} token: {:?}", record_id, &token); + + Ok(Json(UploadStudioShotSuccessResponse { + success: true, + media_file_token: token, + })) +}