Flatten migrations, kvs lib, fix tabs

This commit is contained in:
Gregory Schier
2023-03-17 08:36:21 -07:00
parent 637c220475
commit 10616001df
23 changed files with 406 additions and 392 deletions

View File

@@ -1,17 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak App</title>
<!-- <script src="http://localhost:8097"></script>-->
</head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak App</title>
<!-- <script src="http://localhost:8097"></script>-->
<style>
body {
background-color: white;
}
<body>
<div id="root"></div>
<div id="cm-portal" class="cm-portal" style="pointer-events: auto"></div>
<div id="radix-portal" class="cm-portal"></div>
<script type="module" src="/src-web/main.tsx"></script>
</body>
@media (prefers-color-scheme: dark) {
body {
background-color: black;
}
}
</style>
</head>
<body>
<div id="root"></div>
<div id="cm-portal" class="cm-portal" style="pointer-events: auto"></div>
<div id="radix-portal" class="cm-portal"></div>
<script type="module" src="/src-web/main.tsx"></script>
</body>
</html>

View File

@@ -1,39 +1,65 @@
CREATE TABLE key_values
(
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,
PRIMARY KEY (namespace, key)
);
CREATE TABLE workspaces
(
id TEXT NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'workspace' NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted_at DATETIME,
name TEXT NOT NULL,
description TEXT NOT NULL
name TEXT NOT NULL,
description TEXT NOT NULL
);
CREATE TABLE http_requests
(
id TEXT NOT NULL PRIMARY KEY,
workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'http_request' NOT NULL,
workspace_id TEXT NOT NULL
REFERENCES workspaces
ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted_at DATETIME,
name TEXT NOT NULL,
url TEXT NOT NULL,
method TEXT NOT NULL,
headers TEXT NOT NULL,
body TEXT
name TEXT NOT NULL,
url TEXT NOT NULL,
method TEXT NOT NULL,
headers TEXT NOT NULL,
body TEXT,
body_type TEXT
);
CREATE TABLE http_responses
(
id TEXT NOT NULL PRIMARY KEY,
request_id TEXT NOT NULL REFERENCES http_requests (id) ON DELETE CASCADE,
workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'http_response' NOT NULL,
request_id TEXT NOT NULL
REFERENCES http_requests
ON DELETE CASCADE,
workspace_id TEXT NOT NULL
REFERENCES workspaces
ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted_at DATETIME,
elapsed INTEGER NOT NULL,
status INTEGER NOT NULL,
elapsed INTEGER NOT NULL,
status INTEGER NOT NULL,
status_reason TEXT,
url TEXT NOT NULL,
body TEXT NOT NULL,
headers TEXT NOT NULL
url TEXT NOT NULL,
body TEXT NOT NULL,
headers TEXT NOT NULL,
error TEXT
);

View File

@@ -1 +0,0 @@
ALTER TABLE http_responses ADD COLUMN error TEXT NULL;

View File

@@ -1 +0,0 @@
ALTER TABLE http_requests ADD COLUMN body_type TEXT NULL;

View File

@@ -1,12 +0,0 @@
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)
);

View File

@@ -1,3 +0,0 @@
ALTER TABLE http_responses ADD COLUMN model TEXT DEFAULT 'http_response';
ALTER TABLE http_requests ADD COLUMN model TEXT DEFAULT 'http_request';
ALTER TABLE workspaces ADD COLUMN model TEXT DEFAULT 'workspace';

View File

