Flatten migrations, kvs lib, fix tabs

This commit is contained in:
Gregory Schier
2023-03-17 08:36:21 -07:00
parent 58cf0a2015
commit 4181d87792
23 changed files with 406 additions and 392 deletions

View File

@@ -1,17 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak App</title> <title>Yaak App</title>
<!-- <script src="http://localhost:8097"></script>--> <!-- <script src="http://localhost:8097"></script>-->
</head> <style>
body {
background-color: white;
}
<body> @media (prefers-color-scheme: dark) {
<div id="root"></div> body {
<div id="cm-portal" class="cm-portal" style="pointer-events: auto"></div> background-color: black;
<div id="radix-portal" class="cm-portal"></div> }
<script type="module" src="/src-web/main.tsx"></script> }
</body> </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> </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 CREATE TABLE workspaces
( (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 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, deleted_at DATETIME,
name TEXT NOT NULL, name TEXT NOT NULL,
description TEXT NOT NULL description TEXT NOT NULL
); );
CREATE TABLE http_requests CREATE TABLE http_requests
( (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL
workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, PRIMARY KEY,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, model TEXT DEFAULT 'http_request' NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 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, deleted_at DATETIME,
name TEXT NOT NULL, name TEXT NOT NULL,
url TEXT NOT NULL, url TEXT NOT NULL,
method TEXT NOT NULL, method TEXT NOT NULL,
headers TEXT NOT NULL, headers TEXT NOT NULL,
body TEXT body TEXT,
body_type TEXT
); );
CREATE TABLE http_responses CREATE TABLE http_responses
( (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL
request_id TEXT NOT NULL REFERENCES http_requests (id) ON DELETE CASCADE, PRIMARY KEY,
workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, model TEXT DEFAULT 'http_response' NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, request_id TEXT NOT NULL
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 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, deleted_at DATETIME,
elapsed INTEGER NOT NULL, elapsed INTEGER NOT NULL,
status INTEGER NOT NULL, status INTEGER NOT NULL,
status_reason TEXT, status_reason TEXT,
url TEXT NOT NULL, url TEXT NOT NULL,
body TEXT NOT NULL, body TEXT NOT NULL,
headers 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", "db": "SQLite",
"032c7a3861630c499f3e9785687be303aa95c023548e170572ad36eba90ecf84": { "06aaf8f4a17566f1d25da2a60f0baf4b5fc28c3cf0c001a84e25edf9eab3c7e3": {
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -18,24 +18,19 @@
"ordinal": 2, "ordinal": 2,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{
"name": "deleted_at",
"ordinal": 3,
"type_info": "Datetime"
},
{ {
"name": "namespace", "name": "namespace",
"ordinal": 4, "ordinal": 3,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "key", "name": "key",
"ordinal": 5, "ordinal": 4,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "value", "name": "value",
"ordinal": 6, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -43,7 +38,6 @@
false, false,
false, false,
false, false,
true,
false, false,
false, false,
false false
@@ -52,7 +46,7 @@
"Right": 2 "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": { "07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": {
"describe": { "describe": {
@@ -84,7 +78,7 @@
}, },
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n " "query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
}, },
"8423faeb5ce376acffe4c24804e0332c334edf929d9cea0086ee00c108e833fb": { "68b7b17a25d415ce90b33aef16418d668f7ff6275b383bf21c16cafb38cca342": {
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -103,9 +97,9 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "request_id", "name": "created_at",
"ordinal": 3, "ordinal": 3,
"type_info": "Text" "type_info": "Datetime"
}, },
{ {
"name": "updated_at", "name": "updated_at",
@@ -113,48 +107,33 @@
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "deleted_at", "name": "name",
"ordinal": 5, "ordinal": 5,
"type_info": "Datetime" "type_info": "Text"
}, },
{ {
"name": "created_at", "name": "url",
"ordinal": 6, "ordinal": 6,
"type_info": "Datetime" "type_info": "Text"
}, },
{ {
"name": "status", "name": "method",
"ordinal": 7, "ordinal": 7,
"type_info": "Int64"
},
{
"name": "status_reason",
"ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body", "name": "body",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "body_type",
"ordinal": 9, "ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "elapsed", "name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
"ordinal": 10, "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" "type_info": "Text"
} }
], ],
@@ -164,13 +143,10 @@
false, false,
false, false,
false, false,
true, false,
false, false,
false, false,
true, true,
false,
false,
false,
true, true,
false false
], ],
@@ -178,7 +154,7 @@
"Right": 1 "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": { "913f3c3a46b1834c4cd8367aed9d5a9659a1d775d8771e9f5bf9a5aa41197356": {
"describe": { "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 " "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": { "a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": {
"describe": { "describe": {
"columns": [], "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 " "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": { "describe": {
"columns": [ "columns": [
{ {
@@ -267,54 +189,24 @@
"ordinal": 1, "ordinal": 1,
"type_info": "Text" "type_info": "Text"
}, },
{
"name": "workspace_id",
"ordinal": 2,
"type_info": "Text"
},
{ {
"name": "created_at", "name": "created_at",
"ordinal": 3, "ordinal": 2,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "updated_at", "name": "updated_at",
"ordinal": 4, "ordinal": 3,
"type_info": "Datetime"
},
{
"name": "deleted_at",
"ordinal": 5,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "name", "name": "name",
"ordinal": 6, "ordinal": 4,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "url", "name": "description",
"ordinal": 7, "ordinal": 5,
"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,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -324,31 +216,63 @@
false, false,
false, false,
false, false,
true,
false,
false,
false,
true,
true,
false false
], ],
"parameters": { "parameters": {
"Right": 1 "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": { "describe": {
"columns": [], "columns": [
"nullable": [], {
"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": { "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": { "describe": {
"columns": [ "columns": [
{ {
@@ -377,48 +301,43 @@
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "deleted_at", "name": "created_at",
"ordinal": 5, "ordinal": 5,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{
"name": "created_at",
"ordinal": 6,
"type_info": "Datetime"
},
{ {
"name": "status", "name": "status",
"ordinal": 7, "ordinal": 6,
"type_info": "Int64" "type_info": "Int64"
}, },
{ {
"name": "status_reason", "name": "status_reason",
"ordinal": 8, "ordinal": 7,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body", "name": "body",
"ordinal": 9, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "elapsed", "name": "elapsed",
"ordinal": 10, "ordinal": 9,
"type_info": "Int64" "type_info": "Int64"
}, },
{ {
"name": "url", "name": "url",
"ordinal": 11, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "error", "name": "error",
"ordinal": 12, "ordinal": 11,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>", "name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
"ordinal": 13, "ordinal": 12,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -428,7 +347,6 @@
false, false,
false, false,
false, false,
true,
false, false,
false, false,
true, true,
@@ -442,73 +360,19 @@
"Right": 1 "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": { "describe": {
"columns": [], "columns": [],
"nullable": [], "nullable": [],
"parameters": { "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": { "d9ea350bc21ac2f51f6dcb9713328ec330f0e12105da70bf5a6eff9601e32a85": {
"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": {
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -536,39 +400,34 @@
"ordinal": 4, "ordinal": 4,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{
"name": "deleted_at",
"ordinal": 5,
"type_info": "Datetime"
},
{ {
"name": "name", "name": "name",
"ordinal": 6, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "url", "name": "url",
"ordinal": 7, "ordinal": 6,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "method", "name": "method",
"ordinal": 8, "ordinal": 7,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body", "name": "body",
"ordinal": 9, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "body_type", "name": "body_type",
"ordinal": 10, "ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>", "name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
"ordinal": 11, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -578,7 +437,6 @@
false, false,
false, false,
false, false,
true,
false, false,
false, false,
false, false,
@@ -590,7 +448,107 @@
"Right": 1 "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": { "f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
"describe": { "describe": {

View File

@@ -354,7 +354,7 @@ async fn workspaces(
.await .await
.expect("Failed to find workspaces"); .expect("Failed to find workspaces");
if workspaces.is_empty() { 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 .await
.expect("Failed to create workspace"); .expect("Failed to create workspace");
Ok(vec![workspace]) Ok(vec![workspace])

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,6 @@ import { HStack } from '../Stacks';
import './Tabs.css'; import './Tabs.css';
interface Props { interface Props {
defaultValue?: string;
label: string; label: string;
onChangeValue: (value: string) => void; onChangeValue: (value: string) => void;
value: string; value: string;
@@ -31,7 +30,6 @@ interface Props {
export function Tabs({ export function Tabs({
value, value,
onChangeValue, onChangeValue,
defaultValue,
label, label,
children, children,
tabs, tabs,
@@ -40,7 +38,7 @@ export function Tabs({
}: Props) { }: Props) {
return ( return (
<T.Root <T.Root
defaultValue={defaultValue} value={value}
onValueChange={onChangeValue} onValueChange={onChangeValue}
className={classnames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')} 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 responses = useResponses();
const response = responses[responses.length - 1]; const response = responses[responses.length - 1];
if (!response) return false; 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] { export function useResponseViewMode(requestId?: string): [string, () => void] {
const v = useKeyValues({ const v = useKeyValue<string>({
namespace: 'app', namespace: 'app',
key: ['response_view_mode', requestId ?? 'n/a'], key: ['response_view_mode', requestId ?? 'n/a'],
initialValue: 'pretty', initialValue: 'pretty',

View File

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

View File

@@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query';
export function useWorkspaces() { export function useWorkspaces() {
return ( return (
useQuery(['workspaces'], async () => { useQuery(['workspaces'], async () => {
console.log('INVOKING WORKSPACES');
const workspaces = (await invoke('workspaces')) as Workspace[]; const workspaces = (await invoke('workspaces')) as Workspace[];
return workspaces.map(convertDates); return workspaces.map(convertDates);
}).data ?? [] }).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 workspaceId: string;
readonly createdAt: Date; readonly createdAt: Date;
readonly updatedAt: Date; readonly updatedAt: Date;
readonly deletedAt: Date | null;
} }
export interface Workspace extends BaseModel { export interface Workspace extends BaseModel {
@@ -46,14 +45,11 @@ export interface HttpResponse extends BaseModel {
readonly headers: HttpHeader[]; readonly headers: HttpHeader[];
} }
export function convertDates<T extends Pick<BaseModel, 'createdAt' | 'updatedAt' | 'deletedAt'>>( export function convertDates<T extends Pick<BaseModel, 'createdAt' | 'updatedAt'>>(m: T): T {
m: T,
): T {
return { return {
...m, ...m,
createdAt: convertDate(m.createdAt), createdAt: convertDate(m.createdAt),
updatedAt: convertDate(m.updatedAt), 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 { StrictMode } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { App } from './components/App'; import { App } from './components/App';
import type { KeyValue } from './lib/models'; import { getKeyValue } from './lib/keyValueStore';
import type { Appearance } from './lib/theme/window';
import { getPreferredAppearance, setAppearance } from './lib/theme/window'; import { getPreferredAppearance, setAppearance } from './lib/theme/window';
import './main.css'; import './main.css';
const appearance: KeyValue = await invoke('get_key_value', { setAppearance(await getKeyValue({ key: 'appearance', fallback: getPreferredAppearance() }));
namespace: 'app',
key: 'appearance',
});
setAppearance((appearance?.value ?? getPreferredAppearance()) as Appearance);
// root holds our app's root DOM Element: // root holds our app's root DOM Element:
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(