diff --git a/crates/api_clients/artcraft_api_defs/src/common/responses/mod.rs b/crates/api_clients/artcraft_api_defs/src/common/responses/mod.rs index 14e1ab8093..cf47d56f64 100644 --- a/crates/api_clients/artcraft_api_defs/src/common/responses/mod.rs +++ b/crates/api_clients/artcraft_api_defs/src/common/responses/mod.rs @@ -3,4 +3,5 @@ pub mod job_details; pub mod media_file_cover_image_details; pub mod media_links; pub mod simple_entity_stats; +pub mod simple_generic_json_success; pub mod user_details_light; \ No newline at end of file diff --git a/crates/api_clients/artcraft_api_defs/src/common/responses/simple_generic_json_success.rs b/crates/api_clients/artcraft_api_defs/src/common/responses/simple_generic_json_success.rs new file mode 100644 index 0000000000..297ad544d4 --- /dev/null +++ b/crates/api_clients/artcraft_api_defs/src/common/responses/simple_generic_json_success.rs @@ -0,0 +1,7 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct SimpleGenericJsonSuccess { + pub success: bool, +} diff --git a/crates/api_clients/artcraft_api_defs/src/media_file/delete_media_file.rs b/crates/api_clients/artcraft_api_defs/src/media_file/delete_media_file.rs new file mode 100644 index 0000000000..70ca5e02c1 --- /dev/null +++ b/crates/api_clients/artcraft_api_defs/src/media_file/delete_media_file.rs @@ -0,0 +1,19 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; +use tokens::tokens::media_files::MediaFileToken; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct DeleteMediaFileRequest { + pub set_delete: bool, + + /// NB: this is only to disambiguate when a user is both a mod and an author. + pub as_mod: Option, +} + +/// For the URL PathInfo +#[derive(Serialize, Deserialize, ToSchema)] +pub struct DeleteMediaFilePathInfo { + pub token: MediaFileToken, +} + + diff --git a/crates/api_clients/artcraft_api_defs/src/media_file/mod.rs b/crates/api_clients/artcraft_api_defs/src/media_file/mod.rs index a14a499081..a4572e00a0 100644 --- a/crates/api_clients/artcraft_api_defs/src/media_file/mod.rs +++ b/crates/api_clients/artcraft_api_defs/src/media_file/mod.rs @@ -1 +1,2 @@ +pub mod delete_media_file; pub mod list_batch_generated_media_files; \ No newline at end of file diff --git a/crates/api_clients/storyteller_client/src/endpoints/media_files/delete_media_file.rs b/crates/api_clients/storyteller_client/src/endpoints/media_files/delete_media_file.rs new file mode 100644 index 0000000000..a430ad7eef --- /dev/null +++ b/crates/api_clients/storyteller_client/src/endpoints/media_files/delete_media_file.rs @@ -0,0 +1,37 @@ +use crate::credentials::storyteller_credential_set::StorytellerCredentialSet; +use crate::error::storyteller_error::StorytellerError; +use crate::utils::api_host::ApiHost; +use crate::utils::basic_json_delete_request::basic_json_delete_request; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; +use artcraft_api_defs::media_file::delete_media_file::DeleteMediaFileRequest; +use artcraft_api_defs::subscriptions::get_session_subscription::GetSessionSubscriptionResponse; +use enums::common::payments_namespace::PaymentsNamespace; +use log::debug; +use tokens::tokens::media_files::MediaFileToken; + +pub async fn delete_media_file( + api_host: &ApiHost, + maybe_creds: Option<&StorytellerCredentialSet>, + media_file_token: &MediaFileToken, +) -> Result { + let url_path = get_url_path(media_file_token); + + debug!("Requesting {:?}", &url_path); + + // NB: We probably don't need to expose these options from FakeYou anymore. + let request = DeleteMediaFileRequest { + set_delete: true, + as_mod: None, + }; + + Ok(basic_json_delete_request( + api_host, + &url_path, + maybe_creds, + request, + ).await?) +} + +fn get_url_path(token: &MediaFileToken) -> String { + format!("/v1/media_files/file/{}", token.as_str()) +} diff --git a/crates/api_clients/storyteller_client/src/endpoints/media_files/mod.rs b/crates/api_clients/storyteller_client/src/endpoints/media_files/mod.rs index 8f322cce5e..d7cf4c1378 100644 --- a/crates/api_clients/storyteller_client/src/endpoints/media_files/mod.rs +++ b/crates/api_clients/storyteller_client/src/endpoints/media_files/mod.rs @@ -1,3 +1,4 @@ +pub mod delete_media_file; pub mod get_media_file; pub mod list_batch_generated_redux_media_files; pub mod upload_image_media_file_from_bytes; diff --git a/crates/api_clients/storyteller_client/src/endpoints/subscriptions/get_session_subscription.rs b/crates/api_clients/storyteller_client/src/endpoints/subscriptions/get_session_subscription.rs index 7b44a8ee87..e0fb5b0142 100644 --- a/crates/api_clients/storyteller_client/src/endpoints/subscriptions/get_session_subscription.rs +++ b/crates/api_clients/storyteller_client/src/endpoints/subscriptions/get_session_subscription.rs @@ -11,7 +11,7 @@ pub async fn get_session_subscription( maybe_creds: Option<&StorytellerCredentialSet>, payments_namespace: PaymentsNamespace ) -> Result { - let url_path = get_url_path(api_host, payments_namespace); + let url_path = get_url_path(payments_namespace); debug!("Requesting {:?}", &url_path); @@ -22,7 +22,7 @@ pub async fn get_session_subscription( ).await?) } -fn get_url_path(api_host: &ApiHost, payments_namespace: PaymentsNamespace) -> String { +fn get_url_path(payments_namespace: PaymentsNamespace) -> String { let payments_namespace = payments_namespace.to_str(); format!("/v1/subscriptions/namespace/{}", payments_namespace) } diff --git a/crates/api_clients/storyteller_client/src/utils/basic_json_delete_request.rs b/crates/api_clients/storyteller_client/src/utils/basic_json_delete_request.rs new file mode 100644 index 0000000000..05f3e42bf6 --- /dev/null +++ b/crates/api_clients/storyteller_client/src/utils/basic_json_delete_request.rs @@ -0,0 +1,71 @@ +use crate::credentials::storyteller_credential_set::StorytellerCredentialSet; +use crate::error::api_error::ApiError; +use crate::error::client_error::ClientError; +use crate::error::storyteller_error::StorytellerError; +use crate::utils::api_host::ApiHost; +use crate::utils::constants::{APPLICATION_JSON, USER_AGENT}; +use crate::utils::filter_bad_response::filter_bad_response; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH}; +use log::{debug, info}; +use reqwest::Client; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +pub async fn basic_json_delete_request( + api_host: &ApiHost, + route_path: &str, + maybe_creds: Option<&StorytellerCredentialSet>, + request: Req, +) -> Result { + + let url = get_route(api_host, route_path); + + info!("DELETE {:?}", &url); + + let client = Client::builder() + .gzip(true) + .build() + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + let mut request_builder = client.delete(url) + .header("User-Agent", USER_AGENT) + .header("Accept", APPLICATION_JSON) + .header("Content-Type", APPLICATION_JSON); + + if let Some(creds) = maybe_creds { + if let Some(header) = &creds.maybe_as_cookie_header() { + request_builder = request_builder.header("Cookie", header); + } + } + + let request_body = serde_json::to_string(&request) + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + debug!("Request body: {:?}", request_body); + + let response = request_builder + .body(request_body) + .send() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response = filter_bad_response(response) + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response_body = &response.text() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + debug!("Response body: {:?}", response_body); + + let response = serde_json::from_str(&response_body) + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + Ok(response) +} + +fn get_route(api_host: &ApiHost, route_path: &str) -> String { + let api_hostname = api_host.to_api_hostname_and_scheme(); + format!("{}{}", api_hostname, route_path) +} diff --git a/crates/api_clients/storyteller_client/src/utils/mod.rs b/crates/api_clients/storyteller_client/src/utils/mod.rs index b7bc167ccf..532b39c970 100644 --- a/crates/api_clients/storyteller_client/src/utils/mod.rs +++ b/crates/api_clients/storyteller_client/src/utils/mod.rs @@ -1,8 +1,9 @@ +pub (crate) mod basic_json_delete_request; +pub (crate) mod basic_json_get_request; +pub (crate) mod basic_json_post_request; +pub (crate) mod basic_query_string_post_request; +pub (crate) mod filter_bad_response; +pub (crate) mod http_get_anonymous; pub mod api_host; -pub mod basic_json_get_request; -pub mod basic_json_post_request; -pub mod basic_query_string_post_request; pub mod constants; -pub mod filter_bad_response; -pub mod http_get_anonymous; pub mod status_codes; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/media_files/media_file_delete_command.rs b/crates/desktop/artcraft/src/core/commands/media_files/media_file_delete_command.rs new file mode 100644 index 0000000000..98346fc6ff --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/media_files/media_file_delete_command.rs @@ -0,0 +1,75 @@ +use crate::core::commands::response::shorthand::ResponseOrErrorMessage; +use crate::core::commands::response::success_response_wrapper::SerializeMarker; +use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::state::provider_priority::{Provider, ProviderPriorityStore}; +use crate::core::state::task_database::TaskDatabase; +use crate::services::fal::state::fal_credential_manager::FalCredentialManager; +use crate::services::fal::state::fal_task_queue::FalTaskQueue; +use crate::services::sora::state::sora_task_queue::SoraTaskQueue; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; +use errors::AnyhowResult; +use log::{error, info, warn}; +use serde_derive::{Deserialize, Serialize}; +use storyteller_client::endpoints::media_files::delete_media_file::delete_media_file; +use tauri::{AppHandle, State}; +use tokens::tokens::media_files::MediaFileToken; + +#[derive(Deserialize)] +pub struct MediaFileDeleteRequest { + /// REQUIRED. + /// Media file to delete. The user must own the file. + pub media_file_token: MediaFileToken, +} + +#[derive(Serialize)] +pub struct MediaFileDeleteSuccessResponse { +} + +impl SerializeMarker for MediaFileDeleteSuccessResponse {} + +#[tauri::command] +pub async fn media_file_delete_command( + app: AppHandle, + request: MediaFileDeleteRequest, + app_env_configs: State<'_, AppEnvConfigs>, + storyteller_creds_manager: State<'_, StorytellerCredentialManager>, +) -> ResponseOrErrorMessage { + + info!("media_file_delete_command called"); + + let result = handle_request( + request, + &app, + &app_env_configs, + &storyteller_creds_manager, + ).await; + + if let Err(err) = result { + error!("media_file_delete_command failed: {:?}", err); + return Err("delete failed".into()) + } + + Ok(MediaFileDeleteSuccessResponse{}.into()) +} + +pub async fn handle_request( + request: MediaFileDeleteRequest, + app: &AppHandle, + app_env_configs: &AppEnvConfigs, + storyteller_creds_manager: &StorytellerCredentialManager, +) -> AnyhowResult<()> { + + let creds = storyteller_creds_manager.get_credentials()?; + + let _result = delete_media_file( + &app_env_configs.storyteller_host, + creds.as_ref(), + &request.media_file_token + ).await?; + + // TODO: Send event to frontend that the file was deleted. + + Ok(()) +} diff --git a/crates/desktop/artcraft/src/core/commands/media_files/mod.rs b/crates/desktop/artcraft/src/core/commands/media_files/mod.rs new file mode 100644 index 0000000000..c3bf63aa99 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/media_files/mod.rs @@ -0,0 +1 @@ +pub mod media_file_delete_command; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/mod.rs b/crates/desktop/artcraft/src/core/commands/mod.rs index 2e81047e07..9a4a2135e1 100644 --- a/crates/desktop/artcraft/src/core/commands/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/mod.rs @@ -4,6 +4,7 @@ pub mod enqueue; pub mod flip_image; pub mod get_app_info_command; pub mod load_without_cors_command; +pub mod media_files; pub mod platform_info_command; pub mod providers; pub mod response; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/lib.rs b/crates/desktop/artcraft/src/lib.rs index 58d0de8daa..14caf37a0f 100644 --- a/crates/desktop/artcraft/src/lib.rs +++ b/crates/desktop/artcraft/src/lib.rs @@ -14,6 +14,7 @@ use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command use crate::core::commands::flip_image::flip_image; use crate::core::commands::get_app_info_command::get_app_info_command; use crate::core::commands::load_without_cors_command::load_without_cors_command; +use crate::core::commands::media_files::media_file_delete_command::media_file_delete_command; use crate::core::commands::platform_info_command::platform_info_command; use crate::core::commands::providers::get_provider_order_command::get_provider_order_command; use crate::core::commands::providers::set_provider_order_command::set_provider_order_command; @@ -190,6 +191,7 @@ pub fn run() { get_app_preferences_command, get_fal_api_key_command, get_provider_order_command, + media_file_delete_command, load_without_cors_command, midjourney_clear_credentials_command, midjourney_get_credential_info_command, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete/delete_media_file_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete/delete_media_file_handler.rs index 673c2c0d0d..b9a314d57d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete/delete_media_file_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/delete/delete_media_file_handler.rs @@ -1,66 +1,22 @@ use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::web::Path; -use actix_web::{web, HttpRequest, HttpResponse}; -use log::warn; -use utoipa::ToSchema; - -use http_server_common::response::serialize_as_json_error::serialize_as_json_error; -use mysql_queries::queries::media_files::delete::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::get_media_file::get_media_file; -use tokens::tokens::media_files::MediaFileToken; - +use crate::http_server::common_responses::common_web_error::CommonWebError; use crate::http_server::web_utils::response_success_helpers::simple_json_success; use crate::state::server_state::ServerState; use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; - -#[derive(Deserialize, ToSchema)] -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, ToSchema)] -pub struct DeleteMediaFilePathInfo { - token: MediaFileToken, -} - -// =============== Error Response =============== - -#[derive(Debug, Serialize, ToSchema)] -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) - } -} +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::web::{Json, Path}; +use actix_web::{web, HttpRequest, HttpResponse}; +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; +use artcraft_api_defs::media_file::delete_media_file::{DeleteMediaFilePathInfo, DeleteMediaFileRequest}; +use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use log::warn; +use mysql_queries::queries::media_files::delete::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::get_media_file::get_media_file; +use tokens::tokens::media_files::MediaFileToken; +use utoipa::ToSchema; // =============== Handler =============== @@ -71,9 +27,9 @@ impl fmt::Display for DeleteMediaFileError { path = "/v1/media_files/file/{token}", responses( (status = 200, description = "Success Delete", body = SimpleGenericJsonSuccess), - (status = 400, description = "Bad input", body = DeleteMediaFileError), - (status = 401, description = "Not authorized", body = DeleteMediaFileError), - (status = 500, description = "Server error", body = DeleteMediaFileError), + (status = 400, description = "Bad input", body = CommonWebError), + (status = 401, description = "Not authorized", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError), ), params( ("request" = DeleteMediaFileRequest, description = "Payload for Request"), @@ -83,23 +39,23 @@ impl fmt::Display for DeleteMediaFileError { pub async fn delete_media_file_handler( http_request: HttpRequest, path: Path, - request: web::Json, + request: Json, server_state: web::Data> -) -> Result{ +) -> Result, CommonWebError> { 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 + CommonWebError::ServerError })?; let user_session = match maybe_user_session { Some(session) => session, None => { warn!("not logged in"); - return Err(DeleteMediaFileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } }; @@ -115,11 +71,11 @@ pub async fn delete_media_file_handler( Ok(Some(media_file)) => media_file, Ok(None) => { warn!("MediaFile not found: {:?}", path.token); - return Err(DeleteMediaFileError::NotFound); + return Err(CommonWebError::NotFound); }, Err(err) => { warn!("Error looking up media_file: {:?}", err); - return Err(DeleteMediaFileError::ServerError); + return Err(CommonWebError::ServerError); } }; @@ -128,7 +84,7 @@ pub async fn delete_media_file_handler( if !is_creator && !is_mod { warn!("user is not allowed to delete this media_file: {:?}", user_session.user_token); - return Err(DeleteMediaFileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } let delete_role = delete_role_disambiguation(is_mod, is_creator, request.as_mod); @@ -137,7 +93,7 @@ pub async fn delete_media_file_handler( match delete_role { DeleteRole::ErrorDoNotDelete => { warn!("user is not allowed to delete media_files: {:?}", user_session.user_token); - return Err(DeleteMediaFileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } DeleteRole::AsUser => { delete_media_file_as_user( @@ -157,7 +113,7 @@ pub async fn delete_media_file_handler( match delete_role { DeleteRole::ErrorDoNotDelete => { warn!("user is not allowed to undelete voices: {:?}", user_session.user_token); - return Err(DeleteMediaFileError::NotAuthorized); + return Err(CommonWebError::NotAuthorized); } DeleteRole::AsUser => { // NB: Technically only mods can see their own media_files @@ -180,9 +136,11 @@ pub async fn delete_media_file_handler( Ok(_) => {}, Err(err) => { warn!("Update media_file mod approval status DB error: {:?}", err); - return Err(DeleteMediaFileError::ServerError); + return Err(CommonWebError::ServerError); } }; - Ok(simple_json_success()) + Ok(Json(SimpleGenericJsonSuccess{ + success: true + })) } diff --git a/crates/service/web/storyteller_web/src/http_server/web_utils/response_success_helpers.rs b/crates/service/web/storyteller_web/src/http_server/web_utils/response_success_helpers.rs index 9153e72461..f6e41cc3d0 100644 --- a/crates/service/web/storyteller_web/src/http_server/web_utils/response_success_helpers.rs +++ b/crates/service/web/storyteller_web/src/http_server/web_utils/response_success_helpers.rs @@ -1,10 +1,5 @@ use actix_web::HttpResponse; -use utoipa::ToSchema; - -#[derive(Serialize,ToSchema)] -pub struct SimpleGenericJsonSuccess { - pub success: bool, -} +use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess; pub fn simple_json_success() -> HttpResponse { let response = SimpleGenericJsonSuccess {