mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 17:17:50 +01:00
Compare commits
9 Commits
v2025.4.0-
...
v2025.4.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcda6f8d32 | ||
|
|
021f2171d6 | ||
|
|
2562cf7c55 | ||
|
|
58873ea606 | ||
|
|
9d9e83d59f | ||
|
|
01d40f5b0d | ||
|
|
bdb1adcce1 | ||
|
|
9f6a3da8d3 | ||
|
|
158487e3a6 |
@@ -47,8 +47,7 @@ npm start
|
||||
New migrations can be created from the `src-tauri/` directory:
|
||||
|
||||
```shell
|
||||
cd src-tauri
|
||||
sqlx migrate add migration-name
|
||||
npm run migration
|
||||
```
|
||||
|
||||
Run the app to apply the migrations.
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"start": "npm run app-dev",
|
||||
"app-build": "tauri build",
|
||||
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
|
||||
"migration": "node scripts/create-migration.cjs",
|
||||
"build": "npm run --workspaces --if-present build",
|
||||
"build-plugins": "npm run --workspaces --if-present build",
|
||||
"bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
HttpRequestAction,
|
||||
InternalEvent,
|
||||
InternalEventPayload,
|
||||
JsonPrimitive,
|
||||
ListCookieNamesResponse,
|
||||
PluginWindowContext,
|
||||
PromptTextResponse,
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
TemplateRenderResponse,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import { JsonValue } from '@yaakapp/api/lib/bindings/serde_json/JsonValue';
|
||||
import console from 'node:console';
|
||||
import { readFileSync, type Stats, statSync, watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@@ -568,7 +568,7 @@ function genId(len = 5): string {
|
||||
/** Recursively apply form input defaults to a set of values */
|
||||
function applyFormInputDefaults(
|
||||
inputs: TemplateFunctionArg[],
|
||||
values: { [p: string]: JsonPrimitive | undefined },
|
||||
values: { [p: string]: JsonValue | undefined },
|
||||
) {
|
||||
for (const input of inputs) {
|
||||
if ('inputs' in input) {
|
||||
|
||||
@@ -50,8 +50,11 @@ export async function getAccessToken(
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
console.log('[oauth2] Got access token response', resp.status);
|
||||
|
||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
|
||||
@@ -63,6 +63,7 @@ export async function getOrRefreshAccessToken(ctx: Context, contextId: string, {
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
if (resp.status === 401) {
|
||||
@@ -75,6 +76,8 @@ export async function getOrRefreshAccessToken(ctx: Context, contextId: string, {
|
||||
|
||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||
|
||||
console.log('[oauth2] Got refresh token response', resp.status);
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error('Failed to refresh access token with status=' + resp.status + ' and body=' + body);
|
||||
}
|
||||
@@ -95,5 +98,6 @@ export async function getOrRefreshAccessToken(ctx: Context, contextId: string, {
|
||||
// Assign a new one or keep the old one,
|
||||
refresh_token: response.refresh_token ?? token.response.refresh_token,
|
||||
};
|
||||
|
||||
return storeToken(ctx, contextId, newResponse);
|
||||
}
|
||||
|
||||
46
scripts/create-migration.cjs
Normal file
46
scripts/create-migration.cjs
Normal file
@@ -0,0 +1,46 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const readline = require('readline');
|
||||
const slugify = require('slugify');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
function generateTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
async function createMigration() {
|
||||
try {
|
||||
const migrationName = await new Promise((resolve) => {
|
||||
rl.question('Enter migration name: ', resolve);
|
||||
});
|
||||
|
||||
const timestamp = generateTimestamp();
|
||||
const fileName = `${timestamp}_${slugify(String(migrationName), { lower: true })}.sql`;
|
||||
const migrationsDir = path.join(__dirname, '../src-tauri/migrations');
|
||||
const filePath = path.join(migrationsDir, fileName);
|
||||
|
||||
if (!fs.existsSync(migrationsDir)) {
|
||||
fs.mkdirSync(migrationsDir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, '-- Add migration SQL here\n');
|
||||
console.log(`Created migration file: ${fileName}`);
|
||||
} catch (error) {
|
||||
console.error('Error creating migration:', error);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
createMigration().catch(console.error);
|
||||
508
src-tauri/Cargo.lock
generated
508
src-tauri/Cargo.lock
generated
@@ -91,12 +91,6 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -393,15 +387,6 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -494,12 +479,6 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -973,12 +952,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -1096,21 +1069,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
@@ -1129,15 +1087,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@@ -1262,17 +1211,6 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@@ -1336,7 +1274,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
@@ -1411,12 +1348,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
@@ -1464,9 +1395,6 @@ name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
@@ -1566,17 +1494,6 @@ version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.3.1"
|
||||
@@ -1682,17 +1599,6 @@ dependencies = [
|
||||
"miniz_oxide 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1782,7 +1688,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1802,17 +1707,6 @@ dependencies = [
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-intrusive"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@@ -2248,7 +2142,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2317,15 +2210,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.26.0"
|
||||
@@ -2834,9 +2718,6 @@ name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator"
|
||||
@@ -2911,12 +2792,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
@@ -2925,7 +2800,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"libc",
|
||||
"redox_syscall 0.5.3",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3031,16 +2906,6 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
@@ -3307,23 +3172,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
@@ -3377,7 +3225,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3879,17 +3726,11 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.3",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.2.1"
|
||||
@@ -3902,15 +3743,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@@ -4104,27 +3936,6 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
@@ -4586,15 +4397,6 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.3"
|
||||
@@ -4786,26 +4588,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.32.1"
|
||||
@@ -5331,9 +5113,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@@ -5365,16 +5147,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
@@ -5407,9 +5179,6 @@ name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
@@ -5438,7 +5207,7 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.5.3",
|
||||
"redox_syscall",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -5470,229 +5239,6 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"event-listener",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-intrusive",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.14.5",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap 2.3.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sqlx-core",
|
||||
"sqlx-macros-core",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck 0.5.0",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-sqlite",
|
||||
"syn 2.0.87",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
"digest",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"itoa 1.0.11",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 1.0.63",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.0",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
"etcetera",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"home",
|
||||
"itoa 1.0.11",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 1.0.63",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"flume",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-intrusive",
|
||||
"futures-util",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@@ -5731,17 +5277,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -6731,7 +6266,6 @@ version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -6936,24 +6470,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
@@ -7113,12 +6635,6 @@ dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
@@ -7403,16 +6919,6 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
|
||||
dependencies = [
|
||||
"redox_syscall 0.4.1",
|
||||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -8199,6 +7705,7 @@ name = "yaak-models"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"log",
|
||||
"nanoid",
|
||||
"r2d2",
|
||||
@@ -8208,9 +7715,10 @@ dependencies = [
|
||||
"sea-query-rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"sha2",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-dialog",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
|
||||
@@ -54,7 +54,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["raw_value"] }
|
||||
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.2.2"
|
||||
tauri-plugin-dialog = "2.2.0"
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-log = { version = "2.3.1", features = ["colored"] }
|
||||
tauri-plugin-opener = "2.2.6"
|
||||
@@ -87,6 +87,7 @@ serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
tauri = "2.4.1"
|
||||
tauri-plugin = "2.1.1"
|
||||
tauri-plugin-dialog = "2.2.0"
|
||||
tauri-plugin-shell = "2.2.1"
|
||||
tokio = "1.44.2"
|
||||
thiserror = "2.0.12"
|
||||
|
||||
@@ -164,8 +164,6 @@ DROP TABLE websocket_requests;
|
||||
ALTER TABLE websocket_requests_dg_tmp
|
||||
RENAME TO websocket_requests;
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
---------------------------
|
||||
-- Remove environment FK --
|
||||
---------------------------
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN colored_methods BOOLEAN DEFAULT FALSE;
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::error::Result;
|
||||
use KeyAndValueRef::{Ascii, Binary};
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use yaak_grpc::{KeyAndValueRef, MetadataMap};
|
||||
use yaak_models::models::GrpcRequest;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_plugins::events::{CallHttpAuthenticationRequest, HttpHeader};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use KeyAndValueRef::{Ascii, Binary};
|
||||
|
||||
pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String> {
|
||||
let mut entries = BTreeMap::new();
|
||||
@@ -23,10 +23,10 @@ pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String>
|
||||
pub(crate) fn resolve_grpc_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: &GrpcRequest,
|
||||
) -> Result<GrpcRequest> {
|
||||
) -> Result<(GrpcRequest, String)> {
|
||||
let mut new_request = request.clone();
|
||||
|
||||
let (authentication_type, authentication) =
|
||||
let (authentication_type, authentication, authentication_context_id) =
|
||||
window.db().resolve_auth_for_grpc_request(request)?;
|
||||
new_request.authentication_type = authentication_type;
|
||||
new_request.authentication = authentication;
|
||||
@@ -34,12 +34,13 @@ pub(crate) fn resolve_grpc_request<R: Runtime>(
|
||||
let metadata = window.db().resolve_metadata_for_grpc_request(request)?;
|
||||
new_request.metadata = metadata;
|
||||
|
||||
Ok(new_request)
|
||||
Ok((new_request, authentication_context_id))
|
||||
}
|
||||
|
||||
pub(crate) async fn build_metadata<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: &GrpcRequest,
|
||||
authentication_context_id: &str,
|
||||
) -> Result<BTreeMap<String, String>> {
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
let mut metadata = BTreeMap::new();
|
||||
@@ -67,7 +68,7 @@ pub(crate) async fn build_metadata<R: Runtime>(
|
||||
Some(authentication_type) => {
|
||||
let auth = request.authentication.clone();
|
||||
let plugin_req = CallHttpAuthenticationRequest {
|
||||
context_id: format!("{:x}", md5::compute(request.id.clone())),
|
||||
context_id: format!("{:x}", md5::compute(authentication_context_id)),
|
||||
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url.clone(),
|
||||
|
||||
@@ -62,7 +62,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
);
|
||||
let update_source = UpdateSource::from_window(window);
|
||||
|
||||
let resolved_request = match resolve_http_request(window, unrendered_request) {
|
||||
let (resolved_request, auth_context_id) = match resolve_http_request(window, unrendered_request)
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Ok(response_err(
|
||||
@@ -429,7 +430,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
Some(authentication_type) => {
|
||||
let req = CallHttpAuthenticationRequest {
|
||||
context_id: format!("{:x}", md5::compute(request.id)),
|
||||
context_id: format!("{:x}", md5::compute(auth_context_id)),
|
||||
values: serde_json::from_value(
|
||||
serde_json::to_value(&request.authentication).unwrap(),
|
||||
)
|
||||
@@ -667,10 +668,10 @@ pub async fn send_http_request<R: Runtime>(
|
||||
fn resolve_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: &HttpRequest,
|
||||
) -> Result<HttpRequest> {
|
||||
) -> Result<(HttpRequest, String)> {
|
||||
let mut new_request = request.clone();
|
||||
|
||||
let (authentication_type, authentication) =
|
||||
let (authentication_type, authentication, authentication_context_id) =
|
||||
window.db().resolve_auth_for_http_request(request)?;
|
||||
new_request.authentication_type = authentication_type;
|
||||
new_request.authentication = authentication;
|
||||
@@ -678,7 +679,7 @@ fn resolve_http_request<R: Runtime>(
|
||||
let headers = window.db().resolve_headers_for_http_request(request)?;
|
||||
new_request.headers = headers;
|
||||
|
||||
Ok(new_request)
|
||||
Ok((new_request, authentication_context_id))
|
||||
}
|
||||
|
||||
fn ensure_proto(url_str: &str) -> String {
|
||||
|
||||
@@ -151,7 +151,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
||||
None => None,
|
||||
};
|
||||
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
|
||||
let resolved_request = resolve_grpc_request(&window, &unrendered_request)?;
|
||||
let (resolved_request, auth_context_id) = resolve_grpc_request(&window, &unrendered_request)?;
|
||||
|
||||
let base_environment =
|
||||
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
|
||||
@@ -170,7 +170,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
||||
.await?;
|
||||
|
||||
let uri = safe_uri(&req.url);
|
||||
let metadata = build_metadata(&window, &req).await?;
|
||||
let metadata = build_metadata(&window, &req, &auth_context_id).await?;
|
||||
|
||||
Ok(grpc_handle
|
||||
.lock()
|
||||
@@ -200,7 +200,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
None => None,
|
||||
};
|
||||
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
|
||||
let resolved_request = resolve_grpc_request(&window, &unrendered_request)?;
|
||||
let (resolved_request, auth_context_id) = resolve_grpc_request(&window, &unrendered_request)?;
|
||||
let base_environment =
|
||||
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
|
||||
let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?;
|
||||
@@ -217,7 +217,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let metadata = build_metadata(&window, &request).await?;
|
||||
let metadata = build_metadata(&window, &request, &auth_context_id).await?;
|
||||
|
||||
let conn = app_handle.db().upsert_grpc_connection(
|
||||
&GrpcConnection {
|
||||
@@ -1232,7 +1232,7 @@ pub fn run() {
|
||||
.level_for("hyper_util", log::LevelFilter::Info)
|
||||
.level_for("hyper_rustls", log::LevelFilter::Info)
|
||||
.level_for("reqwest", log::LevelFilter::Info)
|
||||
.level_for("sqlx", log::LevelFilter::Warn)
|
||||
.level_for("sqlx", log::LevelFilter::Debug)
|
||||
.level_for("tao", log::LevelFilter::Info)
|
||||
.level_for("tokio_util", log::LevelFilter::Info)
|
||||
.level_for("tonic", log::LevelFilter::Info)
|
||||
|
||||
@@ -16,11 +16,13 @@ sea-query = { version = "0.32.1", features = ["with-chrono", "attr"] }
|
||||
sea-query-rusqlite = { version = "0.7.0", features = ["with-chrono"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sqlx = { version = "0.8.0", default-features = false, features = ["migrate", "sqlite", "runtime-tokio-rustls"] }
|
||||
tauri = { workspace = true }
|
||||
sha2 = "0.10.9"
|
||||
tauri = { workspace = true}
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
thiserror = "2.0.11"
|
||||
tokio = { workspace = true }
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
hex = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", disabled: boolean, http: string,
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, };
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, coloredMethods: boolean, };
|
||||
|
||||
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./gen_models";
|
||||
import type { Folder } from "./gen_models";
|
||||
import type { GrpcRequest } from "./gen_models";
|
||||
import type { HttpRequest } from "./gen_models";
|
||||
import type { WebsocketRequest } from "./gen_models";
|
||||
import type { Workspace } from "./gen_models";
|
||||
import type { Environment } from "./gen_models.js";
|
||||
import type { Folder } from "./gen_models.js";
|
||||
import type { GrpcRequest } from "./gen_models.js";
|
||||
import type { HttpRequest } from "./gen_models.js";
|
||||
import type { WebsocketRequest } from "./gen_models.js";
|
||||
import type { Workspace } from "./gen_models.js";
|
||||
|
||||
export type BatchUpsertResult = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
||||
|
||||
@@ -20,6 +20,9 @@ pub enum Error {
|
||||
|
||||
#[error("Model error: {0}")]
|
||||
GenericError(String),
|
||||
|
||||
#[error("DB Migration Failed: {0}")]
|
||||
MigrationError(String),
|
||||
|
||||
#[error("No base environment for {0}")]
|
||||
MissingBaseEnvironment(String),
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
use crate::commands::*;
|
||||
use crate::migrate::migrate_db;
|
||||
use crate::query_manager::QueryManager;
|
||||
use crate::util::ModelChangeEvent;
|
||||
use log::info;
|
||||
use log::error;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use sqlx::SqlitePool;
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tauri::async_runtime::Mutex;
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, generate_handler};
|
||||
use tauri::{Emitter, Manager, Runtime, generate_handler};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod commands;
|
||||
@@ -22,6 +18,7 @@ mod commands;
|
||||
mod connection_or_tx;
|
||||
pub mod db_context;
|
||||
pub mod error;
|
||||
mod migrate;
|
||||
pub mod models;
|
||||
pub mod queries;
|
||||
pub mod query_manager;
|
||||
@@ -55,13 +52,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
|
||||
let db_file_path = app_path.join("db.sqlite");
|
||||
|
||||
{
|
||||
let db_file_path = db_file_path.clone();
|
||||
tauri::async_runtime::block_on(async move {
|
||||
must_migrate_db(app_handle.app_handle(), &db_file_path).await;
|
||||
});
|
||||
};
|
||||
|
||||
let manager = SqliteConnectionManager::file(db_file_path);
|
||||
let pool = Pool::builder()
|
||||
.max_size(100) // Up from 10 (just in case)
|
||||
@@ -69,6 +59,16 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.build(manager)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = migrate_db(app_handle.app_handle(), &pool) {
|
||||
error!("Failed to run database migration {e:?}");
|
||||
app_handle
|
||||
.dialog()
|
||||
.message(e.to_string())
|
||||
.kind(MessageDialogKind::Error)
|
||||
.blocking_show();
|
||||
return Err(Box::from(e.to_string()));
|
||||
};
|
||||
|
||||
app_handle.manage(SqliteConnection::new(pool.clone()));
|
||||
|
||||
{
|
||||
@@ -90,21 +90,3 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
async fn must_migrate_db<R: Runtime>(app_handle: &AppHandle<R>, sqlite_file_path: &PathBuf) {
|
||||
info!("Connecting to database at {sqlite_file_path:?}");
|
||||
let sqlite_file_path = sqlite_file_path.to_str().unwrap().to_string();
|
||||
let opts = SqliteConnectOptions::from_str(&sqlite_file_path).unwrap().create_if_missing(true);
|
||||
let pool = SqlitePool::connect_with(opts).await.expect("Failed to connect to database");
|
||||
let p = app_handle
|
||||
.path()
|
||||
.resolve("migrations", BaseDirectory::Resource)
|
||||
.expect("failed to resolve resource");
|
||||
|
||||
info!("Running database migrations from: {}", p.to_string_lossy());
|
||||
let mut m = Migrator::new(p).await.expect("Failed to load migrations");
|
||||
m.set_ignore_missing(true); // So we can roll back versions and not crash
|
||||
m.run(&pool).await.expect("Failed to run migrations");
|
||||
|
||||
info!("Database migrations complete");
|
||||
}
|
||||
|
||||
129
src-tauri/yaak-models/src/migrate.rs
Normal file
129
src-tauri/yaak-models/src/migrate.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::error::Error::MigrationError;
|
||||
use crate::error::Result;
|
||||
use log::info;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::{OptionalExtension, TransactionBehavior, params};
|
||||
use sha2::{Digest, Sha384};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::result::Result as StdResult;
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
|
||||
pub(crate) fn migrate_db<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
pool: &Pool<SqliteConnectionManager>,
|
||||
) -> Result<()> {
|
||||
let migrations_dir = app_handle
|
||||
.path()
|
||||
.resolve("migrations", BaseDirectory::Resource)
|
||||
.expect("failed to resolve resource");
|
||||
|
||||
info!("Running database migrations from: {:?}", migrations_dir);
|
||||
|
||||
// Ensure the table exists
|
||||
// NOTE: Yaak used to use sqlx for migrations, so we need to mirror that table structure. We
|
||||
// are writing checksum but not verifying because we want to be able to change migrations after
|
||||
// a release in case something breaks.
|
||||
pool.get()?.execute(
|
||||
"CREATE TABLE IF NOT EXISTS _sqlx_migrations (
|
||||
version BIGINT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
installed_on TIMESTAMP default CURRENT_TIMESTAMP NOT NULL,
|
||||
success BOOLEAN NOT NULL,
|
||||
checksum BLOB NOT NULL,
|
||||
execution_time BIGINT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
// Read and sort all .sql files
|
||||
let mut entries = fs::read_dir(migrations_dir)
|
||||
.expect("Failed to find migrations directory")
|
||||
.filter_map(StdResult::ok)
|
||||
.filter(|e| e.path().extension().map(|ext| ext == "sql").unwrap_or(false))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Ensure they're in the correct order
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
|
||||
// Run each migration in a transaction
|
||||
for entry in entries {
|
||||
let mut conn = pool.get()?;
|
||||
let mut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
match run_migration(entry.path().as_path(), &mut tx) {
|
||||
Ok(_) => tx.commit()?,
|
||||
Err(e) => {
|
||||
let msg = format!(
|
||||
"{} failed with {}",
|
||||
entry.path().file_name().unwrap().to_str().unwrap(),
|
||||
e.to_string()
|
||||
);
|
||||
tx.rollback()?;
|
||||
return Err(MigrationError(msg));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
info!("Finished running migrations");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_migration(migration_path: &Path, tx: &mut rusqlite::Transaction) -> Result<bool> {
|
||||
let start = std::time::Instant::now();
|
||||
let (version, description) =
|
||||
split_migration_filename(migration_path.file_name().unwrap().to_str().unwrap())
|
||||
.expect("Failed to parse migration filename");
|
||||
|
||||
// Skip if already applied
|
||||
let row: Option<i64> = tx
|
||||
.query_row("SELECT 1 FROM _sqlx_migrations WHERE version = ?", [version.clone()], |r| {
|
||||
r.get(0)
|
||||
})
|
||||
.optional()?;
|
||||
|
||||
if row.is_some() {
|
||||
// Migration was already run
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let sql = fs::read_to_string(migration_path).expect("Failed to read migration file");
|
||||
info!("Applying migration {description}");
|
||||
|
||||
// Split on `;`? → optional depending on how your SQL is structured
|
||||
tx.execute_batch(&sql)?;
|
||||
|
||||
let execution_time = start.elapsed().as_nanos() as i64;
|
||||
let checksum = sha384_hex_prefixed(sql.as_bytes());
|
||||
|
||||
// NOTE: The success column is never used. It's just there for sqlx compatibility.
|
||||
tx.execute(
|
||||
"INSERT INTO _sqlx_migrations (version, description, execution_time, checksum, success) VALUES (?, ?, ?, ?, ?)",
|
||||
params![version, description, execution_time, checksum, true],
|
||||
)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn split_migration_filename(filename: &str) -> Option<(String, String)> {
|
||||
// Remove the .sql extension
|
||||
let trimmed = filename.strip_suffix(".sql")?;
|
||||
|
||||
// Split on the first underscore
|
||||
let mut parts = trimmed.splitn(2, '_');
|
||||
let version = parts.next()?.to_string();
|
||||
let description = parts.next()?.to_string();
|
||||
|
||||
Some((version, description))
|
||||
}
|
||||
|
||||
fn sha384_hex_prefixed(input: &[u8]) -> String {
|
||||
let mut hasher = Sha384::new();
|
||||
hasher.update(input);
|
||||
let result = hasher.finalize();
|
||||
|
||||
// Format as 0x... with uppercase hex
|
||||
format!("0x{}", hex::encode_upper(result))
|
||||
}
|
||||
@@ -114,6 +114,7 @@ pub struct Settings {
|
||||
pub theme_light: String,
|
||||
pub update_channel: String,
|
||||
pub editor_keymap: EditorKeymap,
|
||||
pub colored_methods: bool,
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Settings {
|
||||
@@ -160,6 +161,7 @@ impl UpsertModelInfo for Settings {
|
||||
(ThemeDark, self.theme_dark.as_str().into()),
|
||||
(ThemeLight, self.theme_light.as_str().into()),
|
||||
(UpdateChannel, self.update_channel.into()),
|
||||
(ColoredMethods, self.colored_methods.into()),
|
||||
(Proxy, proxy.into()),
|
||||
])
|
||||
}
|
||||
@@ -179,6 +181,7 @@ impl UpsertModelInfo for Settings {
|
||||
SettingsIden::ThemeDark,
|
||||
SettingsIden::ThemeLight,
|
||||
SettingsIden::UpdateChannel,
|
||||
SettingsIden::ColoredMethods,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -205,6 +208,7 @@ impl UpsertModelInfo for Settings {
|
||||
theme_light: row.get("theme_light")?,
|
||||
hide_window_controls: row.get("hide_window_controls")?,
|
||||
update_channel: row.get("update_channel")?,
|
||||
colored_methods: row.get("colored_methods")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,15 +115,15 @@ impl<'a> DbContext<'a> {
|
||||
|
||||
pub fn resolve_auth_for_folder(
|
||||
&self,
|
||||
folder: Folder,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
if let Some(at) = folder.authentication_type {
|
||||
return Ok((Some(at), folder.authentication));
|
||||
folder: &Folder,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>, String)> {
|
||||
if let Some(at) = folder.authentication_type.clone() {
|
||||
return Ok((Some(at), folder.authentication.clone(), folder.id.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = folder.folder_id {
|
||||
if let Some(folder_id) = folder.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
return self.resolve_auth_for_folder(&folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&folder.workspace_id)?;
|
||||
|
||||
@@ -54,14 +54,14 @@ impl<'a> DbContext<'a> {
|
||||
pub fn resolve_auth_for_grpc_request(
|
||||
&self,
|
||||
grpc_request: &GrpcRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>, String)> {
|
||||
if let Some(at) = grpc_request.authentication_type.clone() {
|
||||
return Ok((Some(at), grpc_request.authentication.clone()));
|
||||
return Ok((Some(at), grpc_request.authentication.clone(), grpc_request.id.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = grpc_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
return self.resolve_auth_for_folder(&folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&grpc_request.workspace_id)?;
|
||||
|
||||
@@ -54,14 +54,14 @@ impl<'a> DbContext<'a> {
|
||||
pub fn resolve_auth_for_http_request(
|
||||
&self,
|
||||
http_request: &HttpRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>, String)> {
|
||||
if let Some(at) = http_request.authentication_type.clone() {
|
||||
return Ok((Some(at), http_request.authentication.clone()));
|
||||
return Ok((Some(at), http_request.authentication.clone(), http_request.id.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = http_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
return self.resolve_auth_for_folder(&folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&http_request.workspace_id)?;
|
||||
|
||||
@@ -29,6 +29,7 @@ impl<'a> DbContext<'a> {
|
||||
theme_dark: "yaak-dark".to_string(),
|
||||
theme_light: "yaak-light".to_string(),
|
||||
update_channel: "stable".to_string(),
|
||||
colored_methods: false,
|
||||
};
|
||||
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ impl<'a> DbContext<'a> {
|
||||
pub fn resolve_auth_for_websocket_request(
|
||||
&self,
|
||||
websocket_request: &WebsocketRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>, String)> {
|
||||
if let Some(at) = websocket_request.authentication_type.clone() {
|
||||
return Ok((Some(at), websocket_request.authentication.clone()));
|
||||
return Ok((Some(at), websocket_request.authentication.clone(), websocket_request.id.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = websocket_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
return self.resolve_auth_for_folder(&folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
||||
|
||||
@@ -71,8 +71,12 @@ impl<'a> DbContext<'a> {
|
||||
pub fn resolve_auth_for_workspace(
|
||||
&self,
|
||||
workspace: &Workspace,
|
||||
) -> (Option<String>, BTreeMap<String, Value>) {
|
||||
(workspace.authentication_type.clone(), workspace.authentication.clone())
|
||||
) -> (Option<String>, BTreeMap<String, Value>, String) {
|
||||
(
|
||||
workspace.authentication_type.clone(),
|
||||
workspace.authentication.clone(),
|
||||
workspace.id.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resolve_headers_for_workspace(&self, workspace: &Workspace) -> Vec<HttpRequestHeader> {
|
||||
|
||||
@@ -120,7 +120,8 @@ pub(crate) async fn send<R: Runtime>(
|
||||
};
|
||||
let base_environment =
|
||||
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
|
||||
let resolved_request = resolve_websocket_request(&window, &unrendered_request)?;
|
||||
let (resolved_request, _auth_context_id) =
|
||||
resolve_websocket_request(&window, &unrendered_request)?;
|
||||
let request = render_websocket_request(
|
||||
&resolved_request,
|
||||
&base_environment,
|
||||
@@ -197,7 +198,8 @@ pub(crate) async fn connect<R: Runtime>(
|
||||
let base_environment =
|
||||
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
|
||||
let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?;
|
||||
let resolved_request = resolve_websocket_request(&window, &unrendered_request)?;
|
||||
let (resolved_request, auth_context_id) =
|
||||
resolve_websocket_request(&window, &unrendered_request)?;
|
||||
let request = render_websocket_request(
|
||||
&resolved_request,
|
||||
&base_environment,
|
||||
@@ -237,7 +239,7 @@ pub(crate) async fn connect<R: Runtime>(
|
||||
Some(authentication_type) => {
|
||||
let auth = request.authentication.clone();
|
||||
let plugin_req = CallHttpAuthenticationRequest {
|
||||
context_id: format!("{:x}", md5::compute(request_id.to_string())),
|
||||
context_id: format!("{:x}", md5::compute(auth_context_id)),
|
||||
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url.clone(),
|
||||
@@ -299,13 +301,15 @@ pub(crate) async fn connect<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
let response = match ws_manager.connect(
|
||||
&connection.id,
|
||||
url.as_str(),
|
||||
headers,
|
||||
receive_tx,
|
||||
workspace.setting_validate_certificates,
|
||||
).await
|
||||
let response = match ws_manager
|
||||
.connect(
|
||||
&connection.id,
|
||||
url.as_str(),
|
||||
headers,
|
||||
receive_tx,
|
||||
workspace.setting_validate_certificates,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
|
||||
@@ -6,10 +6,10 @@ use yaak_models::query_manager::QueryManagerExt;
|
||||
pub(crate) fn resolve_websocket_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: &WebsocketRequest,
|
||||
) -> Result<WebsocketRequest> {
|
||||
) -> Result<(WebsocketRequest, String)> {
|
||||
let mut new_request = request.clone();
|
||||
|
||||
let (authentication_type, authentication) =
|
||||
let (authentication_type, authentication, authentication_context_id) =
|
||||
window.db().resolve_auth_for_websocket_request(request)?;
|
||||
new_request.authentication_type = authentication_type;
|
||||
new_request.authentication = authentication;
|
||||
@@ -17,5 +17,5 @@ pub(crate) fn resolve_websocket_request<R: Runtime>(
|
||||
let headers = window.db().resolve_headers_for_websocket_request(request)?;
|
||||
new_request.headers = headers;
|
||||
|
||||
Ok(new_request)
|
||||
Ok((new_request, authentication_context_id))
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
const r = [...workspaces].sort((a, b) => {
|
||||
return [...workspaces].sort((a, b) => {
|
||||
const aRecentIndex = recentWorkspaces?.indexOf(a.id);
|
||||
const bRecentIndex = recentWorkspaces?.indexOf(b.id);
|
||||
|
||||
@@ -250,7 +250,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
});
|
||||
return r;
|
||||
}, [recentWorkspaces, workspaces]);
|
||||
|
||||
const groups = useMemo<CommandPaletteGroup[]>(() => {
|
||||
@@ -272,7 +271,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
searchText: resolvedModelNameWithFolders(r),
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<HttpMethodTag className="text-text-subtlest" request={r} />
|
||||
<HttpMethodTag short className="text-xs" request={r} />
|
||||
<div className="truncate">{resolvedModelNameWithFolders(r)}</div>
|
||||
</HStack>
|
||||
),
|
||||
|
||||
@@ -176,7 +176,6 @@ export function GrpcRequestPane({
|
||||
<UrlBar
|
||||
key={forceUpdateKey}
|
||||
url={activeRequest.url ?? ''}
|
||||
method={null}
|
||||
submitIcon={null}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
placeholder="localhost:50051"
|
||||
|
||||
@@ -41,8 +41,8 @@ import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { FormMultipartEditor } from './FormMultipartEditor';
|
||||
import { FormUrlencodedEditor } from './FormUrlencodedEditor';
|
||||
@@ -50,6 +50,7 @@ import { GraphQLEditor } from './GraphQLEditor';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
||||
import { UrlBar } from './UrlBar';
|
||||
import { UrlParametersEditor } from './UrlParameterEditor';
|
||||
|
||||
@@ -138,10 +139,9 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ||
|
||||
activeRequest.bodyType === BODY_TYPE_FORM_MULTIPART
|
||||
) {
|
||||
const n = Array.isArray(activeRequest.body?.form)
|
||||
numParams = Array.isArray(activeRequest.body?.form)
|
||||
? activeRequest.body.form.filter((p) => p.name).length
|
||||
: 0;
|
||||
numParams = n;
|
||||
}
|
||||
|
||||
const tabs = useMemo<TabItem[]>(
|
||||
@@ -314,11 +314,6 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
[activeRequest.id, sendRequest],
|
||||
);
|
||||
|
||||
const handleMethodChange = useCallback(
|
||||
(method: string) => patchModel(activeRequest, { method }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleUrlChange = useCallback(
|
||||
(url: string) => patchModel(activeRequest, { url }),
|
||||
[activeRequest],
|
||||
@@ -335,14 +330,17 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
stateKey={`url.${activeRequest.id}`}
|
||||
key={forceUpdateKey + urlKey}
|
||||
url={activeRequest.url}
|
||||
method={activeRequest.method}
|
||||
placeholder="https://example.com"
|
||||
onPasteOverwrite={handlePaste}
|
||||
autocomplete={autocomplete}
|
||||
onSend={handleSend}
|
||||
onCancel={cancelResponse}
|
||||
onMethodChange={handleMethodChange}
|
||||
onUrlChange={handleUrlChange}
|
||||
leftSlot={
|
||||
<div className="py-0.5">
|
||||
<RequestMethodDropdown request={activeRequest} className="ml-0.5 !h-full" />
|
||||
</div>
|
||||
}
|
||||
forceUpdateKey={updateKey}
|
||||
isLoading={activeResponse != null && activeResponse.state !== 'closed'}
|
||||
/>
|
||||
|
||||
@@ -67,7 +67,7 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
||||
...responses.map((r: HttpResponse) => ({
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<HttpStatusTag className="text-sm" response={r} />
|
||||
<HttpStatusTag short className="text-xs" response={r} />
|
||||
<span className="text-text-subtle">→</span>{' '}
|
||||
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
|
||||
</HStack>
|
||||
|
||||
@@ -58,7 +58,7 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
|
||||
recentRequestItems.push({
|
||||
label: resolvedModelName(request),
|
||||
leftSlot: <HttpMethodTag request={request} />,
|
||||
leftSlot: <HttpMethodTag short className="text-xs" request={request} />,
|
||||
onSelect: async () => {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { HttpMethodTag } from './core/HttpMethodTag';
|
||||
import { Icon } from './core/Icon';
|
||||
import type { RadioDropdownItem } from './core/RadioDropdown';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
|
||||
type Props = {
|
||||
method: string;
|
||||
request: HttpRequest;
|
||||
className?: string;
|
||||
onChange: (method: string) => void;
|
||||
};
|
||||
|
||||
const radioItems: RadioDropdownItem<string>[] = [
|
||||
@@ -28,10 +30,16 @@ const radioItems: RadioDropdownItem<string>[] = [
|
||||
}));
|
||||
|
||||
export const RequestMethodDropdown = memo(function RequestMethodDropdown({
|
||||
method,
|
||||
onChange,
|
||||
request,
|
||||
className,
|
||||
}: Props) {
|
||||
const handleChange = useCallback(
|
||||
async (method: string) => {
|
||||
await patchModel(request, { method });
|
||||
},
|
||||
[request],
|
||||
);
|
||||
|
||||
const itemsAfter = useMemo<DropdownItem[]>(
|
||||
() => [
|
||||
{
|
||||
@@ -49,17 +57,22 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
|
||||
placeholder: 'CUSTOM',
|
||||
});
|
||||
if (newMethod == null) return;
|
||||
onChange(newMethod);
|
||||
await handleChange(newMethod);
|
||||
},
|
||||
},
|
||||
],
|
||||
[onChange],
|
||||
[handleChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<RadioDropdown value={method} items={radioItems} itemsAfter={itemsAfter} onChange={onChange}>
|
||||
<RadioDropdown
|
||||
value={request.method}
|
||||
items={radioItems}
|
||||
itemsAfter={itemsAfter}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<Button size="xs" className={classNames(className, 'text-text-subtle hover:text')}>
|
||||
{method.toUpperCase()}
|
||||
<HttpMethodTag request={request} />
|
||||
</Button>
|
||||
</RadioDropdown>
|
||||
);
|
||||
|
||||
@@ -122,6 +122,11 @@ export function SettingsAppearance() {
|
||||
title="Wrap Editor Lines"
|
||||
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.coloredMethods}
|
||||
title="Colorize Request Methods"
|
||||
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
|
||||
/>
|
||||
|
||||
{type() !== 'macos' && (
|
||||
<Checkbox
|
||||
|
||||
@@ -9,11 +9,9 @@ import { IconButton } from './core/IconButton';
|
||||
import type { InputProps } from './core/Input';
|
||||
import { Input } from './core/Input';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
||||
|
||||
type Props = Pick<HttpRequest, 'url'> & {
|
||||
className?: string;
|
||||
method: HttpRequest['method'] | null;
|
||||
placeholder: string;
|
||||
onSend: () => void;
|
||||
onUrlChange: (url: string) => void;
|
||||
@@ -21,10 +19,10 @@ type Props = Pick<HttpRequest, 'url'> & {
|
||||
onPasteOverwrite?: InputProps['onPasteOverwrite'];
|
||||
onCancel: () => void;
|
||||
submitIcon?: IconProps['icon'] | null;
|
||||
onMethodChange?: (method: string) => void;
|
||||
isLoading: boolean;
|
||||
forceUpdateKey: string;
|
||||
rightSlot?: ReactNode;
|
||||
leftSlot?: ReactNode;
|
||||
autocomplete?: InputProps['autocomplete'];
|
||||
stateKey: InputProps['stateKey'];
|
||||
};
|
||||
@@ -33,16 +31,15 @@ export const UrlBar = memo(function UrlBar({
|
||||
forceUpdateKey,
|
||||
onUrlChange,
|
||||
url,
|
||||
method,
|
||||
placeholder,
|
||||
className,
|
||||
onSend,
|
||||
onCancel,
|
||||
onMethodChange,
|
||||
onPaste,
|
||||
onPasteOverwrite,
|
||||
submitIcon = 'send_horizontal',
|
||||
autocomplete,
|
||||
leftSlot,
|
||||
rightSlot,
|
||||
isLoading,
|
||||
stateKey,
|
||||
@@ -87,18 +84,7 @@ export const UrlBar = memo(function UrlBar({
|
||||
onChange={onUrlChange}
|
||||
defaultValue={url}
|
||||
placeholder={placeholder}
|
||||
leftSlot={
|
||||
method != null &&
|
||||
onMethodChange != null && (
|
||||
<div className="py-0.5">
|
||||
<RequestMethodDropdown
|
||||
method={method}
|
||||
onChange={onMethodChange}
|
||||
className="ml-0.5 !h-full"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
leftSlot={leftSlot}
|
||||
rightSlot={
|
||||
<HStack space={0.5}>
|
||||
{rightSlot && <div className="py-0.5 h-full">{rightSlot}</div>}
|
||||
|
||||
@@ -54,10 +54,9 @@ const TAB_DESCRIPTION = 'description';
|
||||
const nonActiveRequestUrlsAtom = atom((get) => {
|
||||
const activeRequestId = get(activeRequestIdAtom);
|
||||
const requests = get(allRequestsAtom);
|
||||
const urls = requests
|
||||
return requests
|
||||
.filter((r) => r.id !== activeRequestId)
|
||||
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
||||
return urls;
|
||||
});
|
||||
|
||||
const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
||||
@@ -227,7 +226,6 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
onUrlChange={handleUrlChange}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
isLoading={activeResponse != null && activeResponse.state !== 'closed'}
|
||||
method={null}
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
|
||||
@@ -55,10 +55,9 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
if (hexDump) {
|
||||
return activeEvent?.message ? hexy(activeEvent?.message) : '';
|
||||
}
|
||||
const text = activeEvent?.message
|
||||
return activeEvent?.message
|
||||
? new TextDecoder('utf-8').decode(Uint8Array.from(activeEvent.message))
|
||||
: '';
|
||||
return text;
|
||||
}, [activeEvent?.message, hexDump]);
|
||||
|
||||
const language = languageFromContentType(null, message);
|
||||
@@ -152,7 +151,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
title="Copy message"
|
||||
icon="copy"
|
||||
size="xs"
|
||||
onClick={() => copyToClipboard(formattedMessage.data ?? '')}
|
||||
onClick={() => copyToClipboard(formattedMessage ?? '')}
|
||||
/>
|
||||
</HStack>
|
||||
)}
|
||||
@@ -183,7 +182,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
) : (
|
||||
<Editor
|
||||
language={language}
|
||||
defaultValue={formattedMessage.data ?? ''}
|
||||
defaultValue={formattedMessage ?? ''}
|
||||
wrapLines={false}
|
||||
readOnly={true}
|
||||
stateKey={null}
|
||||
|
||||
@@ -642,7 +642,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
justify="start"
|
||||
leftSlot={
|
||||
(isLoading || item.leftSlot) && (
|
||||
<div className={classNames('pr-2 flex justify-start opacity-70')}>
|
||||
<div className={classNames('pr-2 flex justify-start [&_svg]:opacity-70')}>
|
||||
{isLoading ? <LoadingIcon /> : item.leftSlot}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
|
||||
interface Props {
|
||||
request: HttpRequest | GrpcRequest | WebsocketRequest;
|
||||
className?: string;
|
||||
shortNames?: boolean;
|
||||
short?: boolean;
|
||||
}
|
||||
|
||||
const methodNames: Record<string, string> = {
|
||||
@@ -18,7 +20,8 @@ const methodNames: Record<string, string> = {
|
||||
query: 'QURY',
|
||||
};
|
||||
|
||||
export function HttpMethodTag({ request, className }: Props) {
|
||||
export function HttpMethodTag({ request, className, short }: Props) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const method =
|
||||
request.model === 'http_request' && request.bodyType === 'graphql'
|
||||
? 'GQL'
|
||||
@@ -26,19 +29,34 @@ export function HttpMethodTag({ request, className }: Props) {
|
||||
? 'GRPC'
|
||||
: request.model === 'websocket_request'
|
||||
? 'WS'
|
||||
: (methodNames[request.method.toLowerCase()] ?? request.method.slice(0, 4));
|
||||
: request.method;
|
||||
let label = method.toUpperCase();
|
||||
|
||||
const paddedMethod = method.padStart(4, ' ').toUpperCase();
|
||||
if (short) {
|
||||
label = methodNames[method.toLowerCase()] ?? method.slice(0, 4);
|
||||
label = label.padStart(4, ' ');
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
className,
|
||||
'text-xs font-mono text-text-subtle flex-shrink-0 whitespace-pre',
|
||||
!settings.coloredMethods && 'text-text-subtle',
|
||||
settings.coloredMethods && method === 'GQL' && 'text-info',
|
||||
settings.coloredMethods && method === 'WS' && 'text-info',
|
||||
settings.coloredMethods && method === 'GRPC' && 'text-info',
|
||||
settings.coloredMethods && method === 'OPTIONS' && 'text-info',
|
||||
settings.coloredMethods && method === 'HEAD' && 'text-info',
|
||||
settings.coloredMethods && method === 'GET' && 'text-primary',
|
||||
settings.coloredMethods && method === 'PUT' && 'text-warning',
|
||||
settings.coloredMethods && method === 'PATCH' && 'text-notice',
|
||||
settings.coloredMethods && method === 'POST' && 'text-success',
|
||||
settings.coloredMethods && method === 'DELETE' && 'text-danger',
|
||||
'font-mono flex-shrink-0 whitespace-pre',
|
||||
'pt-[0.25em]', // Fix for monospace font not vertically centering
|
||||
)}
|
||||
>
|
||||
{paddedMethod}
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,19 +5,20 @@ interface Props {
|
||||
response: HttpResponse;
|
||||
className?: string;
|
||||
showReason?: boolean;
|
||||
short?: boolean;
|
||||
}
|
||||
|
||||
export function HttpStatusTag({ response, className, showReason }: Props) {
|
||||
export function HttpStatusTag({ response, className, showReason, short }: Props) {
|
||||
const { status, state } = response;
|
||||
|
||||
let colorClass;
|
||||
let label = `${status}`;
|
||||
|
||||
if (state === 'initialized') {
|
||||
label = 'CONNECTING';
|
||||
label = short ? 'CONN' : 'CONNECTING';
|
||||
colorClass = 'text-text-subtle';
|
||||
} else if (status < 100) {
|
||||
label = 'ERROR';
|
||||
label = short ? 'ERR' : 'ERROR';
|
||||
colorClass = 'text-danger';
|
||||
} else if (status < 200) {
|
||||
colorClass = 'text-info';
|
||||
@@ -33,8 +34,7 @@ export function HttpStatusTag({ response, className, showReason }: Props) {
|
||||
|
||||
return (
|
||||
<span className={classNames(className, 'font-mono', colorClass)}>
|
||||
{label}{' '}
|
||||
{showReason && 'statusReason' in response ? response.statusReason : null}
|
||||
{label} {showReason && 'statusReason' in response ? response.statusReason : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ export type InputProps = Pick<
|
||||
> & {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
inputWrapperClassName?: string;
|
||||
defaultValue?: string | null;
|
||||
disableObscureToggle?: boolean;
|
||||
fullHeight?: boolean;
|
||||
hideLabel?: boolean;
|
||||
inputWrapperClassName?: string;
|
||||
help?: ReactNode;
|
||||
label: ReactNode;
|
||||
labelClassName?: string;
|
||||
|
||||
@@ -125,8 +125,8 @@ function ActualEventStreamViewer({ response }: Props) {
|
||||
|
||||
function FormattedEditor({ text, language }: { text: string; language: EditorProps['language'] }) {
|
||||
const formatted = useFormatText({ text, language, pretty: true });
|
||||
if (formatted.data == null) return null;
|
||||
return <Editor readOnly defaultValue={formatted.data} language={language} stateKey={null} />;
|
||||
if (formatted == null) return null;
|
||||
return <Editor readOnly defaultValue={formatted} language={language} stateKey={null} />;
|
||||
}
|
||||
|
||||
function EventRow({
|
||||
|
||||
@@ -100,8 +100,7 @@ export function TextViewer({ language, text, responseId, requestId, pretty, clas
|
||||
]);
|
||||
|
||||
const formattedBody = useFormatText({ text, language, pretty });
|
||||
|
||||
if (formattedBody.data == null) {
|
||||
if (formattedBody == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -113,7 +112,7 @@ export function TextViewer({ language, text, responseId, requestId, pretty, clas
|
||||
body = filteredResponse.data != null ? filteredResponse.data : '';
|
||||
}
|
||||
} else {
|
||||
body = formattedBody.data;
|
||||
body = formattedBody;
|
||||
}
|
||||
|
||||
// Decode unicode sequences in the text to readable characters
|
||||
|
||||
@@ -12,7 +12,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from '
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { activeRequestAtom } from '../../hooks/useActiveRequest';
|
||||
import {allRequestsAtom} from "../../hooks/useAllRequests";
|
||||
import { allRequestsAtom } from '../../hooks/useAllRequests';
|
||||
import { useScrollIntoView } from '../../hooks/useScrollIntoView';
|
||||
import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
@@ -214,10 +214,13 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
return null;
|
||||
}
|
||||
|
||||
const opacitySubtle = 'opacity-80';
|
||||
|
||||
const itemPrefix = item.model !== 'folder' && (
|
||||
<HttpMethodTag
|
||||
short
|
||||
request={item}
|
||||
className={classNames(!(active || selected) && 'text-text-subtlest')}
|
||||
className={classNames('text-xs', !(active || selected) && opacitySubtle)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -287,7 +290,11 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
{latestHttpResponse.state !== 'closed' ? (
|
||||
<LoadingIcon size="sm" className="text-text-subtlest" />
|
||||
) : (
|
||||
<HttpStatusTag className="text-xs" response={latestHttpResponse} />
|
||||
<HttpStatusTag
|
||||
short
|
||||
className={classNames('text-xs', !(active || selected) && opacitySubtle)}
|
||||
response={latestHttpResponse}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { tryFormatJson, tryFormatXml } from '../lib/formatters';
|
||||
import type { EditorProps } from '../components/core/Editor/Editor';
|
||||
import { tryFormatJson, tryFormatXml } from '../lib/formatters';
|
||||
|
||||
export function useFormatText({
|
||||
text,
|
||||
@@ -12,6 +12,7 @@ export function useFormatText({
|
||||
pretty: boolean;
|
||||
}) {
|
||||
return useQuery({
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryKey: [text, language, pretty],
|
||||
queryFn: async () => {
|
||||
if (text === '' || !pretty) {
|
||||
@@ -24,5 +25,5 @@ export function useFormatText({
|
||||
return text;
|
||||
}
|
||||
},
|
||||
});
|
||||
}).data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user