Compare commits

...
Author SHA1 Message Date
Brandon Thomas a022bd765f ignore build report 2025-09-30 04:00:03 -04:00
Brandon Thomas 25aa77fff1 delete command 2025-09-30 03:59:21 -04:00
Brandon Thomas a32eb6c64b call tauri delete instead 2025-09-30 03:59:08 -04:00
Brandon Thomas 2937daef92 fix delete 2025-09-30 03:42:48 -04:00
21 changed files with 295 additions and 90 deletions
+3
View File
@@ -45,6 +45,9 @@ crates/lib/sqlite_queries/target/
# Javascript build directories
node_modules/
# Frontend build outputs
frontend/apps/editor3d/bundle-report.html
# Vim noise
*.swo
*.swp
@@ -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;
@@ -0,0 +1,7 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct SimpleGenericJsonSuccess {
pub success: bool,
}
@@ -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<bool>,
}
/// For the URL PathInfo
#[derive(Serialize, Deserialize, ToSchema)]
pub struct DeleteMediaFilePathInfo {
pub token: MediaFileToken,
}
@@ -1 +1,2 @@
pub mod delete_media_file;
pub mod list_batch_generated_media_files;
@@ -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<SimpleGenericJsonSuccess, StorytellerError> {
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())
}
@@ -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;
@@ -11,7 +11,7 @@ pub async fn get_session_subscription(
maybe_creds: Option<&StorytellerCredentialSet>,
payments_namespace: PaymentsNamespace
) -> Result<GetSessionSubscriptionResponse, StorytellerError> {
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)
}
@@ -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<Req: Serialize, Res: DeserializeOwned>(
api_host: &ApiHost,
route_path: &str,
maybe_creds: Option<&StorytellerCredentialSet>,
request: Req,
) -> Result<Res, StorytellerError> {
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)
}
@@ -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;
@@ -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<MediaFileDeleteSuccessResponse> {
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(())
}
@@ -0,0 +1 @@
pub mod media_file_delete_command;
@@ -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;
+2
View File
@@ -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,
@@ -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<bool>,
}
/// 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<DeleteMediaFilePathInfo>,
request: web::Json<DeleteMediaFileRequest>,
request: Json<DeleteMediaFileRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, DeleteMediaFileError>{
) -> Result<Json<SimpleGenericJsonSuccess>, 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
}))
}
@@ -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 {
@@ -16,6 +16,7 @@ import {
isActionReminderOpen,
} from "@storyteller/ui-action-reminder-modal";
import { MediaFilesApi } from "@storyteller/api";
import { MediaFileDelete } from "@storyteller/tauri-api";
type ModalMode = "select" | "view";
@@ -62,13 +63,16 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
secondaryActionText: "Cancel",
primaryActionBtnClassName: "bg-red text-white hover:bg-red/90",
onPrimaryAction: async () => {
console.log(">>> Delete media file via Tauri: ", item.id);
try {
const res = await mediaFilesApi.DeleteMediaFileByToken({
mediaFileToken: item.id,
});
if (res.success) {
onDeleted?.(item.id);
}
await MediaFileDelete(item.id);
onDeleted?.(item.id);
//const res = await mediaFilesApi.DeleteMediaFileByToken({
// mediaFileToken: item.id,
//});
//if (res.success) {
// onDeleted?.(item.id);
//}
} finally {
isActionReminderOpen.value = false;
}
@@ -23,6 +23,9 @@
{
"path": "../../api"
},
{
"path": "../../tauri-api"
},
{
"path": "../action-reminder-modal"
},
@@ -64,6 +64,9 @@
{
"path": "../../api/tsconfig.lib.json"
},
{
"path": "../../tauri-api/tsconfig.lib.json"
},
{
"path": "../action-reminder-modal/tsconfig.lib.json"
},
+1
View File
@@ -1,5 +1,6 @@
export * from "./lib/artcraft/ArtcraftGetCredits.js";
export * from "./lib/artcraft/ArtcraftGetSubscription.js";
export * from "./lib/artcraft/media_files/MediaFileDelete.js";
export * from "./lib/common/CommandStatus.js";
export * from "./lib/enqueue/EnqueueContextualEditImage.js";
export * from "./lib/enqueue/EnqueueImageBgRemovalCommand.js";
@@ -0,0 +1,21 @@
import { invoke } from "@tauri-apps/api/core";
import { CommandResult } from "../../common/CommandStatus";
export interface MediaFileDeleteSuccess extends CommandResult {
payload: MediaFileDeletePayload;
}
export interface MediaFileDeletePayload {
}
export const MediaFileDelete = async (media_file_token: string) : Promise<MediaFileDeleteSuccess> => {
try {
return await invoke("media_file_delete_command", {
request: {
media_file_token: media_file_token
}
}) as MediaFileDeleteSuccess;
} catch (error) {
throw error;
}
}