diff --git a/README.md b/README.md index d617135a..cc58b438 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ -# Tauri REST Client +# Yaak Network Toolkit -It's a REST client, yo. +The most fun you'll ever have working with APIs. + +## Common Commands + +```sh +# Start dev app +npm run tauri-dev + +# Migration commands +cd src-tauri +cargo sqlx migrate add +cargo sqlx migrate run --database-url 'sqlite://db.sqlite?mode=rw' +cargo sqlx prepare --database-url 'sqlite://db.sqlite' +``` diff --git a/src-tauri/migrations/20230316052652_key-values.sql b/src-tauri/migrations/20230316052652_key-values.sql new file mode 100644 index 00000000..54943962 --- /dev/null +++ b/src-tauri/migrations/20230316052652_key-values.sql @@ -0,0 +1,12 @@ +CREATE TABLE key_values +( + model TEXT NOT NULL DEFAULT 'key_value', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME, + namespace TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT NOT NULL, + + PRIMARY KEY (namespace, key) +); diff --git a/src-tauri/sqlx-data.json b/src-tauri/sqlx-data.json index 20a1790f..b82264b2 100644 --- a/src-tauri/sqlx-data.json +++ b/src-tauri/sqlx-data.json @@ -1,5 +1,59 @@ { "db": "SQLite", + "032c7a3861630c499f3e9785687be303aa95c023548e170572ad36eba90ecf84": { + "describe": { + "columns": [ + { + "name": "model", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 1, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 2, + "type_info": "Datetime" + }, + { + "name": "deleted_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "namespace", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "key", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "value", + "ordinal": 6, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + true, + false, + false, + false + ], + "parameters": { + "Right": 2 + } + }, + "query": "\n SELECT model, created_at, updated_at, deleted_at, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n " + }, "07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": { "describe": { "columns": [], @@ -30,7 +84,7 @@ }, "query": "\n DELETE FROM http_requests\n WHERE id = ?\n " }, - "795391626a74376de88fb8ff015f99fb10d6d8a5c9a0623dc279e833e8e86a07": { + "8423faeb5ce376acffe4c24804e0332c334edf929d9cea0086ee00c108e833fb": { "describe": { "columns": [ { @@ -39,142 +93,69 @@ "type_info": "Text" }, { - "name": "workspace_id", + "name": "model", "ordinal": 1, "type_info": "Text" }, { - "name": "created_at", + "name": "workspace_id", "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "url", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "method", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "body", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "body_type", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "headers!: sqlx::types::Json>", - "ordinal": 10, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - false, - true, - true, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT\n id,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n ORDER BY created_at DESC\n " - }, - "7f623d0e8f1ddad33d356e2d159b776a2bef1a238cb9200d74eb0c5e3983df85": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "workspace_id", - "ordinal": 1, "type_info": "Text" }, { "name": "request_id", - "ordinal": 2, + "ordinal": 3, "type_info": "Text" }, { "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", "ordinal": 4, "type_info": "Datetime" }, { - "name": "created_at", + "name": "deleted_at", "ordinal": 5, "type_info": "Datetime" }, { - "name": "status", + "name": "created_at", "ordinal": 6, + "type_info": "Datetime" + }, + { + "name": "status", + "ordinal": 7, "type_info": "Int64" }, { "name": "status_reason", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "body", "ordinal": 8, "type_info": "Text" }, { - "name": "elapsed", + "name": "body", "ordinal": 9, + "type_info": "Text" + }, + { + "name": "elapsed", + "ordinal": 10, "type_info": "Int64" }, { "name": "url", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "error", "ordinal": 11, "type_info": "Text" }, { - "name": "headers!: sqlx::types::Json>", + "name": "error", "ordinal": 12, "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 13, + "type_info": "Text" } ], "nullable": [ @@ -182,6 +163,7 @@ false, false, false, + false, true, false, false, @@ -196,9 +178,19 @@ "Right": 1 } }, - "query": "\n SELECT id, workspace_id, request_id, updated_at, deleted_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n " + "query": "\n SELECT id, model, workspace_id, request_id, updated_at, deleted_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n " }, - "8069c0bd326f659faca7b45b03e5317d7339a168f4cd7776d9f84304bb7ae7ac": { + "913f3c3a46b1834c4cd8367aed9d5a9659a1d775d8771e9f5bf9a5aa41197356": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 8 + } + }, + "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n url = excluded.url\n " + }, + "9fcbf8317858427b2e75b29ce64deeed1cc77ef22a35463ab65eee89eb25fa02": { "describe": { "columns": [ { @@ -207,28 +199,33 @@ "type_info": "Text" }, { - "name": "created_at", + "name": "model", "ordinal": 1, - "type_info": "Datetime" + "type_info": "Text" }, { - "name": "updated_at", + "name": "created_at", "ordinal": 2, "type_info": "Datetime" }, { - "name": "deleted_at", + "name": "updated_at", "ordinal": 3, "type_info": "Datetime" }, { - "name": "name", + "name": "deleted_at", "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 5, "type_info": "Text" }, { "name": "description", - "ordinal": 5, + "ordinal": 6, "type_info": "Text" } ], @@ -236,15 +233,16 @@ false, false, false, + false, true, false, false ], "parameters": { - "Right": 0 + "Right": 1 } }, - "query": "\n SELECT id, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n " + "query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n WHERE id = ?\n " }, "a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": { "describe": { @@ -256,7 +254,7 @@ }, "query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n " }, - "d572673b8eadbd5238c7a972b581fa8788f92a44fcc8decd0f1333e662f3374b": { + "bca6dd1eec0cdf4de57323f7b88e8578ebe44b6c9606e38cbaacc3e0094b434d": { "describe": { "columns": [ { @@ -265,54 +263,59 @@ "type_info": "Text" }, { - "name": "workspace_id", + "name": "model", "ordinal": 1, "type_info": "Text" }, { - "name": "created_at", + "name": "workspace_id", "ordinal": 2, - "type_info": "Datetime" + "type_info": "Text" }, { - "name": "updated_at", + "name": "created_at", "ordinal": 3, "type_info": "Datetime" }, { - "name": "deleted_at", + "name": "updated_at", "ordinal": 4, "type_info": "Datetime" }, { - "name": "name", + "name": "deleted_at", "ordinal": 5, - "type_info": "Text" + "type_info": "Datetime" }, { - "name": "url", + "name": "name", "ordinal": 6, "type_info": "Text" }, { - "name": "method", + "name": "url", "ordinal": 7, "type_info": "Text" }, { - "name": "body", + "name": "method", "ordinal": 8, "type_info": "Text" }, { - "name": "body_type", + "name": "body", "ordinal": 9, "type_info": "Text" }, { - "name": "headers!: sqlx::types::Json>", + "name": "body_type", "ordinal": 10, "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 11, + "type_info": "Text" } ], "nullable": [ @@ -320,6 +323,7 @@ false, false, false, + false, true, false, false, @@ -332,7 +336,113 @@ "Right": 1 } }, - "query": "\n SELECT\n id,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " + "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n " + }, + "d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 3 + } + }, + "query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n " + }, + "d9b614f9f0de223474464e0a30e663f6f4467f85d6f6abaacd8b76d9289ba38f": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "model", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "workspace_id", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "request_id", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "updated_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "deleted_at", + "ordinal": 5, + "type_info": "Datetime" + }, + { + "name": "created_at", + "ordinal": 6, + "type_info": "Datetime" + }, + { + "name": "status", + "ordinal": 7, + "type_info": "Int64" + }, + { + "name": "status_reason", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "body", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "elapsed", + "ordinal": 10, + "type_info": "Int64" + }, + { + "name": "url", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "error", + "ordinal": 12, + "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 13, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT id, model, workspace_id, request_id, updated_at, deleted_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE id = ?\n " }, "e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": { "describe": { @@ -344,15 +454,143 @@ }, "query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n " }, - "f0bc12d939b0f26774a3e599be7518510d298f327cca8743231242dd6105768c": { + "eb7b10c632c871122d9e50cacba1f52604c366d888f014ac7f0a2642dbd27839": { "describe": { - "columns": [], - "nullable": [], + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "model", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 2, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "deleted_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 6, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + true, + false, + false + ], "parameters": { - "Right": 8 + "Right": 0 } }, - "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n headers,\n updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n url = excluded.url\n " + "query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n " + }, + "eda13351c05e67885a71a1b9fc7f2d0641d94ca5f4be824d4a0edad5f1ec657e": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "model", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "workspace_id", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "deleted_at", + "ordinal": 5, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "url", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "method", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "body", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "body_type", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 11, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " }, "f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": { "describe": { @@ -363,143 +601,5 @@ } }, "query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n " - }, - "fa5011146663ba5675764f33bf55a86b8274aa18737aff427fd1e3fb74ef3535": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 1, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "description", - "ordinal": 5, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - true, - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT id, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n WHERE id = ?\n " - }, - "fb2c2328678bbdcb64b79ced26f3d7a1b08d315ef6dedfe4d5ae4231c861b079": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "workspace_id", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "request_id", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "created_at", - "ordinal": 5, - "type_info": "Datetime" - }, - { - "name": "status", - "ordinal": 6, - "type_info": "Int64" - }, - { - "name": "status_reason", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "body", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "elapsed", - "ordinal": 9, - "type_info": "Int64" - }, - { - "name": "url", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "error", - "ordinal": 11, - "type_info": "Text" - }, - { - "name": "headers!: sqlx::types::Json>", - "ordinal": 12, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - true, - false, - false, - false, - true, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT id, workspace_id, request_id, updated_at, deleted_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_responses\n WHERE id = ?\n " } } \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4ec71c20..32ca35e5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -12,16 +12,16 @@ use std::env; use std::env::current_dir; use std::fs::create_dir_all; -use http::header::{ACCEPT, HeaderName, USER_AGENT}; use http::{HeaderMap, HeaderValue, Method}; +use http::header::{ACCEPT, HeaderName, USER_AGENT}; use reqwest::redirect::Policy; +use sqlx::{Pool, Sqlite}; use sqlx::migrate::Migrator; use sqlx::sqlite::SqlitePoolOptions; use sqlx::types::Json; -use sqlx::{Pool, Sqlite}; -use tauri::regex::Regex; use tauri::{AppHandle, Menu, State, Submenu, Wry}; use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowEvent}; +use tauri::regex::Regex; use tokio::sync::Mutex; use window_ext::WindowExt; @@ -51,9 +51,9 @@ async fn migrate_db( .path_resolver() .resolve_resource("migrations") .expect("failed to resolve resource"); + println!("Running migrations at {}", p.to_string_lossy()); let m = Migrator::new(p).await.expect("Failed to load migrations"); m.run(pool).await.expect("Failed to run migrations"); - println!("Migrations ran"); Ok(()) } @@ -194,6 +194,38 @@ async fn response_err( Ok(response.id) } +#[tauri::command] +async fn get_key_value( + namespace: &str, + key: &str, + db_instance: State<'_, Mutex>>, +) -> Result, ()> { + let pool = &*db_instance.lock().await; + let result = models::get_key_value(namespace, key, pool).await; + Ok(result) +} + +#[tauri::command] +async fn set_key_value( + namespace: &str, + key: &str, + value: &str, + app_handle: AppHandle, + db_instance: State<'_, Mutex>>, +) -> Result<(), String> { + let pool = &*db_instance.lock().await; + let created_key_value = + models::set_key_value(namespace, key, value, pool) + .await + .expect("Failed to create key value"); + + app_handle + .emit_all("updated_key_value", &created_key_value) + .unwrap(); + + Ok(()) +} + #[tauri::command] async fn create_request( workspace_id: &str, @@ -432,6 +464,8 @@ fn main() { update_request, delete_request, responses, + get_key_value, + set_key_value, delete_response, delete_all_responses, ]) diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 0ab91f96..33fd6583 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -8,6 +8,7 @@ use sqlx::{Pool, Sqlite}; #[serde(rename_all = "camelCase")] pub struct Workspace { pub id: String, + pub model: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub deleted_at: Option, @@ -26,6 +27,7 @@ pub struct HttpRequestHeader { #[serde(rename_all = "camelCase")] pub struct HttpRequest { pub id: String, + pub model: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub deleted_at: Option, @@ -49,6 +51,7 @@ pub struct HttpResponseHeader { #[serde(rename_all = "camelCase")] pub struct HttpResponse { pub id: String, + pub model: String, pub workspace_id: String, pub request_id: String, pub created_at: NaiveDateTime, @@ -63,11 +66,63 @@ pub struct HttpResponse { pub headers: Json>, } +#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KeyValue { + pub model: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub deleted_at: Option, + pub namespace: String, + pub key: String, + pub value: String, +} + +pub async fn set_key_value( + namespace: &str, + key: &str, + value: &str, + pool: &Pool, +) -> Option { + sqlx::query!( + r#" + INSERT INTO key_values (namespace, key, value) + VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET + updated_at = CURRENT_TIMESTAMP, + value = excluded.value + "#, + namespace, + key, + value, + ) + .execute(pool) + .await + .expect("Failed to insert key value"); + + get_key_value(namespace, key, pool).await +} + +pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool) -> Option { + sqlx::query_as!( + KeyValue, + r#" + SELECT model, created_at, updated_at, deleted_at, namespace, key, value + FROM key_values + WHERE namespace = ? AND key = ? + "#, + namespace, + key, + ) + .fetch_one(pool) + .await + .ok() +} + pub async fn find_workspaces(pool: &Pool) -> Result, sqlx::Error> { sqlx::query_as!( Workspace, r#" - SELECT id, created_at, updated_at, deleted_at, name, description + SELECT id, model, created_at, updated_at, deleted_at, name, description FROM workspaces "#, ) @@ -79,7 +134,7 @@ pub async fn get_workspace(id: &str, pool: &Pool) -> Result) -> Result) -> Result>" FROM http_requests WHERE id = ? - ORDER BY created_at DESC "#, id, ) @@ -306,7 +361,7 @@ pub async fn get_response(id: &str, pool: &Pool) -> Result>" FROM http_responses @@ -325,7 +380,7 @@ pub async fn find_responses( sqlx::query_as!( HttpResponse, r#" - SELECT id, workspace_id, request_id, updated_at, deleted_at, + SELECT id, model, workspace_id, request_id, updated_at, deleted_at, created_at, status, status_reason, body, elapsed, url, error, headers AS "headers!: sqlx::types::Json>" FROM http_responses diff --git a/src-web/components/App.tsx b/src-web/components/App.tsx index f2119190..c9163e23 100644 --- a/src-web/components/App.tsx +++ b/src-web/components/App.tsx @@ -2,15 +2,20 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { listen } from '@tauri-apps/api/event'; import { MotionConfig } from 'framer-motion'; import { HelmetProvider } from 'react-helmet-async'; +import { keyValueQueryKey } from '../hooks/useKeyValues'; import { requestsQueryKey } from '../hooks/useRequests'; import { responsesQueryKey } from '../hooks/useResponses'; import { DEFAULT_FONT_SIZE } from '../lib/constants'; -import type { HttpRequest, HttpResponse } from '../lib/models'; +import type { HttpRequest, HttpResponse, KeyValue } from '../lib/models'; import { convertDates } from '../lib/models'; import { AppRouter } from './AppRouter'; const queryClient = new QueryClient(); +await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => { + queryClient.setQueryData(keyValueQueryKey(keyValue.namespace, keyValue.key), keyValue); +}); + await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => { queryClient.setQueryData( requestsQueryKey(request.workspaceId), diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 02085fbe..e7196e73 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -19,7 +19,6 @@ interface Props { export function Sidebar({ className }: Props) { const requests = useRequests(); const activeRequest = useActiveRequest(); - const deleteRequest = useDeleteRequest(activeRequest); const createRequest = useCreateRequest({ navigateAfter: true }); const { appearance, toggleAppearance } = useTheme(); return ( diff --git a/src-web/hooks/useCreateRequest.ts b/src-web/hooks/useCreateRequest.ts index c309f15d..5c3d3676 100644 --- a/src-web/hooks/useCreateRequest.ts +++ b/src-web/hooks/useCreateRequest.ts @@ -8,7 +8,7 @@ export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean }) const workspace = useActiveWorkspace(); const navigate = useNavigate(); return useMutation>({ - mutationFn: async (patch) => { + mutationFn: (patch) => { if (workspace === null) { throw new Error("Cannot create request when there's no active workspace"); } diff --git a/src-web/hooks/useKeyValues.ts b/src-web/hooks/useKeyValues.ts new file mode 100644 index 00000000..55b7b9b2 --- /dev/null +++ b/src-web/hooks/useKeyValues.ts @@ -0,0 +1,26 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { invoke } from '@tauri-apps/api'; +import type { KeyValue } from '../lib/models'; + +export function keyValueQueryKey(namespace: string, key: string) { + return ['key_value', { namespace, key }]; +} + +export function useKeyValues(namespace: string, key: string) { + const query = useQuery({ + initialData: null, + queryKey: keyValueQueryKey(namespace, key), + queryFn: async () => invoke('get_key_value', { namespace, key }), + }); + + const mutate = useMutation({ + mutationFn: (value) => { + return invoke('set_key_value', { namespace, key, value }); + }, + }); + + return { + value: query.data?.value ?? null, + set: (value: KeyValue['value']) => mutate.mutate(value), + }; +} diff --git a/src-web/hooks/useRequests.ts b/src-web/hooks/useRequests.ts index 69bbd2e7..d61a6b21 100644 --- a/src-web/hooks/useRequests.ts +++ b/src-web/hooks/useRequests.ts @@ -5,7 +5,7 @@ import { convertDates } from '../lib/models'; import { useActiveWorkspace } from './useActiveWorkspace'; export function requestsQueryKey(workspaceId: string) { - return ['requests', { workspaceId }]; + return ['http_requests', { workspaceId }]; } export function useRequests() { diff --git a/src-web/hooks/useResponses.ts b/src-web/hooks/useResponses.ts index fce592f4..f99e7f6e 100644 --- a/src-web/hooks/useResponses.ts +++ b/src-web/hooks/useResponses.ts @@ -5,7 +5,7 @@ import { convertDates } from '../lib/models'; import { useActiveRequest } from './useActiveRequest'; export function responsesQueryKey(requestId: string) { - return ['responses', { requestId }]; + return ['http_responses', { requestId }]; } export function useResponses() { diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index 67731dc5..30b77f46 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -1,12 +1,13 @@ export interface BaseModel { - id: string; - workspaceId: string; - createdAt: Date; - updatedAt: Date; - deletedAt: Date | null; + readonly id: string; + readonly workspaceId: string; + readonly createdAt: Date; + readonly updatedAt: Date; + readonly deletedAt: Date | null; } export interface Workspace extends BaseModel { + readonly model: 'workspace'; name: string; description: string; } @@ -17,6 +18,7 @@ export interface HttpHeader { } export interface HttpRequest extends BaseModel { + readonly model: 'http_request'; name: string; url: string; body: string | null; @@ -25,19 +27,28 @@ export interface HttpRequest extends BaseModel { headers: HttpHeader[]; } -export interface HttpResponse extends BaseModel { - id: string; - requestId: string; - body: string; - error: string; - status: number; - elapsed: number; - statusReason: string; - url: string; - headers: HttpHeader[]; +export interface KeyValue extends Omit { + readonly model: 'key_value'; + readonly key: string; + readonly namespace: string; + value: string; } -export function convertDates(m: T): T { +export interface HttpResponse extends BaseModel { + readonly model: 'http_response'; + readonly requestId: string; + readonly body: string; + readonly error: string; + readonly status: number; + readonly elapsed: number; + readonly statusReason: string; + readonly url: string; + readonly headers: HttpHeader[]; +} + +export function convertDates>( + m: T, +): T { return { ...m, createdAt: convertDate(m.createdAt),