titanpl-sdk 3.0.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +38 -35
- package/templates/app/app.js +1 -1
- package/templates/server/Cargo.toml +2 -0
- package/templates/server/src/extensions/builtin.rs +271 -139
- package/templates/server/src/extensions/external.rs +338 -338
- package/templates/server/src/extensions/mod.rs +6 -6
- package/templates/server/src/extensions/titan_core.js +49 -13
- package/templates/server/src/main.rs +3 -3
- package/templates/titan/bundle.js +259 -259
- package/templates/titan/dev.js +4 -4
- package/templates/titan/error-box.js +277 -277
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ It provides the necessary **Type Definitions** to make your IDE understand the g
|
|
|
24
24
|
|
|
25
25
|
> **Note:** The actual implementation of `t.log`, `t.fetch`, and other APIs are embedded directly into the [Titan Planet Binary](https://github.com/ezet-galaxy/titanpl). This SDK simply provides the "blueprints" (types) and a "sandbox" (test runner).
|
|
26
26
|
|
|
27
|
+
**Important Note:** Currently, Titan Planet and its entire package ecosystem are only for Windows. The Linux version is in development (dev only) for the new architecture and will be launched later.
|
|
28
|
+
|
|
27
29
|
---
|
|
28
30
|
|
|
29
31
|
## ✨ Features
|
package/package.json
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
2
|
+
"name": "titanpl-sdk",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Development SDK for Titan Planet. Provides TypeScript type definitions for the global 't' runtime object and a 'lite' test-harness runtime for building and verifying extensions.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"titanpl-sdk": "./bin/run.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"templates/",
|
|
14
|
+
"titan",
|
|
15
|
+
"assets",
|
|
16
|
+
"index.js",
|
|
17
|
+
"index.d.ts",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"titan",
|
|
22
|
+
"titan-planet",
|
|
23
|
+
"titanpl-sdk",
|
|
24
|
+
"ezetgalaxy",
|
|
25
|
+
"types",
|
|
26
|
+
"typescript",
|
|
27
|
+
"intellisense",
|
|
28
|
+
"sdk",
|
|
29
|
+
"backend-sdk",
|
|
30
|
+
"extension-development"
|
|
31
|
+
],
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@titanpl/core": "latest",
|
|
35
|
+
"@titanpl/node": "latest"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@tgrv/microgravity": "latest"
|
|
39
|
+
}
|
|
37
40
|
}
|
package/templates/app/app.js
CHANGED
|
@@ -17,9 +17,10 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
17
17
|
use serde_json::Value;
|
|
18
18
|
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
19
19
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
20
|
-
use
|
|
21
|
-
use
|
|
22
|
-
use
|
|
20
|
+
use std::sync::OnceLock;
|
|
21
|
+
use deadpool_postgres::{Manager, Pool};
|
|
22
|
+
use tokio_postgres::{NoTls, Config};
|
|
23
|
+
|
|
23
24
|
|
|
24
25
|
use crate::utils::{blue, gray, red, parse_expires_in};
|
|
25
26
|
use super::{TitanRuntime, v8_str, v8_to_string, throw, ShareContextStore};
|
|
@@ -27,7 +28,7 @@ use super::{TitanRuntime, v8_str, v8_to_string, throw, ShareContextStore};
|
|
|
27
28
|
const TITAN_CORE_JS: &str = include_str!("titan_core.js");
|
|
28
29
|
|
|
29
30
|
// Database connection pool
|
|
30
|
-
static DB_POOL:
|
|
31
|
+
static DB_POOL: OnceLock<Pool> = OnceLock::new();
|
|
31
32
|
static HTTP_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
|
|
32
33
|
|
|
33
34
|
fn get_http_client() -> &'static reqwest::Client {
|
|
@@ -359,7 +360,9 @@ fn native_log(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments,
|
|
|
359
360
|
|
|
360
361
|
fn native_jwt_sign(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
361
362
|
let payload_val = args.get(0);
|
|
362
|
-
let json_str = v8::json::stringify(scope, payload_val)
|
|
363
|
+
let json_str = v8::json::stringify(scope, payload_val)
|
|
364
|
+
.map(|s| s.to_rust_string_lossy(scope))
|
|
365
|
+
.unwrap_or_else(|| "{}".to_string());
|
|
363
366
|
let mut payload: serde_json::Map<String, Value> = serde_json::from_str(&json_str).unwrap_or_default();
|
|
364
367
|
let secret = v8_to_string(scope, args.get(1));
|
|
365
368
|
|
|
@@ -450,74 +453,121 @@ fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallback
|
|
|
450
453
|
}
|
|
451
454
|
|
|
452
455
|
fn native_db_connect(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
456
|
+
|
|
453
457
|
let conn_string = v8_to_string(scope, args.get(0));
|
|
454
|
-
|
|
458
|
+
|
|
455
459
|
if conn_string.is_empty() {
|
|
456
|
-
throw(scope, "t.db.connect(): connection string
|
|
460
|
+
throw(scope, "t.db.connect(): connection string required");
|
|
457
461
|
return;
|
|
458
462
|
}
|
|
459
463
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
let mut max_size = 16;
|
|
465
|
+
|
|
466
|
+
if args.length() > 1 && args.get(1).is_object() {
|
|
467
|
+
let opts = args.get(1).to_object(scope).unwrap();
|
|
468
|
+
let max_key = v8_str(scope, "max");
|
|
469
|
+
if let Some(v) = opts.get(scope, max_key.into()) {
|
|
470
|
+
if let Some(n) = v.number_value(scope) {
|
|
471
|
+
max_size = n as usize;
|
|
472
|
+
}
|
|
469
473
|
}
|
|
470
474
|
}
|
|
475
|
+
|
|
476
|
+
if DB_POOL.get().is_none() {
|
|
477
|
+
let cfg: Config = match conn_string.parse() {
|
|
478
|
+
Ok(c) => c,
|
|
479
|
+
Err(e) => {
|
|
480
|
+
let msg = format!("t.db.connect(): Invalid connection string: {}", e);
|
|
481
|
+
let message = v8::String::new(scope, &msg).unwrap_or_else(|| v8::String::new(scope, "Error").unwrap());
|
|
482
|
+
let exception = v8::Exception::error(scope, message);
|
|
483
|
+
scope.throw_exception(exception);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
let mgr = Manager::new(cfg, NoTls);
|
|
471
488
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
489
|
+
let pool = match Pool::builder(mgr)
|
|
490
|
+
.max_size(max_size)
|
|
491
|
+
.build() {
|
|
492
|
+
Ok(p) => p,
|
|
493
|
+
Err(e) => {
|
|
494
|
+
throw(scope, &format!("t.db.connect(): Failed to build connection pool: {}", e));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
477
498
|
|
|
478
|
-
|
|
499
|
+
DB_POOL.set(pool).ok();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
let db_conn_obj = v8::Object::new(scope);
|
|
503
|
+
|
|
504
|
+
let query_fn = match v8::Function::new(scope, native_db_query) {
|
|
505
|
+
Some(f) => f,
|
|
506
|
+
None => {
|
|
507
|
+
throw(scope, "t.db.connect(): Failed to create query function");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
479
511
|
let query_key = v8_str(scope, "query");
|
|
480
512
|
db_conn_obj.set(scope, query_key.into(), query_fn.into());
|
|
481
|
-
|
|
513
|
+
|
|
482
514
|
retval.set(db_conn_obj.into());
|
|
483
515
|
}
|
|
484
516
|
|
|
485
|
-
fn native_db_query(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
let
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
let
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
517
|
+
fn native_db_query(
|
|
518
|
+
scope: &mut v8::HandleScope,
|
|
519
|
+
args: v8::FunctionCallbackArguments,
|
|
520
|
+
mut retval: v8::ReturnValue,
|
|
521
|
+
) {
|
|
522
|
+
let sql = v8_to_string(scope, args.get(0));
|
|
523
|
+
|
|
524
|
+
// Collect params
|
|
525
|
+
let mut params = Vec::new();
|
|
526
|
+
if args.length() > 1 && args.get(1).is_array() {
|
|
527
|
+
let arr = v8::Local::<v8::Array>::try_from(args.get(1)).unwrap();
|
|
528
|
+
for i in 0..arr.length() {
|
|
529
|
+
if let Some(v) = arr.get_index(scope, i) {
|
|
530
|
+
params.push(v8_to_string(scope, v));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
498
533
|
}
|
|
499
534
|
|
|
535
|
+
// Main async wrapper object
|
|
500
536
|
let obj = v8::Object::new(scope);
|
|
501
|
-
|
|
502
|
-
let
|
|
503
|
-
|
|
504
|
-
|
|
537
|
+
|
|
538
|
+
let async_key = v8_str(scope, "__titanAsync");
|
|
539
|
+
let async_val = v8::Boolean::new(scope, true);
|
|
540
|
+
obj.set(scope, async_key.into(), async_val.into());
|
|
541
|
+
|
|
505
542
|
let type_key = v8_str(scope, "type");
|
|
506
543
|
let type_val = v8_str(scope, "db_query");
|
|
507
544
|
obj.set(scope, type_key.into(), type_val.into());
|
|
508
|
-
|
|
545
|
+
|
|
546
|
+
// Data object
|
|
509
547
|
let data_obj = v8::Object::new(scope);
|
|
510
|
-
|
|
511
|
-
let
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
let
|
|
516
|
-
|
|
517
|
-
|
|
548
|
+
|
|
549
|
+
let conn_key = v8_str(scope, "conn");
|
|
550
|
+
let conn_val = v8_str(scope, "default");
|
|
551
|
+
data_obj.set(scope, conn_key.into(), conn_val.into());
|
|
552
|
+
|
|
553
|
+
let query_key = v8_str(scope, "query");
|
|
554
|
+
let query_val = v8_str(scope, &sql);
|
|
555
|
+
data_obj.set(scope, query_key.into(), query_val.into());
|
|
556
|
+
|
|
557
|
+
// Params array
|
|
558
|
+
let params_arr = v8::Array::new(scope, params.len() as i32);
|
|
559
|
+
|
|
560
|
+
for (i, p) in params.iter().enumerate() {
|
|
561
|
+
let param_val = v8_str(scope, p);
|
|
562
|
+
params_arr.set_index(scope, i as u32, param_val.into());
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let params_key = v8_str(scope, "params");
|
|
566
|
+
data_obj.set(scope, params_key.into(), params_arr.into());
|
|
567
|
+
|
|
518
568
|
let data_key = v8_str(scope, "data");
|
|
519
569
|
obj.set(scope, data_key.into(), data_obj.into());
|
|
520
|
-
|
|
570
|
+
|
|
521
571
|
retval.set(obj.into());
|
|
522
572
|
}
|
|
523
573
|
|
|
@@ -604,15 +654,35 @@ fn parse_async_op(scope: &mut v8::HandleScope, op_val: v8::Local<v8::Value>) ->
|
|
|
604
654
|
}
|
|
605
655
|
Some(super::TitanAsyncOp::Fetch { url, method, body, headers })
|
|
606
656
|
},
|
|
657
|
+
|
|
607
658
|
"db_query" => {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
659
|
+
|
|
660
|
+
let conn_key = v8_str(scope, "conn");
|
|
661
|
+
let conn_val = data_obj.get(scope, conn_key.into())?;
|
|
662
|
+
let conn = v8_to_string(scope, conn_val);
|
|
663
|
+
|
|
664
|
+
let query_key = v8_str(scope, "query");
|
|
665
|
+
let query_val = data_obj.get(scope, query_key.into())?;
|
|
666
|
+
let query = v8_to_string(scope, query_val);
|
|
667
|
+
|
|
668
|
+
let params_key = v8_str(scope, "params");
|
|
669
|
+
let mut params = Vec::new();
|
|
670
|
+
|
|
671
|
+
if let Some(p_val) = data_obj.get(scope, params_key.into()) {
|
|
672
|
+
if p_val.is_array() {
|
|
673
|
+
let arr = v8::Local::<v8::Array>::try_from(p_val).unwrap();
|
|
674
|
+
for i in 0..arr.length() {
|
|
675
|
+
if let Some(v) = arr.get_index(scope, i) {
|
|
676
|
+
params.push(v8_to_string(scope, v));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
Some(super::TitanAsyncOp::DbQuery { conn, query, params })
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
|
|
616
686
|
"fs_read" => {
|
|
617
687
|
let path_key = v8_str(scope, "path");
|
|
618
688
|
let path_obj = data_obj.get(scope, path_key.into())?;
|
|
@@ -810,9 +880,15 @@ fn native_finish_request(scope: &mut v8::HandleScope, mut args: v8::FunctionCall
|
|
|
810
880
|
}
|
|
811
881
|
}
|
|
812
882
|
|
|
813
|
-
pub fn run_async_operation(
|
|
883
|
+
pub fn run_async_operation(
|
|
884
|
+
op: super::TitanAsyncOp,
|
|
885
|
+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = serde_json::Value> + Send>> {
|
|
814
886
|
Box::pin(async move {
|
|
815
887
|
match op {
|
|
888
|
+
|
|
889
|
+
// =========================
|
|
890
|
+
// FETCH
|
|
891
|
+
// =========================
|
|
816
892
|
super::TitanAsyncOp::Fetch {
|
|
817
893
|
url,
|
|
818
894
|
method,
|
|
@@ -820,107 +896,163 @@ pub fn run_async_operation(op: super::TitanAsyncOp) -> std::pin::Pin<Box<dyn std
|
|
|
820
896
|
headers,
|
|
821
897
|
} => {
|
|
822
898
|
let client = get_http_client();
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
899
|
+
|
|
900
|
+
let method = reqwest::Method::from_bytes(method.as_bytes())
|
|
901
|
+
.unwrap_or(reqwest::Method::GET);
|
|
902
|
+
|
|
903
|
+
let mut req = client.request(method, &url);
|
|
904
|
+
|
|
827
905
|
for (k, v) in headers {
|
|
828
906
|
req = req.header(k, v);
|
|
829
907
|
}
|
|
830
|
-
|
|
908
|
+
|
|
831
909
|
if let Some(b) = body {
|
|
832
|
-
|
|
910
|
+
req = req.body(b);
|
|
833
911
|
}
|
|
834
|
-
|
|
912
|
+
|
|
835
913
|
match req.send().await {
|
|
836
914
|
Ok(resp) => {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
915
|
+
let status = resp.status().as_u16();
|
|
916
|
+
let api_headers = resp.headers().clone();
|
|
917
|
+
let text = resp.text().await.unwrap_or_default();
|
|
918
|
+
|
|
919
|
+
let mut h_map = serde_json::Map::new();
|
|
920
|
+
for (k, v) in api_headers.iter() {
|
|
921
|
+
if let Ok(s) = v.to_str() {
|
|
922
|
+
h_map.insert(
|
|
923
|
+
k.as_str().to_string(),
|
|
924
|
+
serde_json::Value::String(s.to_string()),
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
serde_json::json!({
|
|
930
|
+
"_isResponse": true,
|
|
931
|
+
"status": status,
|
|
932
|
+
"body": text,
|
|
933
|
+
"headers": h_map
|
|
934
|
+
})
|
|
935
|
+
}
|
|
936
|
+
Err(e) => serde_json::json!({ "error": e.to_string() }),
|
|
856
937
|
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// =========================
|
|
941
|
+
// DB QUERY
|
|
942
|
+
// =========================
|
|
943
|
+
super::TitanAsyncOp::DbQuery { conn: _, query, params } => {
|
|
944
|
+
|
|
945
|
+
let pool = match DB_POOL.get() {
|
|
946
|
+
Some(p) => p,
|
|
947
|
+
None => {
|
|
948
|
+
return serde_json::json!({
|
|
949
|
+
"error": "DB pool not initialized"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
match pool.get().await {
|
|
955
|
+
Ok(client) => {
|
|
956
|
+
|
|
957
|
+
let stmt = match client.prepare(&query).await {
|
|
958
|
+
Ok(s) => s,
|
|
959
|
+
Err(e) => {
|
|
960
|
+
return serde_json::json!({
|
|
961
|
+
"error": e.to_string()
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
let param_refs: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> =
|
|
967
|
+
params.iter()
|
|
968
|
+
.map(|p| p as &(dyn tokio_postgres::types::ToSql + Sync))
|
|
969
|
+
.collect();
|
|
970
|
+
|
|
971
|
+
match client.query(&stmt, ¶m_refs).await {
|
|
972
|
+
Ok(rows) => {
|
|
973
|
+
|
|
974
|
+
let mut result = Vec::new();
|
|
975
|
+
|
|
976
|
+
for row in rows {
|
|
977
|
+
let mut obj = serde_json::Map::new();
|
|
978
|
+
|
|
979
|
+
for (i, col) in row.columns().iter().enumerate() {
|
|
980
|
+
|
|
981
|
+
let val =
|
|
982
|
+
if let Ok(v) = row.try_get::<_, String>(i) {
|
|
983
|
+
serde_json::Value::String(v)
|
|
984
|
+
} else if let Ok(v) = row.try_get::<_, i64>(i) {
|
|
985
|
+
serde_json::Value::Number(v.into())
|
|
986
|
+
} else if let Ok(v) = row.try_get::<_, i32>(i) {
|
|
987
|
+
serde_json::Value::Number(v.into())
|
|
988
|
+
} else if let Ok(v) = row.try_get::<_, bool>(i) {
|
|
989
|
+
serde_json::Value::Bool(v)
|
|
990
|
+
} else {
|
|
991
|
+
serde_json::Value::Null
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
obj.insert(col.name().to_string(), val);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
result.push(serde_json::Value::Object(obj));
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
serde_json::Value::Array(result)
|
|
1001
|
+
}
|
|
1002
|
+
Err(e) => serde_json::json!({
|
|
1003
|
+
"error": e.to_string()
|
|
1004
|
+
}),
|
|
892
1005
|
}
|
|
893
|
-
} else {
|
|
894
|
-
Err("Pool not initialized".to_string())
|
|
895
1006
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
Ok(Ok(v)) => v,
|
|
900
|
-
Ok(Err(s)) => serde_json::json!({ "error": s }),
|
|
901
|
-
Err(e) => serde_json::json!({ "error": e.to_string() })
|
|
1007
|
+
Err(e) => serde_json::json!({
|
|
1008
|
+
"error": e.to_string()
|
|
1009
|
+
}),
|
|
902
1010
|
}
|
|
903
|
-
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// =========================
|
|
1014
|
+
// FS READ
|
|
1015
|
+
// =========================
|
|
904
1016
|
super::TitanAsyncOp::FsRead { path } => {
|
|
905
|
-
|
|
1017
|
+
|
|
1018
|
+
let root = super::PROJECT_ROOT
|
|
1019
|
+
.get()
|
|
1020
|
+
.cloned()
|
|
1021
|
+
.unwrap_or(std::path::PathBuf::from("."));
|
|
1022
|
+
|
|
906
1023
|
let target = root.join(&path);
|
|
907
|
-
|
|
908
|
-
let safe = target
|
|
909
|
-
|
|
1024
|
+
|
|
1025
|
+
let safe = target
|
|
1026
|
+
.canonicalize()
|
|
1027
|
+
.map(|p| {
|
|
1028
|
+
p.starts_with(
|
|
1029
|
+
root.canonicalize()
|
|
1030
|
+
.unwrap_or(root.clone())
|
|
1031
|
+
)
|
|
1032
|
+
})
|
|
1033
|
+
.unwrap_or(false);
|
|
1034
|
+
|
|
910
1035
|
if safe {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1036
|
+
match tokio::fs::read_to_string(target).await {
|
|
1037
|
+
Ok(c) => serde_json::json!({ "data": c }),
|
|
1038
|
+
Err(e) => serde_json::json!({ "error": e.to_string() }),
|
|
1039
|
+
}
|
|
915
1040
|
} else {
|
|
916
|
-
|
|
1041
|
+
serde_json::json!({ "error": "Access denied" })
|
|
917
1042
|
}
|
|
918
|
-
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// =========================
|
|
1046
|
+
// BATCH
|
|
1047
|
+
// =========================
|
|
919
1048
|
super::TitanAsyncOp::Batch(ops) => {
|
|
1049
|
+
|
|
920
1050
|
let mut res = Vec::new();
|
|
1051
|
+
|
|
921
1052
|
for op in ops {
|
|
922
1053
|
res.push(run_async_operation(op).await);
|
|
923
1054
|
}
|
|
1055
|
+
|
|
924
1056
|
serde_json::Value::Array(res)
|
|
925
1057
|
}
|
|
926
1058
|
}
|