From da05885d7d80da16275c8ad8a641ecdced9351d1 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 21 Apr 2023 05:54:10 -0700 Subject: [PATCH] don't upload duplicates --- .../media_uploads/get_media_upload_by_uuid.rs | 104 ++++++++++++++++++ .../src/queries/media_uploads/mod.rs | 1 + ...reverse_list_user_media_uploads_of_type.rs | 1 - .../endpoints/media_uploads/upload_media.rs | 45 +++++++- 4 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 crates/lib/mysql_queries/src/queries/media_uploads/get_media_upload_by_uuid.rs diff --git a/crates/lib/mysql_queries/src/queries/media_uploads/get_media_upload_by_uuid.rs b/crates/lib/mysql_queries/src/queries/media_uploads/get_media_upload_by_uuid.rs new file mode 100644 index 0000000000..0ce9b22f5e --- /dev/null +++ b/crates/lib/mysql_queries/src/queries/media_uploads/get_media_upload_by_uuid.rs @@ -0,0 +1,104 @@ +use anyhow::anyhow; +use chrono::{DateTime, Utc}; +use crate::helpers::boolean_converters::i8_to_bool; +use enums::by_table::voice_conversion_models::voice_conversion_model_type::VoiceConversionModelType; +use enums::common::visibility::Visibility; +use errors::AnyhowResult; +use log::{warn, info, error}; +use sqlx::pool::PoolConnection; +use sqlx::{MySql, MySqlPool}; +use enums::by_table::media_uploads::media_upload_type::MediaUploadType; +use tokens::files::media_upload::MediaUploadToken; +use tokens::users::user::UserToken; +use tokens::voice_conversion::model::VoiceConversionModelToken; + +pub struct MediaUploadRecord { + pub token: MediaUploadToken, + pub media_type: MediaUploadType, + + pub maybe_original_filename: Option, + + pub original_file_size_bytes: u32, + pub original_duration_millis: u32, + + pub creator_set_visibility: Visibility, + + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// Query for a media upload to see if we already uploaded it. +pub async fn get_media_upload_by_uuid( + uuid_idempotency_token: &str, + mysql_pool: &MySqlPool, +) -> AnyhowResult> { + let mut connection = mysql_pool.acquire().await?; + get_media_upload_by_uuid_with_connection( + uuid_idempotency_token, + &mut connection + ).await +} + +/// Query for a media upload to see if we already uploaded it. +pub async fn get_media_upload_by_uuid_with_connection( + uuid_idempotency_token: &str, + mysql_connection: &mut PoolConnection, +) -> AnyhowResult> { + let maybe_result = sqlx::query_as!( + RawMediaUploadRecord, + r#" +SELECT + mu.token as `token: tokens::files::media_upload::MediaUploadToken`, + mu.media_type as `media_type: enums::by_table::media_uploads::media_upload_type::MediaUploadType`, + mu.maybe_original_filename, + mu.original_file_size_bytes, + mu.original_duration_millis, + mu.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`, + mu.created_at, + mu.updated_at +FROM media_uploads as mu +WHERE + mu.uuid_idempotency_token = ? + AND mu.user_deleted_at IS NULL + AND mu.mod_deleted_at IS NULL + "#, + uuid_idempotency_token, + ) + .fetch_one(mysql_connection) + .await; + + match maybe_result { + Err(err) => match err { + sqlx::Error::RowNotFound => Ok(None), + _ => { + error!("list media uploads db error: {:?}", err); + Err(anyhow!("error with query: {:?}", err)) + } + }, + Ok(upload) => Ok(Some(MediaUploadRecord { + token: upload.token, + media_type: upload.media_type, + maybe_original_filename: upload.maybe_original_filename, + original_file_size_bytes: upload.original_file_size_bytes as u32, + original_duration_millis: upload.original_duration_millis as u32, + creator_set_visibility: upload.creator_set_visibility, + created_at: upload.created_at, + updated_at: upload.updated_at, + })) + } +} + +struct RawMediaUploadRecord { + pub token: MediaUploadToken, + pub media_type: MediaUploadType, + + pub maybe_original_filename: Option, + + pub original_file_size_bytes: i32, + pub original_duration_millis: i32, + + pub creator_set_visibility: Visibility, + + pub created_at: DateTime, + pub updated_at: DateTime, +} diff --git a/crates/lib/mysql_queries/src/queries/media_uploads/mod.rs b/crates/lib/mysql_queries/src/queries/media_uploads/mod.rs index d5d1fffc99..c7c8330d26 100644 --- a/crates/lib/mysql_queries/src/queries/media_uploads/mod.rs +++ b/crates/lib/mysql_queries/src/queries/media_uploads/mod.rs @@ -1,2 +1,3 @@ +pub mod get_media_upload_by_uuid; pub mod insert_media_upload; pub mod reverse_list_user_media_uploads_of_type; \ No newline at end of file diff --git a/crates/lib/mysql_queries/src/queries/media_uploads/reverse_list_user_media_uploads_of_type.rs b/crates/lib/mysql_queries/src/queries/media_uploads/reverse_list_user_media_uploads_of_type.rs index e579664281..027e52b4e5 100644 --- a/crates/lib/mysql_queries/src/queries/media_uploads/reverse_list_user_media_uploads_of_type.rs +++ b/crates/lib/mysql_queries/src/queries/media_uploads/reverse_list_user_media_uploads_of_type.rs @@ -12,7 +12,6 @@ use tokens::files::media_upload::MediaUploadToken; use tokens::users::user::UserToken; use tokens::voice_conversion::model::VoiceConversionModelToken; -#[derive(Serialize)] pub struct MediaUploadRecord { pub token: MediaUploadToken, pub media_type: MediaUploadType, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs index fde219552c..7addd6dd13 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs @@ -22,7 +22,7 @@ use files::hash::hash_bytes_sha2; use files::mimetype::{get_mimetype_for_bytes, get_mimetype_for_bytes_or_default}; use http_server_common::request::get_request_ip::get_request_ip; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; -use log::{info, warn, log}; +use log::{info, warn, log, error}; use media::decode_basic_audio_info::decode_basic_audio_info; use regex::Regex; use sqlx::error::DatabaseError; @@ -35,6 +35,7 @@ use symphonia::core::formats::FormatOptions; use symphonia::core::io::{MediaSourceStream, ReadOnlySource}; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; +use mysql_queries::queries::media_uploads::get_media_upload_by_uuid::{get_media_upload_by_uuid, get_media_upload_by_uuid_with_connection}; use tokens::files::media_upload::MediaUploadToken; #[derive(Serialize)] @@ -81,11 +82,19 @@ pub async fn upload_media_handler( mut multipart_payload: Multipart, ) -> Result { + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await + .map_err(|err| { + warn!("MySql pool error: {:?}", err); + UploadMediaError::ServerError + })?; + // ==================== READ SESSION ==================== // let maybe_user_session = server_state .session_checker - .maybe_get_user_session(&http_request, &server_state.mysql_pool) + .maybe_get_user_session_from_connection(&http_request, &mut mysql_connection) .await .map_err(|e| { warn!("Session checker error: {:?}", e); @@ -125,6 +134,27 @@ pub async fn upload_media_handler( let uuid_idempotency_token = upload_media_request.uuid_idempotency_token .ok_or(UploadMediaError::BadInput("no uuid".to_string()))?; + let maybe_existing_upload = + get_media_upload_by_uuid_with_connection(&uuid_idempotency_token, &mut mysql_connection) + .await; + + match maybe_existing_upload { + Err(err) => { + error!("Error checking for previous upload: {:?}", err); + return Err(UploadMediaError::ServerError); + } + Ok(Some(upload)) => { + // File already uploaded and frontend didn't protect us. + return render_ok_response(UploadMediaSuccessResponse { + success: true, + upload_token: upload.token, + }); + } + Ok(None) => { + // Proceed. + } + } + if let Err(reason) = validate_idempotency_token_format(&uuid_idempotency_token) { return Err(UploadMediaError::BadInput(reason)); } @@ -271,15 +301,18 @@ pub async fn upload_media_handler( UploadMediaError::ServerError })?; - let response = UploadMediaSuccessResponse { + render_ok_response(UploadMediaSuccessResponse { success: true, upload_token: token, - }; + }) +} + +fn render_ok_response(response: UploadMediaSuccessResponse) -> Result { let body = serde_json::to_string(&response) .map_err(|e| UploadMediaError::ServerError)?; - Ok(HttpResponse::Ok() + return Ok(HttpResponse::Ok() .content_type("application/json") - .body(body)) + .body(body)); }