diff --git a/.run/Dev Desktop.run.xml b/.run/Dev Desktop.run.xml
index 2e54d7ee..4ebc595c 100644
--- a/.run/Dev Desktop.run.xml
+++ b/.run/Dev Desktop.run.xml
@@ -6,7 +6,9 @@
-
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a0d895b2..85c76643 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,7 +30,8 @@
"prettier": "^2.8.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-helmet-async": "^1.3.0"
+ "react-helmet-async": "^1.3.0",
+ "react-query": "^3.39.3"
},
"devDependencies": {
"@tauri-apps/cli": "^1.2.2",
@@ -2587,6 +2588,14 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/big-integer": {
+ "version": "1.6.51",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -2616,6 +2625,21 @@
"node": ">=8"
}
},
+ "node_modules/broadcast-channel": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
+ "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
+ "dependencies": {
+ "@babel/runtime": "^7.7.2",
+ "detect-node": "^2.1.0",
+ "js-sha3": "0.8.0",
+ "microseconds": "0.2.0",
+ "nano-time": "1.0.0",
+ "oblivious-set": "1.0.0",
+ "rimraf": "3.0.2",
+ "unload": "2.2.0"
+ }
+ },
"node_modules/browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
@@ -3039,6 +3063,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -4537,6 +4566,11 @@
"url": "https://opencollective.com/js-sdsl"
}
},
+ "node_modules/js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4690,6 +4724,15 @@
"node": ">=12"
}
},
+ "node_modules/match-sorter": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
+ "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "remove-accents": "0.4.2"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4710,6 +4753,11 @@
"node": ">=8.6"
}
},
+ "node_modules/microseconds": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
+ "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4734,6 +4782,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "node_modules/nano-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
+ "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
+ "dependencies": {
+ "big-integer": "^1.6.16"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -4902,6 +4958,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/oblivious-set": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
+ "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5275,6 +5336,31 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-query": {
+ "version": "3.39.3",
+ "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
+ "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "broadcast-channel": "^3.4.1",
+ "match-sorter": "^6.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@@ -5419,6 +5505,11 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
+ "node_modules/remove-accents": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
+ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -5942,6 +6033,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/unload": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
+ "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
+ "dependencies": {
+ "@babel/runtime": "^7.6.2",
+ "detect-node": "^2.0.4"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
@@ -8060,6 +8160,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "big-integer": {
+ "version": "1.6.51",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -8083,6 +8188,21 @@
"fill-range": "^7.0.1"
}
},
+ "broadcast-channel": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
+ "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
+ "requires": {
+ "@babel/runtime": "^7.7.2",
+ "detect-node": "^2.1.0",
+ "js-sha3": "0.8.0",
+ "microseconds": "0.2.0",
+ "nano-time": "1.0.0",
+ "oblivious-set": "1.0.0",
+ "rimraf": "3.0.2",
+ "unload": "2.2.0"
+ }
+ },
"browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
@@ -8384,6 +8504,11 @@
"integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
"dev": true
},
+ "detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
+ },
"detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -9470,6 +9595,11 @@
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="
},
+ "js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -9587,6 +9717,15 @@
"@jridgewell/sourcemap-codec": "^1.4.13"
}
},
+ "match-sorter": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
+ "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "remove-accents": "0.4.2"
+ }
+ },
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -9601,6 +9740,11 @@
"picomatch": "^2.3.1"
}
},
+ "microseconds": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
+ "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
+ },
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -9619,6 +9763,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "nano-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
+ "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
+ "requires": {
+ "big-integer": "^1.6.16"
+ }
+ },
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -9733,6 +9885,11 @@
"es-abstract": "^1.20.4"
}
},
+ "oblivious-set": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
+ "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
+ },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -9961,6 +10118,16 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-query": {
+ "version": "3.39.3",
+ "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
+ "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "broadcast-channel": "^3.4.1",
+ "match-sorter": "^6.0.2"
+ }
+ },
"react-refresh": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@@ -10057,6 +10224,11 @@
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg=="
},
+ "remove-accents": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
+ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
+ },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -10435,6 +10607,15 @@
"which-boxed-primitive": "^1.0.2"
}
},
+ "unload": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
+ "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
+ "requires": {
+ "@babel/runtime": "^7.6.2",
+ "detect-node": "^2.0.4"
+ }
+ },
"update-browserslist-db": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
diff --git a/package.json b/package.json
index 0d767a7f..74a88721 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,8 @@
"prettier": "^2.8.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-helmet-async": "^1.3.0"
+ "react-helmet-async": "^1.3.0",
+ "react-query": "^3.39.3"
},
"devDependencies": {
"@tauri-apps/cli": "^1.2.2",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index ce991881..226fb289 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -59,6 +59,15 @@ dependencies = [
"alloc-no-stdlib",
]
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "anyhow"
version = "1.0.69"
@@ -295,6 +304,22 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "serde",
+ "time 0.1.45",
+ "wasm-bindgen",
+ "winapi",
+]
+
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -326,6 +351,16 @@ dependencies = [
"objc",
]
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
[[package]]
name = "color_quant"
version = "1.1.0"
@@ -504,6 +539,50 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+[[package]]
+name = "cxx"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "darling"
version = "0.13.4"
@@ -600,9 +679,9 @@ dependencies = [
[[package]]
name = "deno_core"
-version = "0.171.0"
+version = "0.173.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dc41944f05dfeacfc2610e91f40ddcf246f3aeeac8ae4c26df46bfbf01a3902"
+checksum = "e3f5ea1db875ff1eb020c04b900f75c810a9b66deec4a5437205b868bff9a7df"
dependencies = [
"anyhow",
"bytes",
@@ -625,9 +704,9 @@ dependencies = [
[[package]]
name = "deno_ops"
-version = "0.49.0"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4740bc5738ad07dc1f523a232a4079a995fa2ad11efd71e09e8e32bf28f21ee1"
+checksum = "1d06332c139917f0fe6722e6c9bd3aac45bfdf3e306627e7eb681a42bad56497"
dependencies = [
"once_cell",
"pmutil",
@@ -736,6 +815,9 @@ name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+dependencies = [
+ "serde",
+]
[[package]]
name = "embed_plist"
@@ -1432,6 +1514,30 @@ dependencies = [
"tokio-native-tls",
]
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
[[package]]
name = "ico"
version = "0.1.0"
@@ -1786,6 +1892,15 @@ dependencies = [
"safemem",
]
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "lock_api"
version = "0.4.9"
@@ -2426,7 +2541,7 @@ dependencies = [
"line-wrap",
"quick-xml",
"serde",
- "time",
+ "time 0.3.17",
]
[[package]]
@@ -2842,6 +2957,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+[[package]]
+name = "scratch"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
+
[[package]]
name = "sct"
version = "0.7.0"
@@ -3003,9 +3124,9 @@ dependencies = [
[[package]]
name = "serde_v8"
-version = "0.82.0"
+version = "0.84.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c060fd38f18c420e82ab21592ec1f088b39bccb6897b1dda394d63628e22158d"
+checksum = "94c6b0ea68c2368070520883b5caac9b5bff17597cfac77aebc07280e8585f7f"
dependencies = [
"bytes",
"derive_more",
@@ -3221,6 +3342,7 @@ dependencies = [
"bitflags",
"byteorder",
"bytes",
+ "chrono",
"crc",
"crossbeam-queue",
"dotenvy",
@@ -3245,12 +3367,15 @@ dependencies = [
"percent-encoding",
"rustls",
"rustls-pemfile",
+ "serde",
+ "serde_json",
"sha2",
"smallvec",
"sqlformat",
"sqlx-rt",
"stringprep",
"thiserror",
+ "time 0.3.17",
"tokio-stream",
"url",
"webpki-roots",
@@ -3265,9 +3390,12 @@ dependencies = [
"dotenvy",
"either",
"heck 0.4.1",
+ "hex",
"once_cell",
"proc-macro2",
"quote",
+ "serde",
+ "serde_json",
"sha2",
"sqlx-core",
"sqlx-rt",
@@ -3848,12 +3976,14 @@ dependencies = [
name = "tauri-app"
version = "0.0.0"
dependencies = [
+ "chrono",
"cocoa",
"deno_ast",
"deno_core",
"futures",
"http",
"objc",
+ "rand 0.8.5",
"reqwest",
"serde",
"serde_json",
@@ -3861,6 +3991,7 @@ dependencies = [
"tauri",
"tauri-build",
"tokio",
+ "uuid 1.3.0",
]
[[package]]
@@ -3900,7 +4031,7 @@ dependencies = [
"sha2",
"tauri-utils",
"thiserror",
- "time",
+ "time 0.3.17",
"uuid 1.3.0",
"walkdir",
]
@@ -4013,6 +4144,15 @@ dependencies = [
"utf-8",
]
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "text_lines"
version = "0.6.0"
@@ -4058,6 +4198,17 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "time"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
[[package]]
name = "time"
version = "0.3.17"
@@ -4386,9 +4537,9 @@ dependencies = [
[[package]]
name = "v8"
-version = "0.60.1"
+version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07fd5b3ed559897ff02c0f62bc0a5f300bfe79bb4c77a50031b8df771701c628"
+checksum = "547e58962ac268fe0b1fbfb653ed341a08e3953994f7f7c978e46ec30afdf8f0"
dependencies = [
"bitflags",
"fslock",
@@ -4453,6 +4604,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index c9c9b8fb..b26a472c 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -13,18 +13,21 @@ edition = "2021"
tauri-build = { version = "1.2", features = [] }
[dependencies]
-serde_json = { version = "1.0" }
+serde_json = { version = "1.0", features = ["raw_value"] }
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = ["config-toml", "devtools", "shell-open", "system-tray", "window-start-dragging"] }
http = { version = "0.2.8" }
reqwest = { version = "0.11.14", features = ["json"] }
-tokio = { version = "1.25.0", features = [ "sync"] }
+tokio = { version = "1.25.0", features = ["sync"] }
futures = { version = "0.3.26" }
-deno_core = { version = "0.171.0" }
+deno_core = { version = "0.173.0" }
deno_ast = { version = "0.24.0", features = ["transpiling"] }
objc = { version = "0.2.7" }
cocoa = { version = "0.24.1" }
-sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }
+sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time", "offline"] }
+uuid = { version = "1.3.0" }
+rand = { version = "0.8.5" }
+chrono = { version = "0.4.23", features = ["serde"] }
[features]
# by default Tauri runs in production mode
diff --git a/src-tauri/build.rs b/src-tauri/build.rs
index 795b9b7c..d860e1e6 100644
--- a/src-tauri/build.rs
+++ b/src-tauri/build.rs
@@ -1,3 +1,3 @@
fn main() {
- tauri_build::build()
+ tauri_build::build()
}
diff --git a/src-tauri/migrations/20230225181302_init.sql b/src-tauri/migrations/20230225181302_init.sql
new file mode 100644
index 00000000..6a7b571a
--- /dev/null
+++ b/src-tauri/migrations/20230225181302_init.sql
@@ -0,0 +1,38 @@
+CREATE TABLE workspaces
+(
+ id TEXT NOT NULL PRIMARY KEY,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at DATETIME,
+ name TEXT NOT NULL,
+ description TEXT NOT NULL
+);
+
+CREATE TABLE requests
+(
+ id TEXT NOT NULL PRIMARY KEY,
+ workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at DATETIME,
+ name TEXT NOT NULL,
+ url TEXT NOT NULL,
+ method TEXT NOT NULL,
+ headers TEXT NOT NULL,
+ body TEXT
+);
+
+CREATE TABLE responses
+(
+ id TEXT NOT NULL PRIMARY KEY,
+ request_id TEXT NOT NULL REFERENCES requests (id) ON DELETE CASCADE,
+ workspace_id TEXT NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at DATETIME,
+ elapsed INTEGER NOT NULL,
+ status INTEGER NOT NULL,
+ status_reason TEXT NOT NULL,
+ body TEXT NOT NULL,
+ headers TEXT NOT NULL
+);
diff --git a/src-tauri/plugins/plugin.ts b/src-tauri/plugins/plugin.ts
index e139c284..c68da85e 100644
--- a/src-tauri/plugins/plugin.ts
+++ b/src-tauri/plugins/plugin.ts
@@ -2,4 +2,4 @@ console.log('---------------------------');
console.log('- 👋 Hello from plugin.ts -');
console.log('---------------------------');
-Deno.core.ops.op_hello('World');
+Deno.core.opAsync('op_hello', 'World');
diff --git a/src-tauri/sqlx-data.json b/src-tauri/sqlx-data.json
new file mode 100644
index 00000000..a25d7acd
--- /dev/null
+++ b/src-tauri/sqlx-data.json
@@ -0,0 +1,263 @@
+{
+ "db": "SQLite",
+ "74850a49fa21f4cb5f30905b8ede1fa76935c1ff7ad13c105c6de772d10ff742": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Right": 6
+ }
+ },
+ "query": "\n INSERT INTO requests (id, workspace_id, name, url, method, body, updated_at, headers)\n VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, '{}')\n ON CONFLICT (id) DO UPDATE SET\n name = excluded.name,\n method = excluded.method,\n body = excluded.body,\n url = excluded.url;\n "
+ },
+ "8069c0bd326f659faca7b45b03e5317d7339a168f4cd7776d9f84304bb7ae7ac": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Text"
+ },
+ {
+ "name": "created_at",
+ "ordinal": 1,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "updated_at",
+ "ordinal": 2,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "deleted_at",
+ "ordinal": 3,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "name",
+ "ordinal": 4,
+ "type_info": "Text"
+ },
+ {
+ "name": "description",
+ "ordinal": 5,
+ "type_info": "Text"
+ }
+ ],
+ "nullable": [
+ false,
+ false,
+ false,
+ true,
+ false,
+ false
+ ],
+ "parameters": {
+ "Right": 0
+ }
+ },
+ "query": "\n SELECT id, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n "
+ },
+ "d461b9471bdc1fd3f85ca9351f686def07634b4906c8429eeef343b11992b445": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Text"
+ },
+ {
+ "name": "workspace_id",
+ "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": "url",
+ "ordinal": 6,
+ "type_info": "Text"
+ },
+ {
+ "name": "method",
+ "ordinal": 7,
+ "type_info": "Text"
+ },
+ {
+ "name": "body",
+ "ordinal": 8,
+ "type_info": "Text"
+ },
+ {
+ "name": "headers",
+ "ordinal": 9,
+ "type_info": "Text"
+ }
+ ],
+ "nullable": [
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ true,
+ false
+ ],
+ "parameters": {
+ "Right": 1
+ }
+ },
+ "query": "\n SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body, headers\n FROM requests\n WHERE id = ?\n "
+ },
+ "da08ebedec0942fd5c54ed1e180d7dc399629f83bfa1341c1c09a048123adac1": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Text"
+ },
+ {
+ "name": "workspace_id",
+ "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": "url",
+ "ordinal": 6,
+ "type_info": "Text"
+ },
+ {
+ "name": "method",
+ "ordinal": 7,
+ "type_info": "Text"
+ },
+ {
+ "name": "body",
+ "ordinal": 8,
+ "type_info": "Text"
+ },
+ {
+ "name": "headers",
+ "ordinal": 9,
+ "type_info": "Text"
+ }
+ ],
+ "nullable": [
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ true,
+ false
+ ],
+ "parameters": {
+ "Right": 1
+ }
+ },
+ "query": "\n SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body, headers\n FROM requests\n WHERE workspace_id = ?;\n "
+ },
+ "f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Right": 3
+ }
+ },
+ "query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n "
+ },
+ "fa5011146663ba5675764f33bf55a86b8274aa18737aff427fd1e3fb74ef3535": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Text"
+ },
+ {
+ "name": "created_at",
+ "ordinal": 1,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "updated_at",
+ "ordinal": 2,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "deleted_at",
+ "ordinal": 3,
+ "type_info": "Datetime"
+ },
+ {
+ "name": "name",
+ "ordinal": 4,
+ "type_info": "Text"
+ },
+ {
+ "name": "description",
+ "ordinal": 5,
+ "type_info": "Text"
+ }
+ ],
+ "nullable": [
+ false,
+ false,
+ false,
+ true,
+ false,
+ false
+ ],
+ "parameters": {
+ "Right": 1
+ }
+ },
+ "query": "\n SELECT id, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n WHERE id = ?\n "
+ }
+}
\ No newline at end of file
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 5db5cb2c..5e747f72 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -9,81 +9,25 @@ extern crate objc;
use std::collections::HashMap;
use std::fs::create_dir_all;
-use tokio::sync::Mutex;
+use std::path::Path;
use http::header::{HeaderName, USER_AGENT};
use http::{HeaderMap, HeaderValue, Method};
use reqwest::redirect::Policy;
+use sqlx::migrate::Migrator;
use sqlx::sqlite::SqlitePoolOptions;
use sqlx::{Pool, Sqlite};
use tauri::{AppHandle, State, Wry};
use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowEvent};
+use tokio::sync::Mutex;
+use crate::models::{create_workspace, find_workspaces, Request, Workspace};
use window_ext::WindowExt;
+mod models;
mod runtime;
mod window_ext;
-fn main() {
- // here `"quit".to_string()` defines the menu item id, and the second parameter is the menu item label.
- let quit = CustomMenuItem::new("quit".to_string(), "Quit");
- let tray_menu = SystemTrayMenu::new().add_item(quit);
- let system_tray = SystemTray::new().with_menu(tray_menu);
-
- tauri::Builder::default()
- .system_tray(system_tray)
- .setup(|app| {
- let win = app.get_window("main").unwrap();
- win.position_traffic_lights();
- Ok(())
- })
- .setup(|app| {
- let dir = app.path_resolver().app_data_dir().unwrap();
- create_dir_all(dir.clone()).expect("Problem creating App directory!");
- let p = dir.join("db.sqlite");
- let p_string = p.to_string_lossy().replace(" ", "%20");
- let url = format!("sqlite://{}?mode=rwc", p_string);
- println!("DB URL: {}", url);
- tauri::async_runtime::block_on(async move {
- let pool = SqlitePoolOptions::new()
- .connect(url.as_str())
- .await
- .expect("Failed to connect to database");
- app.manage(Mutex::new(pool));
- println!("CONNECTED!");
- Ok(())
- })
- })
- .on_system_tray_event(|app, event| match event {
- SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
- "quit" => {
- std::process::exit(0);
- }
- "hide" => {
- let window = app.get_window("main").unwrap();
- window.hide().unwrap();
- }
- _ => {}
- },
- _ => {}
- })
- .on_window_event(|e| {
- let apply_offset = || {
- let win = e.window();
- win.position_traffic_lights();
- };
-
- match e.event() {
- WindowEvent::Resized(..) => apply_offset(),
- WindowEvent::ThemeChanged(..) => apply_offset(),
- _ => {}
- }
- })
- .invoke_handler(tauri::generate_handler![send_request, greet, load_db,])
- .run(tauri::generate_context!())
- .expect("error while running tauri application");
-}
-
#[derive(serde::Serialize)]
pub struct CustomResponse {
status: u16,
@@ -98,18 +42,11 @@ pub struct CustomResponse {
#[tauri::command]
async fn load_db(db_instance: State<'_, Mutex>>) -> Result<(), String> {
- println!("INITIALIZING DB");
- sqlx::query(
- "CREATE TABLE IF NOT EXISTS responses (
- id INTEGER PRIMARY KEY,
- body TEXT NOT NULL,
- status INT NOT NULL
- )",
- )
- .execute(&*db_instance.lock().await)
- .await
- .expect("Failed to create table");
-
+ let pool = &*db_instance.lock().await;
+ let m = Migrator::new(Path::new("./migrations"))
+ .await
+ .expect("Failed to load migrations");
+ m.run(pool).await.expect("Failed to run migrations");
Ok(())
}
@@ -142,9 +79,7 @@ async fn send_request(
);
let m = Method::from_bytes(method.to_uppercase().as_bytes()).unwrap();
- let builder = client
- .request(m, abs_url.to_string())
- .headers(headers);
+ let builder = client.request(m, abs_url.to_string()).headers(headers);
let req = match body {
Some(b) => builder.body(b.to_string()).build(),
@@ -160,7 +95,6 @@ async fn send_request(
};
let resp = client.execute(req).await;
-
let elapsed = start.elapsed().as_millis();
let p = app_handle
@@ -183,12 +117,6 @@ async fn send_request(
.collect::>();
let body = v.text().await.unwrap();
let elapsed2 = start.elapsed().as_millis();
- sqlx::query("INSERT INTO responses (body, status) VALUES (?, ?)")
- .bind(body.clone())
- .bind(status.clone())
- .execute(&*db_instance.lock().await)
- .await
- .unwrap();
Ok(CustomResponse {
status,
status_reason,
@@ -207,7 +135,122 @@ async fn send_request(
}
}
+#[tauri::command]
+async fn upsert_request(
+ id: Option<&str>,
+ workspace_id: &str,
+ name: &str,
+ db_instance: State<'_, Mutex>>,
+) -> Result {
+ let pool = &*db_instance.lock().await;
+ models::upsert_request(
+ id,
+ workspace_id,
+ name,
+ "GET",
+ None,
+ "https://google.com",
+ pool,
+ )
+ .await
+ .map_err(|e| e.to_string())
+}
+
+#[tauri::command]
+async fn requests(
+ workspace_id: &str,
+ db_instance: State<'_, Mutex>>,
+) -> Result, String> {
+ let pool = &*db_instance.lock().await;
+ models::find_requests(workspace_id, pool)
+ .await
+ .map_err(|e| e.to_string())
+}
+
+#[tauri::command]
+async fn workspaces(db_instance: State<'_, Mutex>>) -> Result, String> {
+ let pool = &*db_instance.lock().await;
+ let workspaces = find_workspaces(pool)
+ .await
+ .expect("Failed to find workspaces");
+ if workspaces.is_empty() {
+ let workspace = create_workspace("Default", "This is the default workspace", pool)
+ .await
+ .expect("Failed to create workspace");
+ Ok(vec![workspace])
+ } else {
+ Ok(workspaces)
+ }
+}
+
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
+
+fn main() {
+ // here `"quit".to_string()` defines the menu item id, and the second parameter is the menu item label.
+ let quit = CustomMenuItem::new("quit".to_string(), "Quit");
+ let tray_menu = SystemTrayMenu::new().add_item(quit);
+ let system_tray = SystemTray::new().with_menu(tray_menu);
+
+ tauri::Builder::default()
+ .system_tray(system_tray)
+ .setup(|app| {
+ let win = app.get_window("main").unwrap();
+ win.position_traffic_lights();
+ Ok(())
+ })
+ .setup(|app| {
+ let dir = app.path_resolver().app_data_dir().unwrap();
+ create_dir_all(dir.clone()).expect("Problem creating App directory!");
+ let p = dir.join("db.sqlite");
+ let p_string = p.to_string_lossy().replace(" ", "%20");
+ let url = format!("sqlite://{}?mode=rwc", p_string);
+ println!("DB PATH: {}", p_string);
+ tauri::async_runtime::block_on(async move {
+ let pool = SqlitePoolOptions::new()
+ .connect(url.as_str())
+ .await
+ .expect("Failed to connect to database");
+ app.manage(Mutex::new(pool));
+ Ok(())
+ })
+ })
+ .on_system_tray_event(|app, event| {
+ if let SystemTrayEvent::MenuItemClick { id, .. } = event {
+ match id.as_str() {
+ "quit" => {
+ std::process::exit(0);
+ }
+ "hide" => {
+ let window = app.get_window("main").unwrap();
+ window.hide().unwrap();
+ }
+ _ => {}
+ }
+ }
+ })
+ .on_window_event(|e| {
+ let apply_offset = || {
+ let win = e.window();
+ win.position_traffic_lights();
+ };
+
+ match e.event() {
+ WindowEvent::Resized(..) => apply_offset(),
+ WindowEvent::ThemeChanged(..) => apply_offset(),
+ _ => {}
+ }
+ })
+ .invoke_handler(tauri::generate_handler![
+ send_request,
+ greet,
+ load_db,
+ workspaces,
+ requests,
+ upsert_request,
+ ])
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs
new file mode 100644
index 00000000..8b17996c
--- /dev/null
+++ b/src-tauri/src/models.rs
@@ -0,0 +1,169 @@
+use std::collections::HashMap;
+
+use rand::distributions::{Alphanumeric, DistString};
+use serde::{Deserialize, Serialize};
+use sqlx::types::chrono::NaiveDateTime;
+use sqlx::{Pool, Sqlite};
+
+#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Workspace {
+ pub id: String,
+ pub created_at: NaiveDateTime,
+ pub updated_at: NaiveDateTime,
+ pub deleted_at: Option,
+ pub name: String,
+ pub description: String,
+}
+
+#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
+pub struct Request {
+ pub id: String,
+ pub created_at: NaiveDateTime,
+ pub updated_at: NaiveDateTime,
+ pub deleted_at: Option,
+ pub workspace_id: String,
+ pub name: String,
+ pub url: String,
+ pub method: String,
+ pub body: Option,
+ pub headers: String,
+}
+
+#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
+pub struct Response {
+ pub id: String,
+ pub workspace_id: String,
+ pub request_id: String,
+ pub created_at: NaiveDateTime,
+ pub updated_at: NaiveDateTime,
+ pub deleted_at: Option,
+ pub name: String,
+ pub status: u16,
+ pub status_reason: Option<&'static str>,
+ pub body: String,
+ pub url: String,
+ pub method: String,
+ pub elapsed: u128,
+ pub elapsed2: u128,
+ pub headers: HashMap,
+}
+
+pub async fn find_workspaces(pool: &Pool) -> Result, sqlx::Error> {
+ sqlx::query_as!(
+ Workspace,
+ r#"
+ SELECT id, created_at, updated_at, deleted_at, name, description
+ FROM workspaces
+ "#,
+ )
+ .fetch_all(pool)
+ .await
+}
+
+pub async fn get_workspace(id: &str, pool: &Pool) -> Result {
+ sqlx::query_as!(
+ Workspace,
+ r#"
+ SELECT id, created_at, updated_at, deleted_at, name, description
+ FROM workspaces
+ WHERE id = ?
+ "#,
+ id,
+ )
+ .fetch_one(pool)
+ .await
+}
+
+pub async fn create_workspace(
+ name: &str,
+ description: &str,
+ pool: &Pool,
+) -> Result {
+ let id = generate_id("wrk");
+ sqlx::query!(
+ r#"
+ INSERT INTO workspaces (id, name, description)
+ VALUES (?, ?, ?)
+ "#,
+ id,
+ name,
+ description,
+ )
+ .execute(pool)
+ .await
+ .expect("Failed to insert new workspace");
+
+ get_workspace(&id, pool).await
+}
+
+pub async fn upsert_request(
+ id: Option<&str>,
+ workspace_id: &str,
+ name: &str,
+ method: &str,
+ body: Option<&str>,
+ url: &str,
+ pool: &Pool,
+) -> Result {
+ let id = generate_id("wrk");
+ sqlx::query!(
+ r#"
+ INSERT INTO requests (id, workspace_id, name, url, method, body, updated_at, headers)
+ VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, '{}')
+ ON CONFLICT (id) DO UPDATE SET
+ name = excluded.name,
+ method = excluded.method,
+ body = excluded.body,
+ url = excluded.url;
+ "#,
+ id,
+ workspace_id,
+ name,
+ url,
+ method,
+ body,
+ )
+ .execute(pool)
+ .await
+ .expect("Failed to insert new request");
+ get_request(&id, pool).await
+}
+
+pub async fn find_requests(
+ workspace_id: &str,
+ pool: &Pool,
+) -> Result, sqlx::Error> {
+ sqlx::query_as!(
+ Request,
+ r#"
+ SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body, headers
+ FROM requests
+ WHERE workspace_id = ?;
+ "#,
+ workspace_id,
+ )
+ .fetch_all(pool)
+ .await
+}
+
+pub async fn get_request(id: &str, pool: &Pool) -> Result {
+ sqlx::query_as!(
+ Request,
+ r#"
+ SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body, headers
+ FROM requests
+ WHERE id = ?
+ "#,
+ id,
+ )
+ .fetch_one(pool)
+ .await
+}
+
+fn generate_id(prefix: &str) -> String {
+ format!(
+ "{prefix}_{}",
+ Alphanumeric.sample_string(&mut rand::thread_rng(), 10)
+ )
+}
diff --git a/src-tauri/src/runtime.js b/src-tauri/src/runtime.js
index 4a8e686e..51e0a013 100644
--- a/src-tauri/src/runtime.js
+++ b/src-tauri/src/runtime.js
@@ -1,5 +1,5 @@
(function (globalThis) {
- Deno.core.initializeAsyncOps();
+ // Deno.core.print(Object.keys(Deno.core).join('\n'));
function argsToMessage(...args) {
return args.map((arg) => JSON.stringify(arg)).join(' ');
diff --git a/src-tauri/src/runtime.rs b/src-tauri/src/runtime.rs
index aef51fc3..fca39a29 100644
--- a/src-tauri/src/runtime.rs
+++ b/src-tauri/src/runtime.rs
@@ -24,12 +24,18 @@ pub async fn run_plugin(file_path: &str) -> Result<(), AnyError> {
runtime
.execute_script("", include_str!("runtime.js"))
- .unwrap();
+ .expect("Failed to execute runtime.js");
- let main_module = deno_core::resolve_path(file_path)?;
- let mod_id = runtime.load_main_module(&main_module, None).await?;
+ let main_module = deno_core::resolve_path(file_path).expect("Failed to resolve path");
+ let mod_id = runtime
+ .load_main_module(&main_module, None)
+ .await
+ .expect("Failed to load main module");
let result = runtime.mod_evaluate(mod_id);
- runtime.run_event_loop(false).await?;
+ runtime
+ .run_event_loop(false)
+ .await
+ .expect("Failed to run event loop");
result.await?
}
diff --git a/src-web/App.tsx b/src-web/App.tsx
index 286f6b61..080b784b 100644
--- a/src-web/App.tsx
+++ b/src-web/App.tsx
@@ -9,6 +9,7 @@ import { Sidebar } from './components/Sidebar';
import { UrlBar } from './components/UrlBar';
import { Grid } from './components/Grid';
import { motion } from 'framer-motion';
+import { useRequests, useWorkspace, useWorkspaces } from './hooks/useWorkspaces';
interface Response {
url: string;
@@ -21,6 +22,8 @@ interface Response {
}
function App() {
+ const { data } = useWorkspace();
+ console.log('DATA', data);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [response, setResponse] = useState(null);
@@ -31,6 +34,19 @@ function App() {
value: 'GET',
});
+ useEffect(() => {
+ const listener = async (e: KeyboardEvent) => {
+ if (e.metaKey && (e.key === 'Enter' || e.key === 'r')) {
+ await sendRequest();
+ }
+ };
+ document.documentElement.addEventListener('keypress', listener);
+ return () => document.documentElement.removeEventListener('keypress', listener);
+ }, []);
+
+ if (!data) return null;
+ const { requests, workspace } = data;
+
async function sendRequest() {
setLoading(true);
setError(null);
@@ -54,20 +70,10 @@ function App() {
const contentType = response?.headers['content-type']?.split(';')[0] ?? 'text/plain';
- useEffect(() => {
- const listener = async (e: KeyboardEvent) => {
- if (e.metaKey && (e.key === 'Enter' || e.key === 'r')) {
- await sendRequest();
- }
- };
- document.documentElement.addEventListener('keypress', listener);
- return () => document.documentElement.removeEventListener('keypress', listener);
- }, []);
-
return (
<>
-
+
diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx
index 5c44527b..50172497 100644
--- a/src-web/components/Sidebar.tsx
+++ b/src-web/components/Sidebar.tsx
@@ -5,10 +5,15 @@ import { Button } from './Button';
import useTheme from '../hooks/useTheme';
import { HStack } from './Stacks';
import { WindowDragRegion } from './WindowDragRegion';
+import { Request } from '../hooks/useWorkspaces';
+import { invoke } from '@tauri-apps/api';
-type Props = Omit, 'children'>;
+interface Props extends Omit, 'children'> {
+ workspaceId: string;
+ requests: Request[];
+}
-export function Sidebar({ className, ...props }: Props) {
+export function Sidebar({ className, workspaceId, requests, ...props }: Props) {
const { toggleTheme } = useTheme();
return (
+ {
+ const req = await invoke('upsert_request', {
+ workspaceId,
+ id: null,
+ name: 'Test Request',
+ });
+ console.log('UPSERTED', req);
+ }}
+ />
- {['Test Request', 'Another Request', 'Something else', 'And Another'].map((v, i) => (
- -
+ {requests.map((r) => (
+
-
))}
diff --git a/src-web/hooks/useWorkspaces.ts b/src-web/hooks/useWorkspaces.ts
new file mode 100644
index 00000000..fc8deeff
--- /dev/null
+++ b/src-web/hooks/useWorkspaces.ts
@@ -0,0 +1,62 @@
+import { invoke } from '@tauri-apps/api';
+import { useQuery, UseQueryResult } from 'react-query';
+
+interface BaseModel {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ deletedAt: Date | null;
+}
+
+export interface Request extends BaseModel {
+ name: string;
+ url: string;
+ body: string | null;
+ method: string;
+}
+
+export interface Workspace extends BaseModel {
+ name: string;
+ description: string;
+}
+
+export function useWorkspaces(): UseQueryResult {
+ return useQuery('workspaces', async () => {
+ const workspaces = (await invoke('workspaces')) as Workspace[];
+ return workspaces.map(convertDates);
+ });
+}
+
+export function useRequests(): UseQueryResult {
+ return useQuery('requests', async () => {
+ const workspaces = (await invoke('workspaces')) as Workspace[];
+ const requests = (await invoke('requests', { workspaceId: workspaces[0].id })) as Request[];
+ return requests.map(convertDates);
+ });
+}
+
+export function useWorkspace(): UseQueryResult<{ workspace: Workspace; requests: Request[] }> {
+ return useQuery('workspace', async () => {
+ const workspaces = (await invoke('workspaces')) as Workspace[];
+ const requests = (await invoke('requests', { workspaceId: workspaces[0].id })) as Request[];
+ return {
+ workspace: convertDates(workspaces[0]),
+ requests: requests.map(convertDates),
+ };
+ });
+}
+
+function convertDates(m: T): T {
+ return {
+ ...m,
+ createdAt: convertDate(m.createdAt),
+ updatedAt: convertDate(m.updatedAt),
+ deletedAt: m.deletedAt ? convertDate(m.deletedAt) : null,
+ };
+}
+
+function convertDate(d: string | Date): Date {
+ const date = new Date(d);
+ const userTimezoneOffset = date.getTimezoneOffset() * 60000;
+ return new Date(date.getTime() - userTimezoneOffset);
+}
diff --git a/src-web/main.tsx b/src-web/main.tsx
index ed45a463..052f74ea 100644
--- a/src-web/main.tsx
+++ b/src-web/main.tsx
@@ -8,6 +8,7 @@ import { invoke } from '@tauri-apps/api';
import { setTheme } from './lib/theme';
import './main.css';
+import { QueryClient, QueryClientProvider } from 'react-query';
setTheme();
@@ -15,12 +16,16 @@ await init();
greet();
await invoke('load_db');
+const queryClient = new QueryClient();
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
-
-
-
+
+
+
+
+
+
+
,
);