@@ -1,6 +1,6 @@
{
"db": "SQLite",
"032c7a3861630c499f3e9785687be303aa95c023548e170572ad36eba90ecf84": {
"06aaf8f4a17566f1d25da2a60f0baf4b5fc28c3cf0c001a84e25edf9eab3c7e3": {
"describe": {
"columns": [
{
@@ -18,24 +18,19 @@
"ordinal": 2,
"type_info": "Datetime"
},
{
"name": "deleted_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "namespace",
"ordinal": 4,
"ordinal": 3,
"type_info": "Text"
},
{
"name": "key",
"ordinal": 5,
"ordinal": 4,
"type_info": "Text"
},
{
"name": "value",
"ordinal": 6,
"ordinal": 5,
"type_info": "Text"
}
],
@@ -43,7 +38,6 @@
false,
false,
false,
true,
false,
false,
false
@@ -52,7 +46,7 @@
"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 "
"query": "\n SELECT model, created_at, updated_at, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n "
},
"07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": {
"describe": {
@@ -84,7 +78,7 @@
},
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
},
"8423faeb5ce376acffe4c24804e0332c334edf929d9cea0086ee00c108e833fb": {
"68b7b17a25d415ce90b33aef16418d668f7ff6275b383bf21c16cafb38cca342": {
"describe": {
"columns": [
{
@@ -103,9 +97,9 @@
"type_info": "Text"
},
{
"name": "request_id",
"name": "created_at",
"ordinal": 3,
"type_info": "Text"
"type_info": "Datetime"
},
{
"name": "updated_at",
@@ -113,48 +107,33 @@
"type_info": "Datetime"
},
{
"name": "deleted_at",
"name": "name",
"ordinal": 5,
"type_info": "Datetime"
"type_info": "Text"
},
{
"name": "created_at",
"name": "url",
"ordinal": 6,
"type_info": "Datetime"
"type_info": "Text"
},
{
"name": "status",
"name": "method",
"ordinal": 7,
"type_info": "Int64"
},
{
"name": "status_reason",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "body",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "body_type",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "elapsed",
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
"ordinal": 10,
"type_info": "Int64"
},
{
"name": "url",
"ordinal": 11,
"type_info": "Text"
},
{
"name": "error",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
"ordinal": 13,
"type_info": "Text"
}
],
@@ -164,13 +143,10 @@
false,
false,
false,
true,
false,
false,
false,
true,
false,
false,
false,
true,
false
],
@@ -178,7 +154,7 @@
"Right": 1
}
},
"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<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n "
},
"913f3c3a46b1834c4cd8367aed9d5a9659a1d775d8771e9f5bf9a5aa41197356": {
"describe": {
@@ -190,60 +166,6 @@
},
"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": [
{
"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": 1
}
},
"query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n WHERE id = ?\n "
},
"a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": {
"describe": {
"columns": [],
@@ -254,7 +176,7 @@
},
"query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
},
"bca6dd1eec0cdf4de57323f7b88e8578ebe44b6c9606e38cbaacc3e0094b434d": {
"caf3f21bf291dfbd36446592066e96c1f83abe96f6ea9211a3e049eb9c58a8c8": {
"describe": {
"columns": [
{
@@ -267,54 +189,24 @@
"ordinal": 1,
"type_info": "Text"
},
{
"name": "workspace_id",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 3,
"ordinal": 2,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 4,
"type_info": "Datetime"
},
{
"name": "deleted_at",
"ordinal": 5,
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "name",
"ordinal": 6,
"ordinal": 4,
"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<Vec<HttpRequestHeader>>",
"ordinal": 11,
"name": "description",
"ordinal": 5,
"type_info": "Text"
}
],
@@ -324,31 +216,63 @@
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<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n "
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces WHERE id = ?\n "
},
"d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": {
"cea4cae52f16ec78aca9a47b17117422d4f165e5a3b308c70fd1a180382475ea": {
"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": "name",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "description",
"ordinal": 5,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false,
false,
false,
false
],
"parameters": {
"Right": 3
"Right": 0
}
},
"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 "
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces\n "
},
"d9b614f9f0de223474464e0a30e663f6f4467f85d6f6abaacd8b76d9289ba38f": {
"d5ad6d5f82fe837fa9215bd4619ec18a7c95b3088d4fbf9825f2d1d28069d1ce": {
"describe": {
"columns": [
{
@@ -377,48 +301,43 @@
"type_info": "Datetime"
},
{
"name": "deleted_at",
"name": "created_at",
"ordinal": 5,
"type_info": "Datetime"
},
{
"name": "created_at",
"ordinal": 6,
"type_info": "Datetime"
},
{
"name": "status",
"ordinal": 7,
"ordinal": 6,
"type_info": "Int64"
},
{
"name": "status_reason",
"ordinal": 8,
"ordinal": 7,
"type_info": "Text"
},
{
"name": "body",
"ordinal": 9,
"ordinal": 8,
"type_info": "Text"
},
{
"name": "elapsed",
"ordinal": 10,
"ordinal": 9,
"type_info": "Int64"
},
{
"name": "url",
"ordinal": 11,
"ordinal": 10,
"type_info": "Text"
},
{
"name": "error",
"ordinal": 12,
"ordinal": 11,
"type_info": "Text"
},
{
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
"ordinal": 13,
"ordinal": 12,
"type_info": "Text"
}
],
@@ -428,7 +347,6 @@
false,
false,
false,
true,
false,
false,
true,
@@ -442,73 +360,19 @@
"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<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
"query": "\n SELECT id, model, workspace_id, request_id, updated_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
},
"e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": {
"d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 9
"Right": 3
}
},
"query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
"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 "
},
"eb7b10c632c871122d9e50cacba1f52604c366d888f014ac7f0a2642dbd27839": {
"describe": {
"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": 0
}
},
"query": "\n SELECT id, model, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n "
},
"eda13351c05e67885a71a1b9fc7f2d0641d94ca5f4be824d4a0edad5f1ec657e": {
"d9ea350bc21ac2f51f6dcb9713328ec330f0e12105da70bf5a6eff9601e32a85": {
"describe": {
"columns": [
{
@@ -536,39 +400,34 @@
"ordinal": 4,
"type_info": "Datetime"
},
{
"name": "deleted_at",
"ordinal": 5,
"type_info": "Datetime"
},
{
"name": "name",
"ordinal": 6,
"ordinal": 5,
"type_info": "Text"
},
{
"name": "url",
"ordinal": 7,
"ordinal": 6,
"type_info": "Text"
},
{
"name": "method",
"ordinal": 8,
"ordinal": 7,
"type_info": "Text"
},
{
"name": "body",
"ordinal": 9,
"ordinal": 8,
"type_info": "Text"
},
{
"name": "body_type",
"ordinal": 10,
"ordinal": 9,
"type_info": "Text"
},
{
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
"ordinal": 11,
"ordinal": 10,
"type_info": "Text"
}
],
@@ -578,7 +437,6 @@
false,
false,
false,
true,
false,
false,
false,
@@ -590,7 +448,107 @@
"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<Vec<HttpRequestHeader>>\"\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 name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n "
},
"e3ade0a69348d512e47e964bded9d7d890b92fdc1e01c6c22fa5e91f943639f2": {
"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": "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<Vec<HttpResponseHeader>>",
"ordinal": 12,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false,
true,
false
],
"parameters": {
"Right": 1
}
},
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
},
"e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 9
}
},
"query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
},
"f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
"describe": {

View File

@@ -354,7 +354,7 @@ async fn workspaces(
.await
.expect("Failed to find workspaces");
if workspaces.is_empty() {
let workspace = models::create_workspace("Default", "This is the default workspace", pool)
let workspace = models::create_workspace("My Project", "This is the default workspace", pool)
.await
.expect("Failed to create workspace");
Ok(vec![workspace])

View File

@@ -11,7 +11,6 @@ pub struct Workspace {
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
pub name: String,
pub description: String,
}
@@ -30,7 +29,6 @@ pub struct HttpRequest {
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
pub workspace_id: String,
pub name: String,
pub url: String,
@@ -56,7 +54,6 @@ pub struct HttpResponse {
pub request_id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
pub error: Option<String>,
pub url: String,
pub elapsed: i64,
@@ -72,7 +69,6 @@ pub struct KeyValue {
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
pub namespace: String,
pub key: String,
pub value: String,
@@ -106,7 +102,7 @@ pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> O
sqlx::query_as!(
KeyValue,
r#"
SELECT model, created_at, updated_at, deleted_at, namespace, key, value
SELECT model, created_at, updated_at, namespace, key, value
FROM key_values
WHERE namespace = ? AND key = ?
"#,
@@ -122,7 +118,7 @@ pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx
sqlx::query_as!(
Workspace,
r#"
SELECT id, model, created_at, updated_at, deleted_at, name, description
SELECT id, model, created_at, updated_at, name, description
FROM workspaces
"#,
)
@@ -134,9 +130,8 @@ pub async fn get_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace, s
sqlx::query_as!(
Workspace,
r#"
SELECT id, model, created_at, updated_at, deleted_at, name, description
FROM workspaces
WHERE id = ?
SELECT id, model, created_at, updated_at, name, description
FROM workspaces WHERE id = ?
"#,
id,
)
@@ -236,7 +231,6 @@ pub async fn find_requests(
workspace_id,
created_at,
updated_at,
deleted_at,
name,
url,
method,
@@ -262,7 +256,6 @@ pub async fn get_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, s
workspace_id,
created_at,
updated_at,
deleted_at,
name,
url,
method,
@@ -361,7 +354,7 @@ pub async fn get_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse,
sqlx::query_as_unchecked!(
HttpResponse,
r#"
SELECT id, model, workspace_id, request_id, updated_at, deleted_at, created_at,
SELECT id, model, workspace_id, request_id, updated_at, created_at,
status, status_reason, body, elapsed, url, error,
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
FROM http_responses
@@ -380,7 +373,7 @@ pub async fn find_responses(
sqlx::query_as!(
HttpResponse,
r#"
SELECT id, model, workspace_id, request_id, updated_at, deleted_at,
SELECT id, model, workspace_id, request_id, updated_at,
created_at, status, status_reason, body, elapsed, url, error,
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
FROM http_responses

View File

@@ -5,10 +5,11 @@ import { listen } from '@tauri-apps/api/event';
import { MotionConfig } from 'framer-motion';
import { HelmetProvider } from 'react-helmet-async';
import { matchPath } from 'react-router-dom';
import { keyValueQueryKey } from '../hooks/useKeyValues';
import { keyValueQueryKey } from '../hooks/useKeyValue';
import { requestsQueryKey } from '../hooks/useRequests';
import { responsesQueryKey } from '../hooks/useResponses';
import { DEFAULT_FONT_SIZE } from '../lib/constants';
import { extractKeyValue } from '../lib/keyValueStore';
import type { HttpRequest, HttpResponse, KeyValue } from '../lib/models';
import { convertDates } from '../lib/models';
import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter';
@@ -16,7 +17,7 @@ import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter';
const queryClient = new QueryClient();
await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => {
queryClient.setQueryData(keyValueQueryKey(keyValue), keyValue);
queryClient.setQueryData(keyValueQueryKey(keyValue), extractKeyValue(keyValue));
});
await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => {

View File

@@ -1,7 +1,8 @@
import classnames from 'classnames';
import { act } from 'react-dom/test-utils';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
import { useKeyValues } from '../hooks/useKeyValues';
import { useKeyValue } from '../hooks/useKeyValue';
import { useSendRequest } from '../hooks/useSendRequest';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { tryFormatJson } from '../lib/formatters';
@@ -21,7 +22,7 @@ export function RequestPane({ fullHeight, className }: Props) {
const updateRequest = useUpdateRequest(activeRequest);
const sendRequest = useSendRequest(activeRequest);
const responseLoading = useIsResponseLoading();
const activeTab = useKeyValues({
const activeTab = useKeyValue<string>({
key: ['active_request_body_tab', activeRequest?.id ?? 'n/a'],
initialValue: 'body',
});
@@ -61,7 +62,6 @@ export function RequestPane({ fullHeight, className }: Props) {
{ value: 'auth', label: 'Auth' },
]}
className="mt-2"
defaultValue="body"
label="Request body"
>
<TabContent value="headers">

View File

@@ -3,10 +3,11 @@ import React, { useRef, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useKeyValues } from '../hooks/useKeyValues';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRequests } from '../hooks/useRequests';
import { useTheme } from '../hooks/useTheme';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { clamp } from '../lib/clamp';
import type { HttpRequest } from '../lib/models';
import { Button } from './core/Button';
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
@@ -19,11 +20,12 @@ interface Props {
className?: string;
}
const MIN_WIDTH = 130;
const MIN_WIDTH = 110;
const MAX_WIDTH = 500;
export function Sidebar({ className }: Props) {
const [isDragging, setIsDragging] = useState<boolean>(false);
const width = useKeyValues<number>({ key: 'sidebar_width', initialValue: 200 });
const width = useKeyValue<number>({ key: 'sidebar_width', initialValue: 200 });
const requests = useRequests();
const activeRequest = useActiveRequest();
const createRequest = useCreateRequest({ navigateAfter: true });
@@ -43,7 +45,7 @@ export function Sidebar({ className }: Props) {
const startWidth = width.value;
moveState.current = {
move: (e: MouseEvent) => {
const newWidth = Math.max(MIN_WIDTH, startWidth + (e.clientX - mouseStartX));
const newWidth = clamp(startWidth + (e.clientX - mouseStartX), MIN_WIDTH, MAX_WIDTH);
width.set(newWidth);
},
up: () => {
@@ -73,8 +75,8 @@ export function Sidebar({ className }: Props) {
>
<div
className={classnames(
'transition-colors w-[1px] group-hover:bg-white/10 h-full pointer-events-none',
isDragging && '!bg-white/20',
'transition-colors w-[1px] group-hover:bg-gray-300 h-full pointer-events-none',
isDragging && '!bg-blue-500/70',
)}
/>
</div>

View File

@@ -10,7 +10,6 @@ import { HStack } from '../Stacks';
import './Tabs.css';
interface Props {
defaultValue?: string;
label: string;
onChangeValue: (value: string) => void;
value: string;
@@ -31,7 +30,6 @@ interface Props {
export function Tabs({
value,
onChangeValue,
defaultValue,
label,
children,
tabs,
@@ -40,7 +38,7 @@ export function Tabs({
}: Props) {
return (
<T.Root
defaultValue={defaultValue}
value={value}
onValueChange={onChangeValue}
className={classnames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}
>

View File

@@ -4,5 +4,5 @@ export function useIsResponseLoading(): boolean {
const responses = useResponses();
const response = responses[responses.length - 1];
if (!response) return false;
return !(response.body || response.error);
return !(response.body || response.status || response.error);
}

View File

@@ -0,0 +1,39 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
const DEFAULT_NAMESPACE = 'app';
export function keyValueQueryKey({
namespace = DEFAULT_NAMESPACE,
key,
}: {
namespace?: string;
key: string | string[];
}) {
return ['key_value', { namespace, key: buildKeyValueKey(key) }];
}
export function useKeyValue<T extends string | number | boolean>({
namespace = DEFAULT_NAMESPACE,
key,
initialValue,
}: {
namespace?: string;
key: string | string[];
initialValue: T;
}) {
const query = useQuery<T>({
initialData: initialValue,
queryKey: keyValueQueryKey({ namespace, key }),
queryFn: async () => getKeyValue({ namespace, key, fallback: initialValue }),
});
const mutate = useMutation<T, unknown, T>({
mutationFn: (value) => setKeyValue<T>({ namespace, key, value }),
});
return {
value: query.data,
set: (value: T) => mutate.mutate(value),
};
}

View File

@@ -1,57 +0,0 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { KeyValue } from '../lib/models';
const DEFAULT_NAMESPACE = 'app';
export function keyValueQueryKey({
namespace = DEFAULT_NAMESPACE,
key,
}: {
namespace?: string;
key: string | string[];
}) {
return ['key_value', { namespace, key: buildKey(key) }];
}
export function useKeyValues<T extends string | number | boolean>({
namespace = DEFAULT_NAMESPACE,
key,
initialValue,
}: {
namespace?: string;
key: string | string[];
initialValue: T;
}) {
const query = useQuery<KeyValue | null>({
initialData: null,
queryKey: keyValueQueryKey({ namespace, key }),
queryFn: async () => invoke('get_key_value', { namespace, key: buildKey(key) }),
});
const mutate = useMutation<KeyValue, unknown, T>({
mutationFn: (value) => {
return invoke('set_key_value', {
namespace,
key: buildKey(key),
value: JSON.stringify(value),
});
},
});
let value: T;
try {
value = JSON.parse(query.data?.value ?? JSON.stringify(initialValue));
} catch (e) {
value = initialValue;
}
return {
value,
set: (value: T) => mutate.mutate(value),
};
}
function buildKey(key: string | string[]): string {
if (typeof key === 'string') return key;
return key.join('::');
}

View File

@@ -1,7 +1,7 @@
import { useKeyValues } from './useKeyValues';
import { useKeyValue } from './useKeyValue';
export function useResponseViewMode(requestId?: string): [string, () => void] {
const v = useKeyValues({
const v = useKeyValue<string>({
namespace: 'app',
key: ['response_view_mode', requestId ?? 'n/a'],
initialValue: 'pretty',

View File

@@ -1,3 +1,4 @@
import { app } from '@tauri-apps/api';
import { useEffect } from 'react';
import type { Appearance } from '../lib/theme/window';
import {
@@ -5,10 +6,13 @@ import {
setAppearance,
subscribeToPreferredAppearanceChange,
} from '../lib/theme/window';
import { useKeyValues } from './useKeyValues';
import { useKeyValue } from './useKeyValue';
export function useTheme() {
const appearanceKv = useKeyValues({ key: 'appearance', initialValue: getAppearance() });
const appearanceKv = useKeyValue<Appearance>({
key: 'appearance',
initialValue: getAppearance(),
});
const themeChange = (appearance: Appearance) => {
appearanceKv.set(appearance);
@@ -22,7 +26,7 @@ export function useTheme() {
useEffect(() => subscribeToPreferredAppearanceChange(themeChange), []);
// Sync appearance when k/v changes
useEffect(() => setAppearance(appearanceKv.value as Appearance), [appearanceKv.value]);
useEffect(() => setAppearance(appearanceKv.value), [appearanceKv.value]);
return {
appearance: appearanceKv.value,

View File

@@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query';
export function useWorkspaces() {
return (
useQuery(['workspaces'], async () => {
console.log('INVOKING WORKSPACES');
const workspaces = (await invoke('workspaces')) as Workspace[];
return workspaces.map(convertDates);
}).data ?? []

3
src-web/lib/clamp.ts Normal file
View File

@@ -0,0 +1,3 @@
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}

View File

@@ -0,0 +1,62 @@
import { invoke } from '@tauri-apps/api';
import type { KeyValue } from './models';
const DEFAULT_NAMESPACE = 'app';
type KeyValueValue = string | number | boolean;
export async function setKeyValue<T>({
namespace = DEFAULT_NAMESPACE,
key,
value,
}: {
namespace?: string;
key: string | string[];
value: T;
}): Promise<T> {
await invoke('set_key_value', {
namespace,
key: buildKeyValueKey(key),
value: JSON.stringify(value),
});
return value;
}
export async function getKeyValue<T extends KeyValueValue>({
namespace = DEFAULT_NAMESPACE,
key,
fallback,
}: {
namespace?: string;
key: string | string[];
fallback: T;
}) {
const kv = (await invoke('get_key_value', {
namespace,
key: buildKeyValueKey(key),
})) as KeyValue | null;
return extractKeyValueOrFallback(kv, fallback);
}
export function extractKeyValue<T extends KeyValueValue>(kv: KeyValue | null): T | undefined {
if (kv === null) return undefined;
try {
return JSON.parse(kv.value) as T;
} catch (err) {
return undefined;
}
}
export function extractKeyValueOrFallback<T extends KeyValueValue>(
kv: KeyValue | null,
fallback: T,
): T {
const v = extractKeyValue<T>(kv);
if (v === undefined) return fallback;
return v;
}
export function buildKeyValueKey(key: string | string[]): string {
if (typeof key === 'string') return key;
return key.join('::');
}

View File

@@ -3,7 +3,6 @@ export interface BaseModel {
readonly workspaceId: string;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly deletedAt: Date | null;
}
export interface Workspace extends BaseModel {
@@ -46,14 +45,11 @@ export interface HttpResponse extends BaseModel {
readonly headers: HttpHeader[];
}
export function convertDates<T extends Pick<BaseModel, 'createdAt' | 'updatedAt' | 'deletedAt'>>(
m: T,
): T {
export function convertDates<T extends Pick<BaseModel, 'createdAt' | 'updatedAt'>>(m: T): T {
return {
...m,
createdAt: convertDate(m.createdAt),
updatedAt: convertDate(m.updatedAt),
deletedAt: m.deletedAt ? convertDate(m.deletedAt) : null,
};
}

View File

@@ -1,17 +1,11 @@
import { invoke } from '@tauri-apps/api';
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './components/App';
import type { KeyValue } from './lib/models';
import type { Appearance } from './lib/theme/window';
import { getKeyValue } from './lib/keyValueStore';
import { getPreferredAppearance, setAppearance } from './lib/theme/window';
import './main.css';
const appearance: KeyValue = await invoke('get_key_value', {
namespace: 'app',
key: 'appearance',
});
setAppearance((appearance?.value ?? getPreferredAppearance()) as Appearance);
setAppearance(await getKeyValue({ key: 'appearance', fallback: getPreferredAppearance() }));
// root holds our app's root DOM Element:
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(