From 93001e3da76b779371c3c6a633aa0881f11e6a0f Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 15 Aug 2026 11:01:41 -0700 Subject: [PATCH] Extract generic model writes into yaak::models_ops (#552) --- .../yaak-app-client/src/models_ext.rs | 59 +----------- crates/yaak/src/lib.rs | 1 + crates/yaak/src/models_ops.rs | 89 +++++++++++++++++++ 3 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 crates/yaak/src/models_ops.rs diff --git a/crates-tauri/yaak-app-client/src/models_ext.rs b/crates-tauri/yaak-app-client/src/models_ext.rs index 701d88a5..92b2b2dc 100644 --- a/crates-tauri/yaak-app-client/src/models_ext.rs +++ b/crates-tauri/yaak-app-client/src/models_ext.rs @@ -144,28 +144,10 @@ pub(crate) fn models_upsert( window: WebviewWindow, model: AnyModel, ) -> Result { - use yaak_models::error::Error::GenericError; - let db = window.db(); let blobs = window.blob_manager(); let source = &UpdateSource::from_window_label(window.label()); - let id = match model { - AnyModel::CookieJar(m) => db.upsert_cookie_jar(&m, source)?.id, - AnyModel::Environment(m) => db.upsert_environment(&m, source)?.id, - AnyModel::Folder(m) => db.upsert_folder(&m, source)?.id, - AnyModel::GrpcRequest(m) => db.upsert_grpc_request(&m, source)?.id, - AnyModel::HttpRequest(m) => db.upsert_http_request(&m, source)?.id, - AnyModel::HttpResponse(m) => db.upsert_http_response(&m, source, &blobs)?.id, - AnyModel::KeyValue(m) => db.upsert_key_value(&m, source)?.id, - AnyModel::Plugin(m) => db.upsert_plugin(&m, source)?.id, - AnyModel::Settings(m) => db.upsert_settings(&m, source)?.id, - AnyModel::WebsocketRequest(m) => db.upsert_websocket_request(&m, source)?.id, - AnyModel::Workspace(m) => db.upsert_workspace(&m, source)?.id, - AnyModel::WorkspaceMeta(m) => db.upsert_workspace_meta(&m, source)?.id, - a => return Err(GenericError(format!("Cannot upsert AnyModel {a:?})"))), - }; - - Ok(id) + yaak::models_ops::upsert_model(&db, &blobs, model, source) } // Async so cascading deletes (e.g. a workspace with thousands of requests) run on a @@ -181,21 +163,7 @@ pub(crate) async fn models_delete( // Use transaction for deletions because it might recurse window.with_tx(|tx| { let source = &UpdateSource::from_window_label(window.label()); - let id = match model { - AnyModel::CookieJar(m) => tx.delete_cookie_jar(&m, source)?.id, - AnyModel::Environment(m) => tx.delete_environment(&m, source)?.id, - AnyModel::Folder(m) => tx.delete_folder(&m, source)?.id, - AnyModel::GrpcConnection(m) => tx.delete_grpc_connection(&m, source)?.id, - AnyModel::GrpcRequest(m) => tx.delete_grpc_request(&m, source)?.id, - AnyModel::HttpRequest(m) => tx.delete_http_request(&m, source)?.id, - AnyModel::HttpResponse(m) => tx.delete_http_response(&m, source, &blobs)?.id, - AnyModel::Plugin(m) => tx.delete_plugin(&m, source)?.id, - AnyModel::WebsocketConnection(m) => tx.delete_websocket_connection(&m, source)?.id, - AnyModel::WebsocketRequest(m) => tx.delete_websocket_request(&m, source)?.id, - AnyModel::Workspace(m) => tx.delete_workspace(&m, source, &blobs)?.id, - a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))), - }; - Ok(id) + yaak::models_ops::delete_model(tx, &blobs, model, source) }) }) .await @@ -207,31 +175,10 @@ pub(crate) fn models_duplicate( model_type: String, model_id: String, ) -> Result { - use yaak_models::error::Error::GenericError; - // Use transaction for duplications because it might recurse window.with_tx(|tx| { let source = &UpdateSource::from_window_label(window.label()); - // Fetch the model fresh from the DB so the duplicate doesn't come from - // a stale frontend snapshot - let id = match model_type.as_str() { - "environment" => { - tx.duplicate_environment(&tx.get_environment(&model_id)?, source)?.id - } - "folder" => tx.duplicate_folder(&tx.get_folder(&model_id)?, source)?.id, - "grpc_request" => { - tx.duplicate_grpc_request(&tx.get_grpc_request(&model_id)?, source)?.id - } - "http_request" => { - tx.duplicate_http_request(&tx.get_http_request(&model_id)?, source)?.id - } - "websocket_request" => { - tx.duplicate_websocket_request(&tx.get_websocket_request(&model_id)?, source)?.id - } - t => return Err(GenericError(format!("Cannot duplicate model type {t}"))), - }; - - Ok(id) + yaak::models_ops::duplicate_model(tx, &model_type, &model_id, source) }) } diff --git a/crates/yaak/src/lib.rs b/crates/yaak/src/lib.rs index 7bc790e8..c26c2acc 100644 --- a/crates/yaak/src/lib.rs +++ b/crates/yaak/src/lib.rs @@ -1,6 +1,7 @@ pub mod error; pub mod export; pub mod import; +pub mod models_ops; pub mod plugin_events; pub mod render; pub mod send; diff --git a/crates/yaak/src/models_ops.rs b/crates/yaak/src/models_ops.rs new file mode 100644 index 00000000..ee9c1291 --- /dev/null +++ b/crates/yaak/src/models_ops.rs @@ -0,0 +1,89 @@ +//! Generic model writes, shared by every host. +//! +//! `upsert`, `delete` and `duplicate` take an `AnyModel` and fan out to the +//! typed query for its variant. That fan-out is long, mechanical, and has to +//! grow a new arm every time a model is added — exactly the code that should +//! not exist twice. The host supplies the database handles and the +//! `UpdateSource` identifying who is writing; nothing here knows whether the +//! caller is a desktop window or an HTTP request. + +use yaak_models::blob_manager::BlobManager; +use yaak_models::client_db::ClientDb; +use yaak_models::error::Error::GenericError; +use yaak_models::error::Result; +use yaak_models::models::AnyModel; +use yaak_models::util::UpdateSource; + +pub fn upsert_model( + db: &ClientDb, + blobs: &BlobManager, + model: AnyModel, + source: &UpdateSource, +) -> Result { + let id = match model { + AnyModel::CookieJar(m) => db.upsert_cookie_jar(&m, source)?.id, + AnyModel::Environment(m) => db.upsert_environment(&m, source)?.id, + AnyModel::Folder(m) => db.upsert_folder(&m, source)?.id, + AnyModel::GrpcRequest(m) => db.upsert_grpc_request(&m, source)?.id, + AnyModel::HttpRequest(m) => db.upsert_http_request(&m, source)?.id, + AnyModel::HttpResponse(m) => db.upsert_http_response(&m, source, blobs)?.id, + AnyModel::KeyValue(m) => db.upsert_key_value(&m, source)?.id, + AnyModel::Plugin(m) => db.upsert_plugin(&m, source)?.id, + AnyModel::Settings(m) => db.upsert_settings(&m, source)?.id, + AnyModel::WebsocketRequest(m) => db.upsert_websocket_request(&m, source)?.id, + AnyModel::Workspace(m) => db.upsert_workspace(&m, source)?.id, + AnyModel::WorkspaceMeta(m) => db.upsert_workspace_meta(&m, source)?.id, + a => return Err(GenericError(format!("Cannot upsert AnyModel {a:?})"))), + }; + + Ok(id) +} + +/// Deletes cascade, so callers run this inside a transaction. +pub fn delete_model( + tx: &ClientDb, + blobs: &BlobManager, + model: AnyModel, + source: &UpdateSource, +) -> Result { + let id = match model { + AnyModel::CookieJar(m) => tx.delete_cookie_jar(&m, source)?.id, + AnyModel::Environment(m) => tx.delete_environment(&m, source)?.id, + AnyModel::Folder(m) => tx.delete_folder(&m, source)?.id, + AnyModel::GrpcConnection(m) => tx.delete_grpc_connection(&m, source)?.id, + AnyModel::GrpcRequest(m) => tx.delete_grpc_request(&m, source)?.id, + AnyModel::HttpRequest(m) => tx.delete_http_request(&m, source)?.id, + AnyModel::HttpResponse(m) => tx.delete_http_response(&m, source, blobs)?.id, + AnyModel::Plugin(m) => tx.delete_plugin(&m, source)?.id, + AnyModel::WebsocketConnection(m) => tx.delete_websocket_connection(&m, source)?.id, + AnyModel::WebsocketRequest(m) => tx.delete_websocket_request(&m, source)?.id, + AnyModel::Workspace(m) => tx.delete_workspace(&m, source, blobs)?.id, + a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))), + }; + + Ok(id) +} + +/// Duplicates recurse, so callers run this inside a transaction. +/// +/// The model is re-read from the database rather than taken from the caller, so +/// a duplicate never comes from a stale frontend snapshot. +pub fn duplicate_model( + tx: &ClientDb, + model_type: &str, + model_id: &str, + source: &UpdateSource, +) -> Result { + let id = match model_type { + "environment" => tx.duplicate_environment(&tx.get_environment(model_id)?, source)?.id, + "folder" => tx.duplicate_folder(&tx.get_folder(model_id)?, source)?.id, + "grpc_request" => tx.duplicate_grpc_request(&tx.get_grpc_request(model_id)?, source)?.id, + "http_request" => tx.duplicate_http_request(&tx.get_http_request(model_id)?, source)?.id, + "websocket_request" => { + tx.duplicate_websocket_request(&tx.get_websocket_request(model_id)?, source)?.id + } + t => return Err(GenericError(format!("Cannot duplicate model type {t}"))), + }; + + Ok(id) +}