From f958667cc47b2c2d0efb742429bf15a292fabf29 Mon Sep 17 00:00:00 2001 From: kasisnu <5044120+kasisnu@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:02:38 +0530 Subject: [PATCH] delete media files/basic update handler --- .../queries/media_files/delete_media_file.rs | 91 +++++++++ .../src/queries/media_files/mod.rs | 3 +- .../queries/media_files/update_media_file.rs | 47 +++++ .../media_files/delete_media_file.rs | 177 ++++++++++++++++++ .../http_server/endpoints/media_files/mod.rs | 1 + .../service/web/storyteller_web/src/routes.rs | 2 + 6 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 crates/lib/mysql_queries/src/queries/media_files/delete_media_file.rs create mode 100644 crates/lib/mysql_queries/src/queries/media_files/update_media_file.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete_media_file.rs diff --git a/crates/lib/mysql_queries/src/queries/media_files/delete_media_file.rs b/crates/lib/mysql_queries/src/queries/media_files/delete_media_file.rs new file mode 100644 index 0000000000..fee2758c92 --- /dev/null +++ b/crates/lib/mysql_queries/src/queries/media_files/delete_media_file.rs @@ -0,0 +1,91 @@ +use sqlx::MySqlPool; + +use errors::AnyhowResult; +use tokens::tokens::media_files::MediaFileToken; + +pub async fn delete_media_file_as_user( + media_file_token: &MediaFileToken, + mysql_pool: &MySqlPool +) -> AnyhowResult<()> { + let _r = sqlx::query!( + r#" +UPDATE media_files +SET + user_deleted_at = CURRENT_TIMESTAMP +WHERE + token = ? +LIMIT 1 + "#, + media_file_token, + ) + .execute(mysql_pool) + .await?; + Ok(()) +} + +pub async fn delete_media_file_as_mod( + media_file_token: &MediaFileToken, + mod_user_token: &str, + mysql_pool: &MySqlPool +) -> AnyhowResult<()> { + let _r = sqlx::query!( + r#" +UPDATE media_files +SET + mod_deleted_at = CURRENT_TIMESTAMP, + + maybe_mod_user_token = ? +WHERE + token = ? +LIMIT 1 + "#, + mod_user_token, + media_file_token, + ) + .execute(mysql_pool) + .await?; + Ok(()) +} + +pub async fn undelete_media_file_as_user( + media_file_token: &MediaFileToken, + mysql_pool: &MySqlPool +) -> AnyhowResult<()> { + let _r = sqlx::query!( + r#" +UPDATE media_files +SET + user_deleted_at = NULL +WHERE + token = ? +LIMIT 1 + "#, + media_file_token, + ) + .execute(mysql_pool) + .await?; + Ok(()) +} + +pub async fn undelete_media_file_as_mod( + media_file_token: &MediaFileToken, + mod_user_token: &str, + mysql_pool: &MySqlPool +) -> AnyhowResult<()> { + let _r = sqlx::query!( + r#" +UPDATE media_files +SET + mod_deleted_at = NULL, + maybe_mod_user_token = ? +WHERE + token = ? +LIMIT 1 + "#, + mod_user_token, + media_file_token, + ) + .execute(mysql_pool) + .await?; + Ok(()) +} diff --git a/crates/lib/mysql_queries/src/queries/media_files/mod.rs b/crates/lib/mysql_queries/src/queries/media_files/mod.rs index 72f9e07feb..1e4b11a8b3 100644 --- a/crates/lib/mysql_queries/src/queries/media_files/mod.rs +++ b/crates/lib/mysql_queries/src/queries/media_files/mod.rs @@ -2,4 +2,5 @@ pub mod get_media_file; pub mod insert_media_file_from_face_animation; pub mod insert_media_file_from_zero_shot_tts; pub mod insert_media_file_from_cli_tool; -pub mod insert_media_file_from_voice_conversion; +pub mod delete_media_file; +pub mod update_media_file; \ No newline at end of file diff --git a/crates/lib/mysql_queries/src/queries/media_files/update_media_file.rs b/crates/lib/mysql_queries/src/queries/media_files/update_media_file.rs new file mode 100644 index 0000000000..f51fafffea --- /dev/null +++ b/crates/lib/mysql_queries/src/queries/media_files/update_media_file.rs @@ -0,0 +1,47 @@ +use anyhow::anyhow; +use sqlx::MySqlPool; +use enums::common::visibility::Visibility; +use enums::by_table::generic_synthetic_ids::id_category::IdCategory; + +use errors::AnyhowResult; +use tokens::tokens::media_files::MediaFileToken; +use tokens::tokens::users::UserToken; +use crate::queries::generic_synthetic_ids::transactional_increment_generic_synthetic_id::transactional_increment_generic_synthetic_id; + +pub struct UpdateMediaFileArgs<'a> { + pub media_file_token: &'a MediaFileToken, + pub maybe_creator_user_token: Option<&'a str>, + pub creator_ip_address: &'a str, + pub creator_set_visibility: &'a Visibility, + pub maybe_mod_user_token: Option<&'a str>, + pub mysql_pool: &'a MySqlPool +} + +pub async fn update_media_file(args: UpdateMediaFileArgs<'_>) -> AnyhowResult<()>{ + // TODO: enforce checks for idempotency token + let mut transaction = args.mysql_pool.begin().await?; + let query_result = sqlx::query!( + r#" + UPDATE media_files + SET + maybe_creator_user_token = ?, + creator_ip_address = ?, + creator_set_visibility = ?, + maybe_mod_user_token = ? + + WHERE token = ? + LIMIT 1 + "#, + args.maybe_creator_user_token, + args.creator_ip_address, + args.creator_set_visibility.to_str(), + args.maybe_mod_user_token, + args.media_file_token.as_str(), + ).execute(args.mysql_pool).await; + // TODO(Kasisnu): This should probably rollback + transaction.commit().await?; + match query_result { + Ok(_) => Ok(()), + Err(err) => Err(anyhow!("media_file update error: {:?}", err)), + } +} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete_media_file.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete_media_file.rs new file mode 100644 index 0000000000..79c4307745 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete_media_file.rs @@ -0,0 +1,177 @@ +use std::fmt; +use std::sync::Arc; + +use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::web::Path; +use log::{error, log, warn}; +use enums::by_table::audit_logs::audit_log_entity_action::AuditLogEntityAction::Delete; + +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 mysql_queries::queries::media_files::delete_media_file::{delete_media_file_as_mod, delete_media_file_as_user, undelete_media_file_as_mod, undelete_media_file_as_user}; +use mysql_queries::queries::media_files::get_media_file::get_media_file; +use tokens::tokens::media_files::MediaFileToken; +use crate::http_server::web_utils::response_success_helpers::simple_json_success; + + +use crate::server_state::ServerState; +use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; + +#[derive(Deserialize)] +pub struct DeleteMediaFileRequest { + set_delete: bool, + /// NB: this is only to disambiguate when a user is both a mod and an author. + as_mod: Option, +} + +/// For the URL PathInfo +#[derive(Deserialize)] +pub struct DeleteMediaFilePathInfo { + token: String, +} + +// =============== Error Response =============== + +#[derive(Debug, Serialize)] +pub enum DeleteMediaFileError { + BadInput(String), + NotFound, + NotAuthorized, + ServerError, +} + +impl ResponseError for DeleteMediaFileError { + fn status_code(&self) -> StatusCode { + match *self { + DeleteMediaFileError::BadInput(_) => StatusCode::BAD_REQUEST, + DeleteMediaFileError::NotFound => StatusCode::NOT_FOUND, + DeleteMediaFileError::NotAuthorized => StatusCode::UNAUTHORIZED, + DeleteMediaFileError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + serialize_as_json_error(self) + } +} + +// NB: Not using derive_more::Display since Clion doesn't understand it. +impl fmt::Display for DeleteMediaFileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +// =============== Handler =============== + +pub async fn delete_media_file_handler( + http_request: HttpRequest, + path: Path, + request: web::Json, + server_state: web::Data> +) -> Result{ + let maybe_user_session = server_state + .session_checker + .maybe_get_user_session(&http_request, &server_state.mysql_pool) + .await + .map_err(|e| { + warn!("Session checker error: {:?}", e); + DeleteMediaFileError::ServerError + })?; + + let user_session = match maybe_user_session { + Some(session) => session, + None => { + warn!("not logged in"); + return Err(DeleteMediaFileError::NotAuthorized); + } + }; + + let media_file_token = MediaFileToken::new(path.token.clone()); + let is_mod = user_session.can_ban_users; + + let media_file_lookup_result = get_media_file( + &media_file_token, + is_mod, + &server_state.mysql_pool, + ).await; + + let media_file = match media_file_lookup_result { + Ok(Some(media_file)) => media_file, + Ok(None) => { + warn!("MediaFile not found: {:?}", media_file_token); + return Err(DeleteMediaFileError::NotFound); + }, + Err(err) => { + warn!("Error looking up media_file: {:?}", err); + return Err(DeleteMediaFileError::ServerError); + } + }; + + //TODO(KS): Confirm this check is sound + let is_creator = media_file.maybe_creator_user_token + .is_some_and(|t| t.as_str() == &user_session.user_token); + + if !is_creator && !is_mod { + warn!("user is not allowed to delete this media_file: {}", user_session.user_token); + return Err(DeleteMediaFileError::NotAuthorized); + } + + let delete_role = delete_role_disambiguation(is_mod, is_creator, request.as_mod); + + let query_result = if request.set_delete { + match delete_role { + DeleteRole::ErrorDoNotDelete => { + warn!("user is not allowed to delete media_files: {}", user_session.user_token); + return Err(DeleteMediaFileError::NotAuthorized); + } + DeleteRole::AsUser => { + delete_media_file_as_user( + &media_file_token, + &server_state.mysql_pool + ).await + } + DeleteRole::AsMod => { + delete_media_file_as_mod( + &media_file_token, + &user_session.user_token, + &server_state.mysql_pool + ).await + } + } + } else { + match delete_role { + DeleteRole::ErrorDoNotDelete => { + warn!("user is not allowed to undelete voices: {}", user_session.user_token); + return Err(DeleteMediaFileError::NotAuthorized); + } + DeleteRole::AsUser => { + // NB: Technically only mods can see their own media_files + undelete_media_file_as_user( + &media_file_token, + &server_state.mysql_pool + ).await + } + DeleteRole::AsMod => { + undelete_media_file_as_mod( + &media_file_token, + &user_session.user_token, + &server_state.mysql_pool + ).await + } + } + }; + + match query_result { + Ok(_) => {}, + Err(err) => { + warn!("Update media_file mod approval status DB error: {:?}", err); + return Err(DeleteMediaFileError::ServerError); + } + }; + + Ok(simple_json_success()) + +} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs index a965ca7b81..014fa68854 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs @@ -1,2 +1,3 @@ pub mod drain_multipart_request; pub mod get_media_file; +pub mod delete_media_file; diff --git a/crates/service/web/storyteller_web/src/routes.rs b/crates/service/web/storyteller_web/src/routes.rs index a0d8079f49..870977024e 100644 --- a/crates/service/web/storyteller_web/src/routes.rs +++ b/crates/service/web/storyteller_web/src/routes.rs @@ -37,6 +37,7 @@ use crate::http_server::endpoints::inference_job::terminate_inference_job_handle use crate::http_server::endpoints::investor_demo::disable_demo_mode_handler::disable_demo_mode_handler; use crate::http_server::endpoints::investor_demo::enable_demo_mode_handler::enable_demo_mode_handler; use crate::http_server::endpoints::leaderboard::get_leaderboard::leaderboard_handler; +use crate::http_server::endpoints::media_files::delete_media_file::delete_media_file_handler; use crate::http_server::endpoints::media_files::get_media_file::get_media_file_handler; use crate::http_server::endpoints::media_uploads::list_user_media_uploads_of_type::list_user_media_uploads_of_type_handler; use crate::http_server::endpoints::media_uploads::upload_audio::upload_audio_handler; @@ -982,6 +983,7 @@ fn add_media_file_routes (app: App) -> App app.service(web::scope("/v1/media_files") .service(web::resource("/file/{token}") .route(web::get().to(get_media_file_handler)) + .route(web::delete().to(delete_media_file_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) )