mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-10 11:13:48 +02:00
Generalized frontend model store (#193)
This commit is contained in:
2
src-tauri/gen/schemas/acl-manifests.json
generated
2
src-tauri/gen/schemas/acl-manifests.json
generated
File diff suppressed because one or more lines are too long
50
src-tauri/gen/schemas/desktop-schema.json
generated
50
src-tauri/gen/schemas/desktop-schema.json
generated
@@ -5642,21 +5642,71 @@
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-delete"
|
||||
},
|
||||
{
|
||||
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the upsert command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-upsert"
|
||||
},
|
||||
{
|
||||
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-delete"
|
||||
},
|
||||
{
|
||||
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the upsert command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-upsert"
|
||||
},
|
||||
{
|
||||
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
50
src-tauri/gen/schemas/macOS-schema.json
generated
50
src-tauri/gen/schemas/macOS-schema.json
generated
@@ -5642,21 +5642,71 @@
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-delete"
|
||||
},
|
||||
{
|
||||
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the upsert command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-upsert"
|
||||
},
|
||||
{
|
||||
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:allow-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-delete"
|
||||
},
|
||||
{
|
||||
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the upsert command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-upsert"
|
||||
},
|
||||
{
|
||||
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-models:deny-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
43
src-tauri/migrations/20250326193143_key-value-id.sql
Normal file
43
src-tauri/migrations/20250326193143_key-value-id.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- 1. Create the new table with `id` as the primary key
|
||||
CREATE TABLE key_values_new
|
||||
(
|
||||
id TEXT PRIMARY KEY,
|
||||
model TEXT DEFAULT 'key_value' NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
deleted_at DATETIME,
|
||||
namespace TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 2. Copy data from the old table
|
||||
INSERT INTO key_values_new (id, model, created_at, updated_at, deleted_at, namespace, key, value)
|
||||
SELECT (
|
||||
-- This is the best way to generate a random string in SQLite, apparently
|
||||
'kv_' || SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1)
|
||||
) AS id,
|
||||
model,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at,
|
||||
namespace,
|
||||
key,
|
||||
value
|
||||
FROM key_values;
|
||||
|
||||
-- 3. Drop the old table
|
||||
DROP TABLE key_values;
|
||||
|
||||
-- 4. Rename the new table
|
||||
ALTER TABLE key_values_new
|
||||
RENAME TO key_values;
|
||||
@@ -24,11 +24,11 @@ use tokio::fs::{create_dir_all, File};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::watch::Receiver;
|
||||
use tokio::sync::{oneshot, Mutex};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::models::{
|
||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
||||
HttpResponseState, ProxySetting, ProxySettingAuth,
|
||||
};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::events::{
|
||||
CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext,
|
||||
@@ -46,10 +46,9 @@ pub async fn send_http_request<R: Runtime>(
|
||||
) -> Result<HttpResponse> {
|
||||
let app_handle = window.app_handle().clone();
|
||||
let plugin_manager = app_handle.state::<PluginManager>();
|
||||
let update_source = &UpdateSource::from_window(&window);
|
||||
let (settings, workspace) = {
|
||||
let db = window.db();
|
||||
let settings = db.get_or_create_settings(update_source);
|
||||
let settings = db.get_settings();
|
||||
let workspace = db.get_workspace(&unrendered_request.workspace_id)?;
|
||||
(settings, workspace)
|
||||
};
|
||||
|
||||
@@ -13,12 +13,12 @@ use error::Result as YaakResult;
|
||||
use eventsource_client::{EventParser, SSE};
|
||||
use log::{debug, error, warn};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::fs::{File, create_dir_all};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{fs, panic};
|
||||
use tauri::{is_dev, AppHandle, Emitter, RunEvent, State, WebviewWindow};
|
||||
use tauri::{AppHandle, Emitter, RunEvent, State, WebviewWindow, is_dev};
|
||||
use tauri::{Listener, Runtime};
|
||||
use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
@@ -29,15 +29,15 @@ use tokio::sync::Mutex;
|
||||
use tokio::task::block_in_place;
|
||||
use yaak_common::window::WorkspaceWindowTrait;
|
||||
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
|
||||
use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
|
||||
use yaak_grpc::{Code, ServiceDefinition, deserialize_message, serialize_message};
|
||||
use yaak_models::models::{
|
||||
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState,
|
||||
GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue,
|
||||
Plugin, Settings, WebsocketRequest, Workspace, WorkspaceMeta,
|
||||
CookieJar, Environment, Folder, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType,
|
||||
GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, WebsocketRequest, Workspace,
|
||||
WorkspaceMeta,
|
||||
};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{
|
||||
get_workspace_export_resources, maybe_gen_id, maybe_gen_id_opt, BatchUpsertResult, UpdateSource,
|
||||
BatchUpsertResult, UpdateSource, get_workspace_export_resources, maybe_gen_id, maybe_gen_id_opt,
|
||||
};
|
||||
use yaak_plugins::events::{
|
||||
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
|
||||
@@ -1064,47 +1064,6 @@ fn response_err<R: Runtime>(
|
||||
response
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_set_update_mode<R: Runtime>(
|
||||
update_mode: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<KeyValue> {
|
||||
let (key_value, _created) = app_handle.db().set_key_value_raw(
|
||||
"app",
|
||||
"update_mode",
|
||||
update_mode,
|
||||
&UpdateSource::from_window(&window),
|
||||
);
|
||||
Ok(key_value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_key_value<R: Runtime>(
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Option<KeyValue>> {
|
||||
Ok(app_handle.db().get_key_value_raw(namespace, key))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_set_key_value<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> YaakResult<KeyValue> {
|
||||
let (key_value, _created) = app_handle.db().set_key_value_raw(
|
||||
namespace,
|
||||
key,
|
||||
value,
|
||||
&UpdateSource::from_window(&window),
|
||||
);
|
||||
Ok(key_value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_install_plugin<R: Runtime>(
|
||||
directory: &str,
|
||||
@@ -1144,64 +1103,6 @@ async fn cmd_uninstall_plugin<R: Runtime>(
|
||||
Ok(plugin)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_cookie_jar<R: Runtime>(
|
||||
cookie_jar: CookieJar,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<CookieJar> {
|
||||
Ok(app_handle.db().upsert_cookie_jar(&cookie_jar, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_cookie_jar<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
cookie_jar_id: &str,
|
||||
) -> YaakResult<CookieJar> {
|
||||
Ok(app_handle
|
||||
.db()
|
||||
.delete_cookie_jar_by_id(cookie_jar_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_create_cookie_jar<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
name: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<CookieJar> {
|
||||
Ok(app_handle.db().upsert_cookie_jar(
|
||||
&CookieJar {
|
||||
name: name.to_string(),
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::from_window(&window),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_create_environment<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
name: &str,
|
||||
variables: Vec<EnvironmentVariable>,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Environment> {
|
||||
Ok(app_handle.db().upsert_environment(
|
||||
&Environment {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
environment_id: environment_id.map(|s| s.to_string()),
|
||||
name: name.to_string(),
|
||||
variables,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::from_window(&window),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_create_grpc_request<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
@@ -1223,193 +1124,6 @@ async fn cmd_create_grpc_request<R: Runtime>(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_duplicate_grpc_request<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<GrpcRequest> {
|
||||
let db = app_handle.db();
|
||||
let request = db.get_grpc_request(id)?;
|
||||
Ok(db.duplicate_grpc_request(&request, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_duplicate_folder<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
id: &str,
|
||||
) -> YaakResult<Folder> {
|
||||
let db = app_handle.db();
|
||||
let folder = db.get_folder(id)?;
|
||||
Ok(db.duplicate_folder(&folder, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_duplicate_http_request<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<HttpRequest> {
|
||||
let db = app_handle.db();
|
||||
let request = db.get_http_request(id)?;
|
||||
Ok(db.duplicate_http_request(&request, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_workspace<R: Runtime>(
|
||||
workspace: Workspace,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Workspace> {
|
||||
Ok(app_handle.db().upsert_workspace(&workspace, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_workspace_meta<R: Runtime>(
|
||||
workspace_meta: WorkspaceMeta,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<WorkspaceMeta> {
|
||||
Ok(app_handle
|
||||
.db()
|
||||
.upsert_workspace_meta(&workspace_meta, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_environment<R: Runtime>(
|
||||
environment: Environment,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Environment> {
|
||||
Ok(app_handle.db().upsert_environment(&environment, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_grpc_request<R: Runtime>(
|
||||
request: GrpcRequest,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<GrpcRequest> {
|
||||
Ok(app_handle.db().upsert_grpc_request(&request, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_upsert_http_request<R: Runtime>(
|
||||
request: HttpRequest,
|
||||
window: WebviewWindow<R>,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<HttpRequest> {
|
||||
Ok(app_handle.db().upsert(&request, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_grpc_request<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
request_id: &str,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<GrpcRequest> {
|
||||
Ok(app_handle
|
||||
.db()
|
||||
.delete_grpc_request_by_id(request_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_http_request<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
request_id: &str,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<HttpRequest> {
|
||||
Ok(app_handle
|
||||
.db()
|
||||
.delete_http_request_by_id(request_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_folders<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<Folder>> {
|
||||
Ok(app_handle.db().list_folders(workspace_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_folder<R: Runtime>(
|
||||
folder: Folder,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Folder> {
|
||||
Ok(app_handle.db().upsert_folder(&folder, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_folder<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
folder_id: &str,
|
||||
) -> YaakResult<Folder> {
|
||||
Ok(app_handle.db().delete_folder_by_id(folder_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_environment<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
environment_id: &str,
|
||||
) -> YaakResult<Environment> {
|
||||
Ok(app_handle
|
||||
.db()
|
||||
.delete_environment_by_id(environment_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_grpc_connections<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<GrpcConnection>> {
|
||||
Ok(app_handle.db().list_grpc_connections_for_workspace(workspace_id, None)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_grpc_events<R: Runtime>(
|
||||
connection_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<GrpcEvent>> {
|
||||
Ok(app_handle.db().list_grpc_events(connection_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_grpc_requests<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<GrpcRequest>> {
|
||||
Ok(app_handle.db().list_grpc_requests(workspace_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_http_requests<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<HttpRequest>> {
|
||||
Ok(app_handle.db().list_http_requests(workspace_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_environments<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<Environment>> {
|
||||
// Not sure of a better place to put this...
|
||||
let db = app_handle.db();
|
||||
db.ensure_base_environment(workspace_id)?;
|
||||
Ok(db.list_environments(workspace_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_plugins<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<Vec<Plugin>> {
|
||||
Ok(app_handle.db().list_plugins()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_reload_plugins<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
@@ -1438,99 +1152,6 @@ async fn cmd_plugin_info<R: Runtime>(
|
||||
.await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_settings<R: Runtime>(window: WebviewWindow<R>) -> YaakResult<Settings> {
|
||||
Ok(window.db().get_or_create_settings(&UpdateSource::from_window(&window)))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_settings<R: Runtime>(
|
||||
settings: Settings,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Settings> {
|
||||
Ok(app_handle.db().upsert_settings(&settings, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_folder<R: Runtime>(id: &str, app_handle: AppHandle<R>) -> YaakResult<Folder> {
|
||||
Ok(app_handle.db().get_folder(id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_cookie_jars<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Vec<CookieJar>> {
|
||||
let db = app_handle.db();
|
||||
let cookie_jars = db.list_cookie_jars(workspace_id)?;
|
||||
|
||||
if cookie_jars.is_empty() {
|
||||
let cookie_jar = db.upsert_cookie_jar(
|
||||
&CookieJar {
|
||||
name: "Default".to_string(),
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::from_window(&window),
|
||||
)?;
|
||||
Ok(vec![cookie_jar])
|
||||
} else {
|
||||
Ok(cookie_jars)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_key_values<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<Vec<KeyValue>> {
|
||||
Ok(app_handle.db().list_key_values_raw()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_environment<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Environment> {
|
||||
Ok(app_handle.db().get_environment(id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_workspace<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Workspace> {
|
||||
Ok(app_handle.db().get_workspace(id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_http_responses<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
limit: Option<i64>,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<Vec<HttpResponse>> {
|
||||
Ok(app_handle.db().list_http_responses_for_workspace(workspace_id, limit.map(|l| l as u64))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_http_response<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<HttpResponse> {
|
||||
let db = app_handle.db();
|
||||
let http_response = db.get_http_response(id)?;
|
||||
Ok(db.delete_http_response(&http_response, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_grpc_connection<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<GrpcConnection> {
|
||||
Ok(app_handle.db().delete_grpc_connection_by_id(id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_all_grpc_connections<R: Runtime>(
|
||||
request_id: &str,
|
||||
@@ -1568,29 +1189,6 @@ async fn cmd_delete_all_http_responses<R: Runtime>(
|
||||
.delete_all_http_responses_for_request(request_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_list_workspaces<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Vec<Workspace>> {
|
||||
let db = app_handle.db();
|
||||
let mut workspaces = db.find_all::<Workspace>()?;
|
||||
|
||||
if workspaces.is_empty() {
|
||||
workspaces.push(db.upsert_workspace(
|
||||
&Workspace {
|
||||
name: "Yaak".to_string(),
|
||||
setting_follow_redirects: true,
|
||||
setting_validate_certificates: true,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::from_window(&window),
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_workspace_meta<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
@@ -1599,7 +1197,7 @@ async fn cmd_get_workspace_meta<R: Runtime>(
|
||||
) -> YaakResult<WorkspaceMeta> {
|
||||
let db = app_handle.db();
|
||||
let workspace = db.get_workspace(workspace_id)?;
|
||||
Ok(db.get_or_create_workspace_meta(&workspace, &UpdateSource::from_window(&window))?)
|
||||
Ok(db.get_or_create_workspace_meta(&workspace.id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1620,15 +1218,6 @@ async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> Result<(), Str
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_workspace<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
) -> YaakResult<Workspace> {
|
||||
Ok(app_handle.db().delete_workspace_by_id(workspace_id, &UpdateSource::from_window(&window))?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_check_for_updates<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
@@ -1722,53 +1311,24 @@ pub fn run() {
|
||||
cmd_call_http_authentication_action,
|
||||
cmd_call_http_request_action,
|
||||
cmd_check_for_updates,
|
||||
cmd_create_cookie_jar,
|
||||
cmd_create_environment,
|
||||
cmd_create_grpc_request,
|
||||
cmd_curl_to_request,
|
||||
cmd_delete_all_grpc_connections,
|
||||
cmd_delete_all_http_responses,
|
||||
cmd_delete_cookie_jar,
|
||||
cmd_delete_environment,
|
||||
cmd_delete_folder,
|
||||
cmd_delete_grpc_connection,
|
||||
cmd_delete_grpc_request,
|
||||
cmd_delete_http_request,
|
||||
cmd_delete_http_response,
|
||||
cmd_delete_send_history,
|
||||
cmd_delete_workspace,
|
||||
cmd_dismiss_notification,
|
||||
cmd_duplicate_folder,
|
||||
cmd_duplicate_grpc_request,
|
||||
cmd_duplicate_http_request,
|
||||
cmd_export_data,
|
||||
cmd_filter_response,
|
||||
cmd_format_json,
|
||||
cmd_get_environment,
|
||||
cmd_get_folder,
|
||||
cmd_get_http_authentication_summaries,
|
||||
cmd_get_http_authentication_config,
|
||||
cmd_get_key_value,
|
||||
cmd_get_settings,
|
||||
cmd_get_sse_events,
|
||||
cmd_get_workspace,
|
||||
cmd_get_workspace_meta,
|
||||
cmd_grpc_go,
|
||||
cmd_grpc_reflect,
|
||||
cmd_http_request_actions,
|
||||
cmd_import_data,
|
||||
cmd_install_plugin,
|
||||
cmd_list_cookie_jars,
|
||||
cmd_list_environments,
|
||||
cmd_list_folders,
|
||||
cmd_list_grpc_connections,
|
||||
cmd_list_grpc_events,
|
||||
cmd_list_grpc_requests,
|
||||
cmd_list_key_values,
|
||||
cmd_list_http_requests,
|
||||
cmd_list_http_responses,
|
||||
cmd_list_plugins,
|
||||
cmd_list_workspaces,
|
||||
cmd_metadata,
|
||||
cmd_new_child_window,
|
||||
cmd_new_main_window,
|
||||
@@ -1779,19 +1339,9 @@ pub fn run() {
|
||||
cmd_save_response,
|
||||
cmd_send_ephemeral_request,
|
||||
cmd_send_http_request,
|
||||
cmd_set_key_value,
|
||||
cmd_set_update_mode,
|
||||
cmd_template_functions,
|
||||
cmd_template_tokens_to_string,
|
||||
cmd_uninstall_plugin,
|
||||
cmd_update_cookie_jar,
|
||||
cmd_update_environment,
|
||||
cmd_update_folder,
|
||||
cmd_update_grpc_request,
|
||||
cmd_upsert_http_request,
|
||||
cmd_update_settings,
|
||||
cmd_update_workspace,
|
||||
cmd_update_workspace_meta,
|
||||
])
|
||||
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
|
||||
.build(tauri::generate_context!())
|
||||
@@ -1859,7 +1409,7 @@ pub fn run() {
|
||||
}
|
||||
|
||||
async fn get_update_mode<R: Runtime>(window: &WebviewWindow<R>) -> YaakResult<UpdateMode> {
|
||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
||||
let settings = window.db().get_settings();
|
||||
Ok(UpdateMode::new(settings.update_channel.as_str()))
|
||||
}
|
||||
|
||||
@@ -1934,13 +1484,10 @@ fn get_window_from_window_context<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
let window = app_handle.webview_windows().iter().find_map(|(_, w)| {
|
||||
if w.label() == label {
|
||||
Some(w.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let window = app_handle
|
||||
.webview_windows()
|
||||
.iter()
|
||||
.find_map(|(_, w)| if w.label() == label { Some(w.to_owned()) } else { None });
|
||||
|
||||
if window.is_none() {
|
||||
error!("Failed to find window by {window_context:?}");
|
||||
|
||||
@@ -8,7 +8,6 @@ use tauri_plugin_dialog::{DialogExt, MessageDialogButtons};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tokio::task::block_in_place;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
|
||||
use crate::is_dev;
|
||||
@@ -67,7 +66,7 @@ impl YaakUpdater {
|
||||
mode: UpdateMode,
|
||||
update_trigger: UpdateTrigger,
|
||||
) -> Result<bool> {
|
||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
||||
let settings = window.db().get_settings();
|
||||
let update_key = format!("{:x}", md5::compute(settings.id));
|
||||
self.last_update_check = SystemTime::now();
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ pub async fn check_license<R: Runtime>(
|
||||
payload: CheckActivationRequestPayload,
|
||||
) -> Result<LicenseCheckStatus> {
|
||||
let activation_id = get_activation_id(window.app_handle()).await;
|
||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
||||
let settings = window.db().get_settings();
|
||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
||||
|
||||
debug!("Trial ending at {trial_end:?}");
|
||||
|
||||
@@ -42,7 +42,7 @@ export type HttpResponseState = "initialized" | "connected" | "closed";
|
||||
|
||||
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
||||
export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
||||
|
||||
export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" };
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
const COMMANDS: &[&str] = &["upsert", "delete"];
|
||||
const COMMANDS: &[&str] = &[
|
||||
"delete",
|
||||
"duplicate",
|
||||
"get_settings",
|
||||
"grpc_events",
|
||||
"upsert",
|
||||
"websocket_events",
|
||||
"workspace_models",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
|
||||
80
src-tauri/yaak-models/guest-js/atoms.ts
Normal file
80
src-tauri/yaak-models/guest-js/atoms.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
import { selectAtom } from 'jotai/utils';
|
||||
import type { AnyModel } from '../bindings/gen_models';
|
||||
import { ExtractModel } from './types';
|
||||
import { newStoreData } from './util';
|
||||
|
||||
export const modelStoreDataAtom = atom(newStoreData());
|
||||
|
||||
export const cookieJarsAtom = createOrderedModelAtom('cookie_jar', 'name', 'asc');
|
||||
export const environmentsAtom = createOrderedModelAtom('environment', 'name', 'asc');
|
||||
export const foldersAtom = createModelAtom('folder');
|
||||
export const grpcConnectionsAtom = createOrderedModelAtom('grpc_connection', 'createdAt', 'desc');
|
||||
export const grpcEventsAtom = createOrderedModelAtom('grpc_event', 'createdAt', 'asc');
|
||||
export const grpcRequestsAtom = createModelAtom('grpc_request');
|
||||
export const httpRequestsAtom = createModelAtom('http_request');
|
||||
export const httpResponsesAtom = createOrderedModelAtom('http_response', 'createdAt', 'desc');
|
||||
export const keyValuesAtom = createModelAtom('key_value');
|
||||
export const pluginsAtom = createModelAtom('plugin');
|
||||
export const settingsAtom = createSingularModelAtom('settings');
|
||||
export const websocketRequestsAtom = createModelAtom('websocket_request');
|
||||
export const websocketEventsAtom = createOrderedModelAtom('websocket_event', 'createdAt', 'asc');
|
||||
export const websocketConnectionsAtom = createOrderedModelAtom(
|
||||
'websocket_connection',
|
||||
'createdAt',
|
||||
'desc',
|
||||
);
|
||||
export const workspaceMetasAtom = createModelAtom('workspace_meta');
|
||||
export const workspacesAtom = createOrderedModelAtom('workspace', 'name', 'asc');
|
||||
|
||||
export function createModelAtom<M extends AnyModel['model']>(modelType: M) {
|
||||
return selectAtom(
|
||||
modelStoreDataAtom,
|
||||
(data) => Object.values(data[modelType] ?? {}),
|
||||
shallowEqual,
|
||||
);
|
||||
}
|
||||
|
||||
export function createSingularModelAtom<M extends AnyModel['model']>(modelType: M) {
|
||||
return selectAtom(modelStoreDataAtom, (data) => {
|
||||
const modelData = Object.values(data[modelType] ?? {});
|
||||
const item = modelData[0];
|
||||
if (item == null) throw new Error('Failed creating singular model with no data: ' + modelType);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
export function createOrderedModelAtom<M extends AnyModel['model']>(
|
||||
modelType: M,
|
||||
field: keyof ExtractModel<AnyModel, M>,
|
||||
order: 'asc' | 'desc',
|
||||
) {
|
||||
return selectAtom(
|
||||
modelStoreDataAtom,
|
||||
(data) => {
|
||||
const modelData = data[modelType] ?? {};
|
||||
return Object.values(modelData).sort(
|
||||
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
||||
const n = a[field] > b[field] ? 1 : -1;
|
||||
return order === 'desc' ? n * -1 : n;
|
||||
},
|
||||
);
|
||||
},
|
||||
shallowEqual,
|
||||
);
|
||||
}
|
||||
|
||||
function shallowEqual<T>(a: T[], b: T[]): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
10
src-tauri/yaak-models/guest-js/index.ts
Normal file
10
src-tauri/yaak-models/guest-js/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { AnyModel } from '../bindings/gen_models';
|
||||
|
||||
export * from '../bindings/gen_models';
|
||||
export * from './store';
|
||||
export * from './atoms';
|
||||
|
||||
export function modelTypeLabel(m: AnyModel): string {
|
||||
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||
return m.model.split('_').map(capitalize).join(' ');
|
||||
}
|
||||
199
src-tauri/yaak-models/guest-js/store.ts
Normal file
199
src-tauri/yaak-models/guest-js/store.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { AnyModel, ModelPayload } from '../bindings/gen_models';
|
||||
import { modelStoreDataAtom } from './atoms';
|
||||
import { ExtractModel, JotaiStore, ModelStoreData } from './types';
|
||||
import { newStoreData } from './util';
|
||||
|
||||
let _store: JotaiStore | null = null;
|
||||
|
||||
export function initModelStore(store: JotaiStore) {
|
||||
_store = store;
|
||||
|
||||
getCurrentWebviewWindow()
|
||||
.listen<ModelPayload>('upserted_model', ({ payload }) => {
|
||||
if (shouldIgnoreModel(payload)) return;
|
||||
|
||||
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||
return {
|
||||
...prev,
|
||||
[payload.model.model]: {
|
||||
...prev[payload.model.model],
|
||||
[payload.model.id]: payload.model,
|
||||
},
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
getCurrentWebviewWindow()
|
||||
.listen<ModelPayload>('deleted_model', ({ payload }) => {
|
||||
if (shouldIgnoreModel(payload)) return;
|
||||
|
||||
console.log('Delete model', payload);
|
||||
|
||||
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||
const modelData = { ...prev[payload.model.model] };
|
||||
delete modelData[payload.model.id];
|
||||
return { ...prev, [payload.model.model]: modelData };
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function mustStore(): JotaiStore {
|
||||
if (_store == null) {
|
||||
throw new Error('Model store was not initialized');
|
||||
}
|
||||
|
||||
return _store;
|
||||
}
|
||||
|
||||
let _activeWorkspaceId: string | null = null;
|
||||
|
||||
export async function changeModelStoreWorkspace(workspaceId: string | null) {
|
||||
console.log('Syncing models with new workspace', workspaceId);
|
||||
const workspaceModels = await invoke<AnyModel[]>('plugin:yaak-models|workspace_models', {
|
||||
workspaceId, // NOTE: if no workspace id provided, it will just fetch global models
|
||||
});
|
||||
const data = newStoreData();
|
||||
for (const model of workspaceModels) {
|
||||
data[model.model][model.id] = model;
|
||||
}
|
||||
|
||||
mustStore().set(modelStoreDataAtom, data);
|
||||
|
||||
console.log('Synced model store with workspace', workspaceId, data);
|
||||
|
||||
_activeWorkspaceId = workspaceId;
|
||||
}
|
||||
|
||||
export function getAnyModel(id: string): AnyModel | null {
|
||||
let data = mustStore().get(modelStoreDataAtom);
|
||||
for (const modelData of Object.values(data)) {
|
||||
let model = modelData[id];
|
||||
if (model != null) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||
modelType: M | M[],
|
||||
id: string,
|
||||
): T | null {
|
||||
let data = mustStore().get(modelStoreDataAtom);
|
||||
for (const t of Array.isArray(modelType) ? modelType : [modelType]) {
|
||||
let v = data[t][id];
|
||||
if (v?.model === t) return v as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function patchModelById<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||
model: M,
|
||||
id: string,
|
||||
patch: Partial<T> | ((prev: T) => T),
|
||||
): Promise<string> {
|
||||
let prev = getModel<M, T>(model, id);
|
||||
if (prev == null) {
|
||||
throw new Error(`Failed to get model to patch id=${id} model=${model}`);
|
||||
}
|
||||
|
||||
const newModel = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch };
|
||||
return invoke<string>('plugin:yaak-models|upsert', { model: newModel });
|
||||
}
|
||||
|
||||
export async function patchModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||
base: Pick<T, 'id' | 'model'>,
|
||||
patch: Partial<T>,
|
||||
): Promise<string> {
|
||||
return patchModelById<M, T>(base.model, base.id, patch);
|
||||
}
|
||||
|
||||
export async function deleteModelById<
|
||||
M extends AnyModel['model'],
|
||||
T extends ExtractModel<AnyModel, M>,
|
||||
>(modelType: M | M[], id: string) {
|
||||
let model = getModel<M, T>(modelType, id);
|
||||
await deleteModel(model);
|
||||
}
|
||||
|
||||
export async function deleteModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||
model: T | null,
|
||||
) {
|
||||
if (model == null) {
|
||||
throw new Error('Failed to delete null model');
|
||||
}
|
||||
await invoke<string>('plugin:yaak-models|delete', { model });
|
||||
}
|
||||
|
||||
export function duplicateModelById<
|
||||
M extends AnyModel['model'],
|
||||
T extends ExtractModel<AnyModel, M>,
|
||||
>(modelType: M | M[], id: string) {
|
||||
let model = getModel<M, T>(modelType, id);
|
||||
return duplicateModel(model);
|
||||
}
|
||||
|
||||
export function duplicateModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||
model: T | null,
|
||||
) {
|
||||
if (model == null) {
|
||||
throw new Error('Failed to delete null model');
|
||||
}
|
||||
return invoke<string>('plugin:yaak-models|duplicate', { model });
|
||||
}
|
||||
|
||||
export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>(
|
||||
patch: Partial<T> & Pick<T, 'model'>,
|
||||
): Promise<string> {
|
||||
return invoke<string>('plugin:yaak-models|upsert', { model: patch });
|
||||
}
|
||||
|
||||
export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>(
|
||||
patch: Partial<T> & Pick<T, 'model' | 'workspaceId'>,
|
||||
): Promise<string> {
|
||||
return invoke<string>('plugin:yaak-models|upsert', { model: patch });
|
||||
}
|
||||
|
||||
export function replaceModelsInStore<
|
||||
M extends AnyModel['model'],
|
||||
T extends Extract<AnyModel, { model: M }>,
|
||||
>(model: M, models: T[]) {
|
||||
const newModels: Record<string, T> = {};
|
||||
for (const model of models) {
|
||||
newModels[model.id] = model;
|
||||
}
|
||||
|
||||
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||
return {
|
||||
...prev,
|
||||
[model]: newModels,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
|
||||
// Never ignore updates from non-user sources
|
||||
if (updateSource.type !== 'window') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Never ignore same-window updates
|
||||
if (updateSource.label === getCurrentWebviewWindow().label) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only sync models that belong to this workspace, if a workspace ID is present
|
||||
if ('workspaceId' in model && model.workspaceId !== _activeWorkspaceId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (model.model === 'key_value' && model.namespace === 'no_sync') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
8
src-tauri/yaak-models/guest-js/types.ts
Normal file
8
src-tauri/yaak-models/guest-js/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createStore } from 'jotai/index';
|
||||
import { AnyModel } from '../bindings/gen_models';
|
||||
|
||||
export type ExtractModel<T, M> = T extends { model: M } ? T : never;
|
||||
export type ModelStoreData<T extends AnyModel = AnyModel> = {
|
||||
[M in T['model']]: Record<string, Extract<T, { model: M }>>;
|
||||
};
|
||||
export type JotaiStore = ReturnType<typeof createStore>;
|
||||
23
src-tauri/yaak-models/guest-js/util.ts
Normal file
23
src-tauri/yaak-models/guest-js/util.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ModelStoreData } from './types';
|
||||
|
||||
export function newStoreData(): ModelStoreData {
|
||||
return {
|
||||
cookie_jar: {},
|
||||
environment: {},
|
||||
folder: {},
|
||||
grpc_connection: {},
|
||||
grpc_event: {},
|
||||
grpc_request: {},
|
||||
http_request: {},
|
||||
http_response: {},
|
||||
key_value: {},
|
||||
plugin: {},
|
||||
settings: {},
|
||||
sync_state: {},
|
||||
websocket_connection: {},
|
||||
websocket_event: {},
|
||||
websocket_request: {},
|
||||
workspace: {},
|
||||
workspace_meta: {},
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { AnyModel } from './bindings/gen_models';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
|
||||
export async function upsertAnyModel(model: AnyModel): Promise<String> {
|
||||
return invoke<String>('plugin:yaak-models|upsert', { model });
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
"name": "@yaakapp-internal/models",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts"
|
||||
"main": "guest-js/index.ts"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-duplicate"
|
||||
description = "Enables the duplicate command without any pre-configured scope."
|
||||
commands.allow = ["duplicate"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-duplicate"
|
||||
description = "Denies the duplicate command without any pre-configured scope."
|
||||
commands.deny = ["duplicate"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-settings"
|
||||
description = "Enables the get_settings command without any pre-configured scope."
|
||||
commands.allow = ["get_settings"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-settings"
|
||||
description = "Denies the get_settings command without any pre-configured scope."
|
||||
commands.deny = ["get_settings"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-grpc-events"
|
||||
description = "Enables the grpc_events command without any pre-configured scope."
|
||||
commands.allow = ["grpc_events"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-grpc-events"
|
||||
description = "Denies the grpc_events command without any pre-configured scope."
|
||||
commands.deny = ["grpc_events"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-websocket-events"
|
||||
description = "Enables the websocket_events command without any pre-configured scope."
|
||||
commands.allow = ["websocket_events"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-websocket-events"
|
||||
description = "Denies the websocket_events command without any pre-configured scope."
|
||||
commands.deny = ["websocket_events"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-workspace-models"
|
||||
description = "Enables the workspace_models command without any pre-configured scope."
|
||||
commands.allow = ["workspace_models"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-workspace-models"
|
||||
description = "Denies the workspace_models command without any pre-configured scope."
|
||||
commands.deny = ["workspace_models"]
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
Default permissions for the plugin
|
||||
|
||||
- `allow-upsert`
|
||||
- `allow-delete`
|
||||
- `allow-duplicate`
|
||||
- `allow-get-settings`
|
||||
- `allow-grpc-events`
|
||||
- `allow-upsert`
|
||||
- `allow-websocket-events`
|
||||
- `allow-workspace-models`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -43,6 +48,84 @@ Denies the delete command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-duplicate`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the duplicate command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:deny-duplicate`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the duplicate command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-get-settings`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_settings command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:deny-get-settings`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_settings command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-grpc-events`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the grpc_events command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:deny-grpc-events`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the grpc_events command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-upsert`
|
||||
|
||||
</td>
|
||||
@@ -63,6 +146,58 @@ Enables the upsert command without any pre-configured scope.
|
||||
|
||||
Denies the upsert command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-websocket-events`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the websocket_events command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:deny-websocket-events`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the websocket_events command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:allow-workspace-models`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the workspace_models command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-models:deny-workspace-models`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the workspace_models command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = [
|
||||
"allow-upsert",
|
||||
"allow-delete",
|
||||
"allow-duplicate",
|
||||
"allow-get-settings",
|
||||
"allow-grpc-events",
|
||||
"allow-upsert",
|
||||
"allow-websocket-events",
|
||||
"allow-workspace-models",
|
||||
]
|
||||
|
||||
@@ -304,6 +304,36 @@
|
||||
"type": "string",
|
||||
"const": "deny-delete"
|
||||
},
|
||||
{
|
||||
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-duplicate"
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-settings"
|
||||
},
|
||||
{
|
||||
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-grpc-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the upsert command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -314,6 +344,26 @@
|
||||
"type": "string",
|
||||
"const": "deny-upsert"
|
||||
},
|
||||
{
|
||||
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-websocket-events"
|
||||
},
|
||||
{
|
||||
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-workspace-models"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::error::Result;
|
||||
use crate::models::AnyModel;
|
||||
use crate::models::{AnyModel, GrpcEvent, Settings, WebsocketEvent};
|
||||
use crate::query_manager::QueryManagerExt;
|
||||
use crate::util::UpdateSource;
|
||||
use tauri::{Runtime, WebviewWindow};
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow};
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn upsert<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
model: AnyModel,
|
||||
) -> Result<String> {
|
||||
pub(crate) fn upsert<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||
let db = window.db();
|
||||
let source = &UpdateSource::from_window(&window);
|
||||
let id = match model {
|
||||
AnyModel::HttpRequest(m) => db.upsert_http_request(&m, source)?.id,
|
||||
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)?.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,
|
||||
@@ -32,22 +30,95 @@ pub(crate) async fn upsert<R: Runtime>(
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn delete<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||
let db = window.db();
|
||||
let source = &UpdateSource::from_window(&window);
|
||||
let id = match model {
|
||||
AnyModel::HttpRequest(m) => db.delete_http_request(&m, source)?.id,
|
||||
AnyModel::CookieJar(m) => db.delete_cookie_jar(&m, source)?.id,
|
||||
AnyModel::Environment(m) => db.delete_environment(&m, source)?.id,
|
||||
AnyModel::Folder(m) => db.delete_folder(&m, source)?.id,
|
||||
AnyModel::GrpcConnection(m) => db.delete_grpc_connection(&m, source)?.id,
|
||||
AnyModel::GrpcRequest(m) => db.delete_grpc_request(&m, source)?.id,
|
||||
AnyModel::HttpResponse(m) => db.delete_http_response(&m, source)?.id,
|
||||
AnyModel::Plugin(m) => db.delete_plugin(&m, source)?.id,
|
||||
AnyModel::WebsocketConnection(m) => db.delete_websocket_connection(&m, source)?.id,
|
||||
AnyModel::WebsocketRequest(m) => db.delete_websocket_request(&m, source)?.id,
|
||||
AnyModel::Workspace(m) => db.delete_workspace(&m, source)?.id,
|
||||
a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))),
|
||||
};
|
||||
|
||||
Ok(id)
|
||||
// Use transaction for deletions because it might recurse
|
||||
window.with_tx(|tx| {
|
||||
let source = &UpdateSource::from_window(&window);
|
||||
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)?.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)?.id,
|
||||
a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))),
|
||||
};
|
||||
Ok(id)
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn duplicate<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||
// Use transaction for duplications because it might recurse
|
||||
window.with_tx(|tx| {
|
||||
let source = &UpdateSource::from_window(&window);
|
||||
let id = match model {
|
||||
AnyModel::Environment(m) => tx.duplicate_environment(&m, source)?.id,
|
||||
AnyModel::Folder(m) => tx.duplicate_folder(&m, source)?.id,
|
||||
AnyModel::GrpcRequest(m) => tx.duplicate_grpc_request(&m, source)?.id,
|
||||
AnyModel::HttpRequest(m) => tx.duplicate_http_request(&m, source)?.id,
|
||||
AnyModel::WebsocketRequest(m) => tx.duplicate_websocket_request(&m, source)?.id,
|
||||
a => return Err(GenericError(format!("Cannot duplicate AnyModel {a:?})"))),
|
||||
};
|
||||
|
||||
Ok(id)
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn websocket_events<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
connection_id: &str,
|
||||
) -> Result<Vec<WebsocketEvent>> {
|
||||
Ok(app_handle.db().list_websocket_events(connection_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn grpc_events<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
connection_id: &str,
|
||||
) -> Result<Vec<GrpcEvent>> {
|
||||
Ok(app_handle.db().list_grpc_events(connection_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn get_settings<R: Runtime>(app_handle: AppHandle<R>) -> Result<Settings> {
|
||||
Ok(app_handle.db().get_settings())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn workspace_models<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
workspace_id: Option<&str>,
|
||||
) -> Result<Vec<AnyModel>> {
|
||||
let db = window.db();
|
||||
let mut l: Vec<AnyModel> = Vec::new();
|
||||
|
||||
// Add the settings
|
||||
l.push(db.get_settings().into());
|
||||
|
||||
// Add global models
|
||||
l.append(&mut db.list_workspaces()?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_key_values()?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_plugins()?.into_iter().map(Into::into).collect());
|
||||
|
||||
// Add the workspace children
|
||||
if let Some(wid) = workspace_id {
|
||||
l.append(&mut db.list_cookie_jars(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_environments(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_folders(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_grpc_connections(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_grpc_requests(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_http_requests(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_http_responses(wid, None)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_websocket_connections(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_websocket_requests(wid)?.into_iter().map(Into::into).collect());
|
||||
l.append(&mut db.list_workspace_metas(wid)?.into_iter().map(Into::into).collect());
|
||||
}
|
||||
|
||||
Ok(l)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use sea_query_rusqlite::RusqliteBinder;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub struct DbContext<'a> {
|
||||
pub(crate) tx: mpsc::Sender<ModelPayload>,
|
||||
pub(crate) events_tx: mpsc::Sender<ModelPayload>,
|
||||
pub(crate) conn: ConnectionOrTx<'a>,
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ impl<'a> DbContext<'a> {
|
||||
update_source: source.clone(),
|
||||
change: ModelChangeEvent::Upsert,
|
||||
};
|
||||
self.tx.try_send(payload).unwrap();
|
||||
self.events_tx.try_send(payload).unwrap();
|
||||
|
||||
Ok(m)
|
||||
}
|
||||
@@ -170,7 +170,7 @@ impl<'a> DbContext<'a> {
|
||||
change: ModelChangeEvent::Delete,
|
||||
};
|
||||
|
||||
self.tx.try_send(payload).unwrap();
|
||||
self.events_tx.try_send(payload).unwrap();
|
||||
Ok(m.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::commands::{delete, upsert};
|
||||
use crate::commands::*;
|
||||
use crate::query_manager::QueryManager;
|
||||
use crate::util::ModelChangeEvent;
|
||||
use log::info;
|
||||
@@ -22,11 +22,11 @@ mod commands;
|
||||
mod connection_or_tx;
|
||||
mod db_context;
|
||||
pub mod error;
|
||||
pub mod query_manager;
|
||||
pub mod models;
|
||||
pub mod queries;
|
||||
pub mod util;
|
||||
pub mod query_manager;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
|
||||
pub struct SqliteConnection(pub Mutex<Pool<SqliteConnectionManager>>);
|
||||
|
||||
@@ -38,7 +38,15 @@ impl SqliteConnection {
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("yaak-models")
|
||||
.invoke_handler(generate_handler![upsert, delete])
|
||||
.invoke_handler(generate_handler![
|
||||
upsert,
|
||||
delete,
|
||||
duplicate,
|
||||
workspace_models,
|
||||
grpc_events,
|
||||
websocket_events,
|
||||
get_settings,
|
||||
])
|
||||
.setup(|app_handle, _api| {
|
||||
let app_path = app_handle.path().app_data_dir().unwrap();
|
||||
create_dir_all(app_path.clone()).expect("Problem creating App directory!");
|
||||
|
||||
@@ -105,7 +105,6 @@ pub struct Settings {
|
||||
pub interface_scale: f32,
|
||||
pub open_workspace_new_window: Option<bool>,
|
||||
pub proxy: Option<ProxySetting>,
|
||||
pub theme: String,
|
||||
pub theme_dark: String,
|
||||
pub theme_light: String,
|
||||
pub update_channel: String,
|
||||
@@ -148,7 +147,6 @@ impl UpsertModelInfo for Settings {
|
||||
(InterfaceFontSize, self.interface_font_size.into()),
|
||||
(InterfaceScale, self.interface_scale.into()),
|
||||
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
||||
(Theme, self.theme.as_str().into()),
|
||||
(ThemeDark, self.theme_dark.as_str().into()),
|
||||
(ThemeLight, self.theme_light.as_str().into()),
|
||||
(UpdateChannel, self.update_channel.into()),
|
||||
@@ -167,7 +165,6 @@ impl UpsertModelInfo for Settings {
|
||||
SettingsIden::InterfaceScale,
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
SettingsIden::Proxy,
|
||||
SettingsIden::Theme,
|
||||
SettingsIden::ThemeDark,
|
||||
SettingsIden::ThemeLight,
|
||||
SettingsIden::UpdateChannel,
|
||||
@@ -193,7 +190,6 @@ impl UpsertModelInfo for Settings {
|
||||
interface_scale: row.get("interface_scale")?,
|
||||
open_workspace_new_window: row.get("open_workspace_new_window")?,
|
||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||
theme: row.get("theme")?,
|
||||
theme_dark: row.get("theme_dark")?,
|
||||
theme_light: row.get("theme_light")?,
|
||||
update_channel: row.get("update_channel")?,
|
||||
@@ -1751,6 +1747,7 @@ impl UpsertModelInfo for SyncState {
|
||||
pub struct KeyValue {
|
||||
#[ts(type = "\"key_value\"")]
|
||||
pub model: String,
|
||||
pub id: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
|
||||
@@ -1759,17 +1756,53 @@ pub struct KeyValue {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl<'s> TryFrom<&Row<'s>> for KeyValue {
|
||||
type Error = rusqlite::Error;
|
||||
impl UpsertModelInfo for KeyValue {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
KeyValueIden::Table
|
||||
}
|
||||
|
||||
fn try_from(r: &Row<'s>) -> std::result::Result<Self, Self::Error> {
|
||||
fn id_column() -> impl IntoIden + Eq + Clone {
|
||||
KeyValueIden::Id
|
||||
}
|
||||
|
||||
fn generate_id() -> String {
|
||||
generate_prefixed_id("kv")
|
||||
}
|
||||
|
||||
fn get_id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn insert_values(
|
||||
self,
|
||||
source: &UpdateSource,
|
||||
) -> Result<Vec<(impl IntoIden + Eq, impl Into<SimpleExpr>)>> {
|
||||
use KeyValueIden::*;
|
||||
Ok(vec![
|
||||
(CreatedAt, upsert_date(source, self.created_at)),
|
||||
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||
(Namespace, self.namespace.clone().into()),
|
||||
(Key, self.key.clone().into()),
|
||||
(Value, self.value.clone().into()),
|
||||
])
|
||||
}
|
||||
|
||||
fn update_columns() -> Vec<impl IntoIden> {
|
||||
vec![KeyValueIden::UpdatedAt, KeyValueIden::Value]
|
||||
}
|
||||
|
||||
fn from_row(row: &Row) -> rusqlite::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self {
|
||||
model: r.get("model")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
namespace: r.get("namespace")?,
|
||||
key: r.get("key")?,
|
||||
value: r.get("value")?,
|
||||
id: row.get("id")?,
|
||||
model: row.get("model")?,
|
||||
created_at: row.get("created_at")?,
|
||||
updated_at: row.get("updated_at")?,
|
||||
namespace: row.get("namespace")?,
|
||||
key: row.get("key")?,
|
||||
value: row.get("value")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1876,18 +1909,23 @@ impl<'de> Deserialize<'de> for AnyModel {
|
||||
use serde_json::from_value as fv;
|
||||
|
||||
let model = match model.get("model") {
|
||||
Some(m) if m == "http_request" => AnyModel::HttpRequest(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()),
|
||||
Some(m) if m == "workspace" => AnyModel::Workspace(fv(value).unwrap()),
|
||||
Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()),
|
||||
Some(m) if m == "environment" => AnyModel::Environment(fv(value).unwrap()),
|
||||
Some(m) if m == "folder" => AnyModel::Folder(fv(value).unwrap()),
|
||||
Some(m) if m == "key_value" => AnyModel::KeyValue(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_connection" => AnyModel::GrpcConnection(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_event" => AnyModel::GrpcEvent(fv(value).unwrap()),
|
||||
Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()),
|
||||
Some(m) if m == "http_request" => AnyModel::HttpRequest(fv(value).unwrap()),
|
||||
Some(m) if m == "key_value" => AnyModel::KeyValue(fv(value).unwrap()),
|
||||
Some(m) if m == "plugin" => AnyModel::Plugin(fv(value).unwrap()),
|
||||
Some(m) if m == "settings" => AnyModel::Settings(fv(value).unwrap()),
|
||||
Some(m) if m == "websocket_connection" => AnyModel::WebsocketConnection(fv(value).unwrap()),
|
||||
Some(m) if m == "websocket_event" => AnyModel::WebsocketEvent(fv(value).unwrap()),
|
||||
Some(m) if m == "websocket_request" => AnyModel::WebsocketRequest(fv(value).unwrap()),
|
||||
Some(m) if m == "workspace" => AnyModel::Workspace(fv(value).unwrap()),
|
||||
Some(m) if m == "workspace_meta" => AnyModel::WorkspaceMeta(fv(value).unwrap()),
|
||||
Some(m) => {
|
||||
return Err(serde::de::Error::custom(format!("Unknown model {}", m)));
|
||||
return Err(serde::de::Error::custom(format!("Failed to deserialize AnyModel {}", m)));
|
||||
}
|
||||
None => {
|
||||
return Err(serde::de::Error::custom("Missing or invalid model"));
|
||||
@@ -1905,11 +1943,7 @@ impl AnyModel {
|
||||
return name.to_string();
|
||||
}
|
||||
let without_variables = url.replace(r"\$\{\[\s*([^\]\s]+)\s*]}", "$1");
|
||||
if without_variables.is_empty() {
|
||||
fallback.to_string()
|
||||
} else {
|
||||
without_variables
|
||||
}
|
||||
if without_variables.is_empty() { fallback.to_string() } else { without_variables }
|
||||
};
|
||||
|
||||
match self.clone() {
|
||||
|
||||
@@ -9,7 +9,18 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
|
||||
pub fn list_cookie_jars(&self, workspace_id: &str) -> Result<Vec<CookieJar>> {
|
||||
self.find_many(CookieJarIden::WorkspaceId, workspace_id, None)
|
||||
let mut cookie_jars = self.find_many(CookieJarIden::WorkspaceId, workspace_id, None)?;
|
||||
|
||||
if cookie_jars.is_empty() {
|
||||
let jar = CookieJar {
|
||||
name: "Default".to_string(),
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
cookie_jars.push(self.upsert_cookie_jar(&jar, &UpdateSource::Background)?);
|
||||
}
|
||||
|
||||
Ok(cookie_jars)
|
||||
}
|
||||
|
||||
pub fn delete_cookie_jar(
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use crate::error::Result;
|
||||
use crate::models::{Environment, EnvironmentIden, UpsertModelInfo};
|
||||
use crate::util::UpdateSource;
|
||||
use log::info;
|
||||
use sea_query::ColumnRef::Asterisk;
|
||||
use sea_query::{Cond, Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::error::Result;
|
||||
use crate::models::{Environment, EnvironmentIden};
|
||||
use crate::util::UpdateSource;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_environment(&self, id: &str) -> Result<Environment> {
|
||||
@@ -13,42 +10,38 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
|
||||
pub fn get_base_environment(&self, workspace_id: &str) -> Result<Environment> {
|
||||
let (sql, params) = Query::select()
|
||||
.from(EnvironmentIden::Table)
|
||||
.column(Asterisk)
|
||||
.cond_where(
|
||||
Cond::all()
|
||||
.add(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id))
|
||||
.add(Expr::col(EnvironmentIden::EnvironmentId).is_null()),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
let mut stmt = self.conn.prepare(sql.as_str())?;
|
||||
Ok(stmt.query_row(&*params.as_params(), Environment::from_row)?)
|
||||
// Will create base environment if it doesn't exist
|
||||
let environments = self.list_environments(workspace_id)?;
|
||||
|
||||
let base_environment = environments
|
||||
.into_iter()
|
||||
.find(|e| e.environment_id == None && e.workspace_id == workspace_id)
|
||||
.ok_or(GenericError(format!("No base environment found for {workspace_id}")))?;
|
||||
|
||||
Ok(base_environment)
|
||||
}
|
||||
|
||||
pub fn ensure_base_environment(&self, workspace_id: &str) -> Result<()> {
|
||||
let environments = self.list_environments(workspace_id)?;
|
||||
pub fn list_environments(&self, workspace_id: &str) -> Result<Vec<Environment>> {
|
||||
let mut environments =
|
||||
self.find_many::<Environment>(EnvironmentIden::WorkspaceId, workspace_id, None)?;
|
||||
|
||||
let base_environment = environments
|
||||
.iter()
|
||||
.find(|e| e.environment_id == None && e.workspace_id == workspace_id);
|
||||
|
||||
if let None = base_environment {
|
||||
info!("Creating base environment for {workspace_id}");
|
||||
self.upsert_environment(
|
||||
environments.push(self.upsert_environment(
|
||||
&Environment {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
environment_id: None,
|
||||
name: "Global Variables".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)?;
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_environments(&self, workspace_id: &str) -> Result<Vec<Environment>> {
|
||||
self.find_many(EnvironmentIden::WorkspaceId, workspace_id, None)
|
||||
Ok(environments)
|
||||
}
|
||||
|
||||
pub fn delete_environment(
|
||||
@@ -69,6 +62,16 @@ impl<'a> DbContext<'a> {
|
||||
self.delete_environment(&environment, source)
|
||||
}
|
||||
|
||||
pub fn duplicate_environment(
|
||||
&self,
|
||||
environment: &Environment,
|
||||
source: &UpdateSource,
|
||||
) -> Result<Environment> {
|
||||
let mut environment = environment.clone();
|
||||
environment.id = "".to_string();
|
||||
self.upsert(&environment, source)
|
||||
}
|
||||
|
||||
pub fn upsert_environment(
|
||||
&self,
|
||||
environment: &Environment,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::connection_or_tx::ConnectionOrTx;
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
@@ -16,20 +17,29 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
|
||||
pub fn delete_folder(&self, folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
||||
for folder in self.find_many::<Folder>(FolderIden::FolderId, &folder.id, None)? {
|
||||
match self.conn {
|
||||
ConnectionOrTx::Connection(_) => {}
|
||||
ConnectionOrTx::Transaction(_) => {}
|
||||
}
|
||||
|
||||
let fid = &folder.id;
|
||||
for m in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, fid, None)? {
|
||||
self.delete_http_request(&m, source)?;
|
||||
}
|
||||
|
||||
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, fid, None)? {
|
||||
self.delete_grpc_request(&m, source)?;
|
||||
}
|
||||
|
||||
for m in self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, fid, None)? {
|
||||
self.delete_websocket_request(&m, source)?;
|
||||
}
|
||||
|
||||
// Recurse down into child folders
|
||||
for folder in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
|
||||
self.delete_folder(&folder, source)?;
|
||||
}
|
||||
for request in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, &folder.id, None)? {
|
||||
self.delete_http_request(&request, source)?;
|
||||
}
|
||||
for request in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, &folder.id, None)? {
|
||||
self.delete_grpc_request(&request, source)?;
|
||||
}
|
||||
for request in
|
||||
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &folder.id, None)?
|
||||
{
|
||||
self.delete_websocket_request(&request, source)?;
|
||||
}
|
||||
|
||||
self.delete(folder, source)
|
||||
}
|
||||
|
||||
@@ -43,22 +53,7 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
|
||||
pub fn duplicate_folder(&self, src_folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
||||
let workspace_id = src_folder.workspace_id.as_str();
|
||||
|
||||
let http_requests = self
|
||||
.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, workspace_id, None)?
|
||||
.into_iter()
|
||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
||||
|
||||
let grpc_requests = self
|
||||
.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, workspace_id, None)?
|
||||
.into_iter()
|
||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
||||
|
||||
let folders = self
|
||||
.find_many::<Folder>(FolderIden::WorkspaceId, workspace_id, None)?
|
||||
.into_iter()
|
||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
||||
let fid = &src_folder.id;
|
||||
|
||||
let new_folder = self.upsert_folder(
|
||||
&Folder {
|
||||
@@ -69,29 +64,40 @@ impl<'a> DbContext<'a> {
|
||||
source,
|
||||
)?;
|
||||
|
||||
for m in http_requests {
|
||||
for m in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, fid, None)? {
|
||||
self.upsert_http_request(
|
||||
&HttpRequest {
|
||||
id: "".into(),
|
||||
folder_id: Some(new_folder.id.clone()),
|
||||
sort_priority: m.sort_priority + 0.001,
|
||||
..m
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
for m in grpc_requests {
|
||||
|
||||
for m in self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, fid, None)? {
|
||||
self.upsert_websocket_request(
|
||||
&WebsocketRequest {
|
||||
id: "".into(),
|
||||
folder_id: Some(new_folder.id.clone()),
|
||||
..m
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
|
||||
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, fid, None)? {
|
||||
self.upsert_grpc_request(
|
||||
&GrpcRequest {
|
||||
id: "".into(),
|
||||
folder_id: Some(new_folder.id.clone()),
|
||||
sort_priority: m.sort_priority + 0.001,
|
||||
..m
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
for m in folders {
|
||||
|
||||
for m in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
|
||||
// Recurse down
|
||||
self.duplicate_folder(
|
||||
&Folder {
|
||||
@@ -101,6 +107,7 @@ impl<'a> DbContext<'a> {
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(new_folder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{GrpcConnection, GrpcConnectionIden, GrpcConnectionState};
|
||||
use crate::queries::MAX_HISTORY_ITEMS;
|
||||
use crate::util::UpdateSource;
|
||||
use log::debug;
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
use crate::db_context::DbContext;
|
||||
use crate::queries::MAX_HISTORY_ITEMS;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_grpc_connection(&self, id: &str) -> Result<GrpcConnection> {
|
||||
@@ -29,7 +29,7 @@ impl<'a> DbContext<'a> {
|
||||
workspace_id: &str,
|
||||
source: &UpdateSource,
|
||||
) -> Result<()> {
|
||||
for m in self.list_grpc_connections_for_workspace(workspace_id, None)? {
|
||||
for m in self.list_grpc_connections(workspace_id)? {
|
||||
self.delete(&m, source)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -60,12 +60,8 @@ impl<'a> DbContext<'a> {
|
||||
self.find_many(GrpcConnectionIden::RequestId, request_id, limit)
|
||||
}
|
||||
|
||||
pub fn list_grpc_connections_for_workspace(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
limit: Option<u64>,
|
||||
) -> Result<Vec<GrpcConnection>> {
|
||||
self.find_many(GrpcConnectionIden::WorkspaceId, workspace_id, limit)
|
||||
pub fn list_grpc_connections(&self, workspace_id: &str) -> Result<Vec<GrpcConnection>> {
|
||||
self.find_many(GrpcConnectionIden::WorkspaceId, workspace_id, None)
|
||||
}
|
||||
|
||||
pub fn cancel_pending_grpc_connections(&self) -> Result<()> {
|
||||
|
||||
@@ -21,7 +21,7 @@ impl<'a> DbContext<'a> {
|
||||
self.find_many(HttpResponseIden::RequestId, request_id, limit)
|
||||
}
|
||||
|
||||
pub fn list_http_responses_for_workspace(
|
||||
pub fn list_http_responses(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
limit: Option<u64>,
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{KeyValue, KeyValueIden};
|
||||
use crate::util::{ModelChangeEvent, ModelPayload, UpdateSource};
|
||||
use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
||||
use crate::util::UpdateSource;
|
||||
use log::error;
|
||||
use sea_query::Keyword::CurrentTimestamp;
|
||||
use sea_query::{Asterisk, Cond, Expr, OnConflict, Query, SqliteQueryBuilder};
|
||||
use sea_query::{Asterisk, Cond, Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn list_key_values_raw(&self) -> Result<Vec<KeyValue>> {
|
||||
pub fn list_key_values(&self) -> Result<Vec<KeyValue>> {
|
||||
let (sql, params) = Query::select()
|
||||
.from(KeyValueIden::Table)
|
||||
.column(Asterisk)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
let mut stmt = self.conn.prepare(sql.as_str())?;
|
||||
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
||||
let items = stmt.query_map(&*params.as_params(), KeyValue::from_row)?;
|
||||
Ok(items.map(|v| v.unwrap()).collect())
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ impl<'a> DbContext<'a> {
|
||||
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok()
|
||||
self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), KeyValue::from_row).ok()
|
||||
}
|
||||
|
||||
pub fn set_key_value_string(
|
||||
@@ -125,43 +124,7 @@ impl<'a> DbContext<'a> {
|
||||
key_value: &KeyValue,
|
||||
source: &UpdateSource,
|
||||
) -> Result<KeyValue> {
|
||||
let (sql, params) = Query::insert()
|
||||
.into_table(KeyValueIden::Table)
|
||||
.columns([
|
||||
KeyValueIden::CreatedAt,
|
||||
KeyValueIden::UpdatedAt,
|
||||
KeyValueIden::Namespace,
|
||||
KeyValueIden::Key,
|
||||
KeyValueIden::Value,
|
||||
])
|
||||
.values_panic([
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
key_value.namespace.clone().into(),
|
||||
key_value.key.clone().into(),
|
||||
key_value.value.clone().into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::new()
|
||||
.update_columns([KeyValueIden::UpdatedAt, KeyValueIden::Value])
|
||||
.to_owned(),
|
||||
)
|
||||
.returning_all()
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = self.conn.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert");
|
||||
let m: KeyValue = stmt
|
||||
.query_row(&*params.as_params(), |row| row.try_into())
|
||||
.expect("Failed to upsert KeyValue");
|
||||
|
||||
let payload = ModelPayload {
|
||||
model: m.clone().into(),
|
||||
update_source: source.clone(),
|
||||
change: ModelChangeEvent::Upsert,
|
||||
};
|
||||
self.tx.try_send(payload).unwrap();
|
||||
|
||||
Ok(m)
|
||||
self.upsert(key_value, source)
|
||||
}
|
||||
|
||||
pub fn delete_key_value(
|
||||
@@ -175,21 +138,7 @@ impl<'a> DbContext<'a> {
|
||||
Some(m) => m,
|
||||
};
|
||||
|
||||
let (sql, params) = Query::delete()
|
||||
.from_table(KeyValueIden::Table)
|
||||
.cond_where(
|
||||
Cond::all()
|
||||
.add(Expr::col(KeyValueIden::Namespace).eq(namespace))
|
||||
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
self.conn.execute(sql.as_str(), &*params.as_params())?;
|
||||
let payload = ModelPayload {
|
||||
model: kv.clone().into(),
|
||||
update_source: source.clone(),
|
||||
change: ModelChangeEvent::Delete,
|
||||
};
|
||||
self.tx.try_send(payload).unwrap();
|
||||
self.delete(&kv, source)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@ mod websocket_requests;
|
||||
mod workspace_metas;
|
||||
mod workspaces;
|
||||
|
||||
const MAX_HISTORY_ITEMS: usize = 20;
|
||||
const MAX_HISTORY_ITEMS: usize = 20;
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{Settings, SettingsIden};
|
||||
use crate::models::{EditorKeymap, Settings, SettingsIden};
|
||||
use crate::util::UpdateSource;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_or_create_settings(&self, source: &UpdateSource) -> Settings {
|
||||
pub fn get_settings(&self) -> Settings {
|
||||
let id = "default".to_string();
|
||||
|
||||
if let Some(s) = self.find_optional::<Settings>(SettingsIden::Id, &id) {
|
||||
return s;
|
||||
};
|
||||
|
||||
self.upsert(
|
||||
&Settings {
|
||||
id,
|
||||
..Default::default()
|
||||
},
|
||||
source,
|
||||
)
|
||||
.expect("Failed to upsert settings")
|
||||
let settings = Settings {
|
||||
model: "settings".to_string(),
|
||||
id,
|
||||
created_at: Default::default(),
|
||||
updated_at: Default::default(),
|
||||
|
||||
appearance: "system".to_string(),
|
||||
editor_font_size: 13,
|
||||
editor_keymap: EditorKeymap::Default,
|
||||
editor_soft_wrap: true,
|
||||
interface_font_size: 15,
|
||||
interface_scale: 1.0,
|
||||
open_workspace_new_window: None,
|
||||
proxy: None,
|
||||
theme_dark: "yaak-dark".to_string(),
|
||||
theme_light: "yaak-light".to_string(),
|
||||
update_channel: "stable".to_string(),
|
||||
};
|
||||
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
|
||||
}
|
||||
|
||||
pub fn upsert_settings(&self, settings: &Settings, source: &UpdateSource) -> Result<Settings> {
|
||||
|
||||
@@ -29,14 +29,14 @@ impl<'a> DbContext<'a> {
|
||||
workspace_id: &str,
|
||||
source: &UpdateSource,
|
||||
) -> Result<()> {
|
||||
let responses = self.list_websocket_connections_for_workspace(workspace_id)?;
|
||||
let responses = self.list_websocket_connections(workspace_id)?;
|
||||
for m in responses {
|
||||
self.delete(&m, source)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_websocket_connections_for_workspace(
|
||||
pub fn list_websocket_connections(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<WebsocketConnection>> {
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{Workspace, WorkspaceMeta, WorkspaceMetaIden};
|
||||
use crate::models::{WorkspaceMeta, WorkspaceMetaIden};
|
||||
use crate::util::UpdateSource;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_workspace_meta(&self, workspace: &Workspace) -> Option<WorkspaceMeta> {
|
||||
self.find_optional(WorkspaceMetaIden::WorkspaceId, &workspace.id)
|
||||
pub fn get_workspace_meta(&self, workspace_id: &str) -> Option<WorkspaceMeta> {
|
||||
self.find_optional(WorkspaceMetaIden::WorkspaceId, workspace_id)
|
||||
}
|
||||
|
||||
pub fn list_workspace_metas(&self, workspace_id: &str) -> Result<Vec<WorkspaceMeta>> {
|
||||
let mut workspace_metas =
|
||||
self.find_many(WorkspaceMetaIden::WorkspaceId, workspace_id, None)?;
|
||||
|
||||
if workspace_metas.is_empty() {
|
||||
let wm = WorkspaceMeta {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
workspace_metas.push(self.upsert_workspace_meta(&wm, &UpdateSource::Background)?)
|
||||
}
|
||||
|
||||
Ok(workspace_metas)
|
||||
}
|
||||
|
||||
pub fn get_or_create_workspace_meta(
|
||||
&self,
|
||||
workspace: &Workspace,
|
||||
workspace_id: &str,
|
||||
source: &UpdateSource,
|
||||
) -> Result<WorkspaceMeta> {
|
||||
let workspace_meta = self.get_workspace_meta(workspace);
|
||||
let workspace_meta = self.get_workspace_meta(workspace_id);
|
||||
if let Some(workspace_meta) = workspace_meta {
|
||||
return Ok(workspace_meta);
|
||||
}
|
||||
|
||||
let workspace_meta = WorkspaceMeta {
|
||||
workspace_id: workspace.to_owned().id,
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,21 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
|
||||
pub fn list_workspaces(&self) -> Result<Vec<Workspace>> {
|
||||
self.find_all()
|
||||
let mut workspaces = self.find_all()?;
|
||||
|
||||
if workspaces.is_empty() {
|
||||
workspaces.push(self.upsert_workspace(
|
||||
&Workspace {
|
||||
name: "Yaak".to_string(),
|
||||
setting_follow_redirects: true,
|
||||
setting_validate_certificates: true,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)?)
|
||||
}
|
||||
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
pub fn delete_workspace(
|
||||
@@ -20,24 +34,24 @@ impl<'a> DbContext<'a> {
|
||||
workspace: &Workspace,
|
||||
source: &UpdateSource,
|
||||
) -> Result<Workspace> {
|
||||
for m in self.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, &workspace.id, None)? {
|
||||
self.delete_http_request(&m, source)?;
|
||||
}
|
||||
|
||||
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, &workspace.id, None)? {
|
||||
self.delete_grpc_request(&m, source)?;
|
||||
}
|
||||
|
||||
for m in
|
||||
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &workspace.id, None)?
|
||||
{
|
||||
self.delete_websocket_request(&m, source)?;
|
||||
}
|
||||
|
||||
for folder in self.find_many::<Folder>(FolderIden::WorkspaceId, &workspace.id, None)? {
|
||||
self.delete_folder(&folder, source)?;
|
||||
}
|
||||
for request in
|
||||
self.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, &workspace.id, None)?
|
||||
{
|
||||
self.delete_http_request(&request, source)?;
|
||||
}
|
||||
for request in
|
||||
self.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, &workspace.id, None)?
|
||||
{
|
||||
self.delete_grpc_request(&request, source)?;
|
||||
}
|
||||
for request in
|
||||
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &workspace.id, None)?
|
||||
{
|
||||
self.delete_websocket_request(&request, source)?;
|
||||
}
|
||||
|
||||
self.delete(workspace, source)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pub trait QueryManagerExt<'a, R> {
|
||||
impl<'a, R: Runtime, M: Manager<R>> QueryManagerExt<'a, R> for M {
|
||||
fn db(&'a self) -> DbContext<'a> {
|
||||
let qm = self.state::<QueryManager>();
|
||||
qm.inner().connect_2()
|
||||
qm.inner().connect()
|
||||
}
|
||||
|
||||
fn with_db<F, T>(&'a self, func: F) -> T
|
||||
@@ -59,7 +59,7 @@ impl QueryManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_2(&self) -> DbContext {
|
||||
pub fn connect(&self) -> DbContext {
|
||||
let conn = self
|
||||
.pool
|
||||
.lock()
|
||||
@@ -67,7 +67,7 @@ impl QueryManager {
|
||||
.get()
|
||||
.expect("Failed to get a new DB connection from the pool");
|
||||
DbContext {
|
||||
tx: self.events_tx.clone(),
|
||||
events_tx: self.events_tx.clone(),
|
||||
conn: ConnectionOrTx::Connection(conn),
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ impl QueryManager {
|
||||
.expect("Failed to get new DB connection from the pool");
|
||||
|
||||
let db_context = DbContext {
|
||||
tx: self.events_tx.clone(),
|
||||
events_tx: self.events_tx.clone(),
|
||||
conn: ConnectionOrTx::Connection(conn),
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@ impl QueryManager {
|
||||
.expect("Failed to start DB transaction");
|
||||
|
||||
let db_context = DbContext {
|
||||
tx: self.events_tx.clone(),
|
||||
events_tx: self.events_tx.clone(),
|
||||
conn: ConnectionOrTx::Transaction(&tx),
|
||||
};
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ pub enum ModelChangeEvent {
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum UpdateSource {
|
||||
Sync,
|
||||
Window { label: String },
|
||||
Plugin,
|
||||
Background,
|
||||
Import,
|
||||
Plugin,
|
||||
Sync,
|
||||
Window { label: String },
|
||||
}
|
||||
|
||||
impl UpdateSource {
|
||||
|
||||
@@ -13,7 +13,7 @@ use tokio::io::AsyncWriteExt;
|
||||
use ts_rs::TS;
|
||||
use yaak_models::models::{SyncState, WorkspaceMeta};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{get_workspace_export_resources, UpdateSource};
|
||||
use yaak_models::util::{UpdateSource, get_workspace_export_resources};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
@@ -454,7 +454,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
|
||||
let sync_dir_string = sync_dir.to_string_lossy().to_string();
|
||||
let db = app_handle.db();
|
||||
for workspace in upserted_models.workspaces {
|
||||
let r = match db.get_workspace_meta(&workspace) {
|
||||
let r = match db.get_workspace_meta(&workspace.id) {
|
||||
Some(m) => {
|
||||
if m.setting_sync_dir == Some(sync_dir_string.clone()) {
|
||||
// We don't need to update if unchanged
|
||||
|
||||
@@ -96,7 +96,7 @@ pub(crate) async fn list_connections<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> Result<Vec<WebsocketConnection>> {
|
||||
Ok(app_handle.db().list_websocket_connections_for_workspace(workspace_id)?)
|
||||
Ok(app_handle.db().list_websocket_connections(workspace_id)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
Reference in New Issue
Block a user