From 0be9fb3074daedfc75be22c574e1484d9fb5edb7 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:28:20 +0800 Subject: [PATCH] feat: remove deprecated codes & update wasm binding (#530) * feat: remove deprecated codes & update wasm binding * chore: style & clippy --- Cargo.lock | 538 +--------- Cargo.toml | 29 +- apps/cloud/Cargo.toml | 16 +- apps/keck/src/server/api/doc.rs | 2 +- libs/cloud-database/Cargo.toml | 36 - libs/cloud-database/migration/Cargo.toml | 17 - libs/cloud-database/migration/README.md | 41 - libs/cloud-database/migration/src/lib.rs | 25 - .../src/m20220101_000001_create_user_table.rs | 45 - ...0230101_000002_create_google_user_table.rs | 48 - ...20230101_000003_create_workspaces_table.rs | 41 - ...0230101_000004_create_permissions_table.rs | 92 -- ...0230217_000001_update_permissions_table.rs | 40 - libs/cloud-database/migration/src/main.rs | 7 - libs/cloud-database/src/database.rs | 980 ------------------ .../src/entities/google_users.rs | 33 - libs/cloud-database/src/entities/mod.rs | 8 - .../src/entities/permissions.rs | 51 - libs/cloud-database/src/entities/prelude.rs | 6 - libs/cloud-database/src/entities/users.rs | 39 - .../cloud-database/src/entities/workspaces.rs | 27 - libs/cloud-database/src/lib.rs | 22 - libs/cloud-database/src/model.rs | 370 ------- libs/cloud-infra/Cargo.toml | 45 - libs/cloud-infra/assets/404-dev.html | 49 - libs/cloud-infra/assets/404.html | 47 - libs/cloud-infra/assets/invite.html | 278 ----- libs/cloud-infra/src/auth.rs | 240 ----- libs/cloud-infra/src/constants.rs | 11 - libs/cloud-infra/src/hosting/api_doc.rs | 32 - libs/cloud-infra/src/hosting/files.rs | 33 - libs/cloud-infra/src/hosting/mod.rs | 18 - libs/cloud-infra/src/hosting/pages.rs | 63 -- libs/cloud-infra/src/lib.rs | 11 - libs/cloud-infra/src/mail.rs | 261 ----- libs/jwst-binding/jwst-ffi/.gitignore | 2 - libs/jwst-binding/jwst-ffi/Cargo.toml | 19 - libs/jwst-binding/jwst-ffi/binding.h | 105 -- libs/jwst-binding/jwst-ffi/build.rs | 47 - libs/jwst-binding/jwst-ffi/src/lib.rs | 340 ------ libs/jwst-binding/jwst-py/.gitignore | 73 -- libs/jwst-binding/jwst-py/Cargo.toml | 17 - libs/jwst-binding/jwst-py/pyproject.toml | 14 - libs/jwst-binding/jwst-py/src/lib.rs | 85 -- .../jwst-py/tests/test_workspace.py | 19 - libs/jwst-binding/jwst-swift/Cargo.toml | 4 +- libs/jwst-binding/jwst-wasm/Cargo.toml | 13 +- libs/jwst-binding/jwst-wasm/src/lib.rs | 40 +- libs/jwst-codec/Cargo.toml | 50 +- libs/jwst-codec/src/doc/types/array.rs | 4 +- libs/jwst-core/Cargo.toml | 1 - libs/jwst-core/src/workspaces/observe.rs | 2 +- libs/jwst-core/src/workspaces/sync.rs | 12 +- libs/jwst-core/src/workspaces/workspace.rs | 3 +- libs/jwst-logger/Cargo.toml | 4 +- libs/jwst-rpc/Cargo.toml | 6 +- libs/jwst-rpc/src/context.rs | 2 +- libs/jwst-rpc/src/handler.rs | 2 +- libs/jwst-storage/Cargo.toml | 4 +- libs/jwst/Cargo.toml | 53 - libs/jwst/build.rs | 5 - libs/jwst/fixtures/test_multi_layer.bin | Bin 2520 -> 0 bytes libs/jwst/fixtures/test_shared_page.bin | Bin 12068 -> 0 bytes libs/jwst/src/block/convert.rs | 292 ------ libs/jwst/src/block/mod.rs | 680 ------------ libs/jwst/src/constants.rs | 36 - libs/jwst/src/history/mod.rs | 5 - libs/jwst/src/history/raw.rs | 182 ---- libs/jwst/src/history/record.rs | 86 -- libs/jwst/src/lib.rs | 44 - libs/jwst/src/space/convert.rs | 165 --- libs/jwst/src/space/mod.rs | 345 ------ libs/jwst/src/space/transaction.rs | 59 -- libs/jwst/src/types/blob.rs | 49 - libs/jwst/src/types/doc.rs | 26 - libs/jwst/src/types/error.rs | 29 - libs/jwst/src/types/mod.rs | 10 - libs/jwst/src/utils.rs | 35 - libs/jwst/src/workspace/block_observer.rs | 176 ---- libs/jwst/src/workspace/metadata/meta.rs | 87 -- libs/jwst/src/workspace/metadata/mod.rs | 7 - libs/jwst/src/workspace/metadata/pages.rs | 199 ---- libs/jwst/src/workspace/mod.rs | 16 - libs/jwst/src/workspace/observe.rs | 67 -- .../src/workspace/plugins/indexing/indexer.rs | 388 ------- .../src/workspace/plugins/indexing/mod.rs | 9 - .../workspace/plugins/indexing/register.rs | 113 -- .../workspace/plugins/indexing/tokenizer.rs | 7 - libs/jwst/src/workspace/plugins/mod.rs | 81 -- libs/jwst/src/workspace/plugins/plugin.rs | 63 -- libs/jwst/src/workspace/sync.rs | 193 ---- libs/jwst/src/workspace/transaction.rs | 126 --- libs/jwst/src/workspace/workspace.rs | 574 ---------- 93 files changed, 111 insertions(+), 8565 deletions(-) delete mode 100644 libs/cloud-database/Cargo.toml delete mode 100644 libs/cloud-database/migration/Cargo.toml delete mode 100644 libs/cloud-database/migration/README.md delete mode 100644 libs/cloud-database/migration/src/lib.rs delete mode 100644 libs/cloud-database/migration/src/m20220101_000001_create_user_table.rs delete mode 100644 libs/cloud-database/migration/src/m20230101_000002_create_google_user_table.rs delete mode 100644 libs/cloud-database/migration/src/m20230101_000003_create_workspaces_table.rs delete mode 100644 libs/cloud-database/migration/src/m20230101_000004_create_permissions_table.rs delete mode 100644 libs/cloud-database/migration/src/m20230217_000001_update_permissions_table.rs delete mode 100644 libs/cloud-database/migration/src/main.rs delete mode 100644 libs/cloud-database/src/database.rs delete mode 100644 libs/cloud-database/src/entities/google_users.rs delete mode 100644 libs/cloud-database/src/entities/mod.rs delete mode 100644 libs/cloud-database/src/entities/permissions.rs delete mode 100644 libs/cloud-database/src/entities/prelude.rs delete mode 100644 libs/cloud-database/src/entities/users.rs delete mode 100644 libs/cloud-database/src/entities/workspaces.rs delete mode 100644 libs/cloud-database/src/lib.rs delete mode 100644 libs/cloud-database/src/model.rs delete mode 100644 libs/cloud-infra/Cargo.toml delete mode 100644 libs/cloud-infra/assets/404-dev.html delete mode 100644 libs/cloud-infra/assets/404.html delete mode 100644 libs/cloud-infra/assets/invite.html delete mode 100644 libs/cloud-infra/src/auth.rs delete mode 100644 libs/cloud-infra/src/constants.rs delete mode 100644 libs/cloud-infra/src/hosting/api_doc.rs delete mode 100644 libs/cloud-infra/src/hosting/files.rs delete mode 100644 libs/cloud-infra/src/hosting/mod.rs delete mode 100644 libs/cloud-infra/src/hosting/pages.rs delete mode 100644 libs/cloud-infra/src/lib.rs delete mode 100644 libs/cloud-infra/src/mail.rs delete mode 100644 libs/jwst-binding/jwst-ffi/.gitignore delete mode 100644 libs/jwst-binding/jwst-ffi/Cargo.toml delete mode 100644 libs/jwst-binding/jwst-ffi/binding.h delete mode 100644 libs/jwst-binding/jwst-ffi/build.rs delete mode 100644 libs/jwst-binding/jwst-ffi/src/lib.rs delete mode 100644 libs/jwst-binding/jwst-py/.gitignore delete mode 100644 libs/jwst-binding/jwst-py/Cargo.toml delete mode 100644 libs/jwst-binding/jwst-py/pyproject.toml delete mode 100644 libs/jwst-binding/jwst-py/src/lib.rs delete mode 100644 libs/jwst-binding/jwst-py/tests/test_workspace.py delete mode 100644 libs/jwst/Cargo.toml delete mode 100644 libs/jwst/build.rs delete mode 100644 libs/jwst/fixtures/test_multi_layer.bin delete mode 100644 libs/jwst/fixtures/test_shared_page.bin delete mode 100644 libs/jwst/src/block/convert.rs delete mode 100644 libs/jwst/src/block/mod.rs delete mode 100644 libs/jwst/src/constants.rs delete mode 100644 libs/jwst/src/history/mod.rs delete mode 100644 libs/jwst/src/history/raw.rs delete mode 100644 libs/jwst/src/history/record.rs delete mode 100644 libs/jwst/src/lib.rs delete mode 100644 libs/jwst/src/space/convert.rs delete mode 100644 libs/jwst/src/space/mod.rs delete mode 100644 libs/jwst/src/space/transaction.rs delete mode 100644 libs/jwst/src/types/blob.rs delete mode 100644 libs/jwst/src/types/doc.rs delete mode 100644 libs/jwst/src/types/error.rs delete mode 100644 libs/jwst/src/types/mod.rs delete mode 100644 libs/jwst/src/utils.rs delete mode 100644 libs/jwst/src/workspace/block_observer.rs delete mode 100644 libs/jwst/src/workspace/metadata/meta.rs delete mode 100644 libs/jwst/src/workspace/metadata/mod.rs delete mode 100644 libs/jwst/src/workspace/metadata/pages.rs delete mode 100644 libs/jwst/src/workspace/mod.rs delete mode 100644 libs/jwst/src/workspace/observe.rs delete mode 100644 libs/jwst/src/workspace/plugins/indexing/indexer.rs delete mode 100644 libs/jwst/src/workspace/plugins/indexing/mod.rs delete mode 100644 libs/jwst/src/workspace/plugins/indexing/register.rs delete mode 100644 libs/jwst/src/workspace/plugins/indexing/tokenizer.rs delete mode 100644 libs/jwst/src/workspace/plugins/mod.rs delete mode 100644 libs/jwst/src/workspace/plugins/plugin.rs delete mode 100644 libs/jwst/src/workspace/sync.rs delete mode 100644 libs/jwst/src/workspace/transaction.rs delete mode 100644 libs/jwst/src/workspace/workspace.rs diff --git a/Cargo.lock b/Cargo.lock index 858619a83..3d5a1b830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,15 +138,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - [[package]] name = "aho-corasick" version = "1.0.4" @@ -595,15 +586,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitpacking" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c7d2ac73c167c06af4a5f37e6e59d84148d57ccbe4480b76f0273eefea82d7" -dependencies = [ - "crunchy", -] - [[package]] name = "bitvec" version = "1.0.1" @@ -732,17 +714,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cang-jie" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1238ed330d627f47a2309023a7425a4c73cd586ccbda77151e18ece8f9495b92" -dependencies = [ - "jieba-rs", - "log", - "tantivy", -] - [[package]] name = "cast" version = "0.3.0" @@ -770,21 +741,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "cedarwood" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d910bedd62c24733263d0bed247460853c9d22e8956bd4cd964302095e04e90" -dependencies = [ - "smallvec", -] - -[[package]] -name = "census" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fafee10a5dd1cffcb5cc560e0d0df8803d7355a2b12272e3557dee57314cb6e" - [[package]] name = "cfg-if" version = "0.1.10" @@ -921,15 +877,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "memchr", -] - [[package]] name = "console" version = "0.15.7" @@ -943,6 +890,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -971,15 +928,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -1459,18 +1407,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] -name = "dyn-clone" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" - [[package]] name = "ecdsa" version = "0.14.8" @@ -1529,26 +1465,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "enum-iterator" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.33", - "syn 2.0.29", -] - [[package]] name = "env_logger" version = "0.10.0" @@ -1619,37 +1535,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "fail" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5e43d0f78a42ad591453aedb1d7ae631ce7ee445c7643691055a9ed8d3b01c" -dependencies = [ - "log", - "once_cell", - "rand 0.8.5", -] - -[[package]] -name = "fastdivide" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25c7df09945d65ea8d70b3321547ed414bbc540aad5bac6883d021b970f35b04" - -[[package]] -name = "fastfield_codecs" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374a3a53c1bd5fb31b10084229290eafb0a05f260ec90f1f726afffda4877a8a" -dependencies = [ - "fastdivide", - "itertools", - "log", - "ownedbytes", - "tantivy-bitpacker", - "tantivy-common", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1760,16 +1645,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "funty" version = "2.0.0" @@ -1882,15 +1757,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generator" version = "0.7.5" @@ -1940,18 +1806,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2 1.0.66", - "quote 1.0.33", - "syn 1.0.109", -] - [[package]] name = "ghash" version = "0.5.0" @@ -1972,19 +1826,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "git2" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "glob" version = "0.3.1" @@ -2164,12 +2005,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - [[package]] name = "http" version = "0.2.9" @@ -2368,9 +2203,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -2424,21 +2256,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "jieba-rs" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f0c1347cd3ac8d7c6e3a2dc33ac496d365cf09fc0831aa61111e1a6738983e" -dependencies = [ - "cedarwood", - "fxhash", - "hashbrown 0.14.0", - "lazy_static", - "phf", - "phf_codegen", - "regex", -] - [[package]] name = "jni-sys" version = "0.3.0" @@ -2472,34 +2289,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jwst" -version = "0.1.1" -dependencies = [ - "assert-json-diff", - "async-trait", - "base64 0.21.3", - "bytes", - "cang-jie", - "chrono", - "convert_case", - "futures", - "jwst-codec", - "lib0", - "nanoid", - "schemars", - "serde", - "serde_json", - "tantivy", - "thiserror", - "tokio", - "tracing", - "type-map", - "utoipa", - "vergen", - "yrs", -] - [[package]] name = "jwst-codec" version = "0.1.0" @@ -2511,7 +2300,7 @@ dependencies = [ "criterion", "jwst-logger", "lib0", - "loom 0.6.1", + "loom", "nanoid", "nom", "ordered-float", @@ -2553,7 +2342,6 @@ dependencies = [ "serde", "serde_json", "thiserror", - "tokio", "tracing", ] @@ -2680,6 +2468,18 @@ dependencies = [ "swift-bridge-build", ] +[[package]] +name = "jwst-wasm" +version = "0.1.0" +dependencies = [ + "cfg-if 1.0.0", + "console_error_panic_hook", + "getrandom 0.2.10", + "js-sys", + "jwst-core", + "wasm-bindgen", +] + [[package]] name = "keck" version = "0.1.0" @@ -2727,12 +2527,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" -[[package]] -name = "levenshtein_automata" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" - [[package]] name = "lib0" version = "0.16.5" @@ -2749,18 +2543,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libgit2-sys" -version = "0.14.2+1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - [[package]] name = "libm" version = "0.2.7" @@ -2798,18 +2580,6 @@ dependencies = [ "glob", ] -[[package]] -name = "libz-sys" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -2832,20 +2602,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if 1.0.0", - "generator", - "pin-utils", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - [[package]] name = "loom" version = "0.6.1" @@ -2861,21 +2617,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "lru" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" -dependencies = [ - "hashbrown 0.12.3", -] - -[[package]] -name = "lz4_flex" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8cbbb2831780bc3b9c15a41f5b49222ef756b6730a95f3decfdd15903eb5a3" - [[package]] name = "mach2" version = "0.4.1" @@ -2909,31 +2650,12 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -3005,15 +2727,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "murmurhash32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d736ff882f0e85fe9689fb23db229616c4c00aee2b3ac282f666d8f20eb25d4a" -dependencies = [ - "byteorder", -] - [[package]] name = "nanoid" version = "0.4.0" @@ -3186,15 +2899,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "oneshot" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc22d22931513428ea6cc089e942d38600e3d00976eef8c86de6b8a3aadec6eb" -dependencies = [ - "loom 0.5.6", -] - [[package]] name = "oorandom" version = "11.1.3" @@ -3296,15 +3000,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "ownedbytes" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e957eaa64a299f39755416e5b3128c505e9d63a91d0453771ad2ccd3907f8db" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "p256" version = "0.11.1" @@ -3418,16 +3113,6 @@ dependencies = [ "phf_shared", ] -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - [[package]] name = "phf_generator" version = "0.11.2" @@ -3932,7 +3617,7 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ - "aho-corasick 1.0.4", + "aho-corasick", "memchr", "regex-automata 0.3.7", "regex-syntax 0.7.5", @@ -3953,7 +3638,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ - "aho-corasick 1.0.4", + "aho-corasick", "memchr", "regex-syntax 0.7.5", ] @@ -4218,16 +3903,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rust-stemmers" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" -dependencies = [ - "serde", - "serde_derive", -] - [[package]] name = "rust_decimal" version = "1.32.0" @@ -4389,30 +4064,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.33", - "serde_derive_internals", - "syn 1.0.109", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -4685,17 +4336,6 @@ dependencies = [ "syn 2.0.29", ] -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.33", - "syn 1.0.109", -] - [[package]] name = "serde_json" version = "1.0.105" @@ -5131,12 +4771,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -5317,96 +4951,6 @@ dependencies = [ "unicode-xid 0.2.4", ] -[[package]] -name = "tantivy" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb26a6b22c84d8be41d99a14016d6f04d30d8d31a2ea411a8ab553af5cc490d" -dependencies = [ - "aho-corasick 0.7.20", - "arc-swap", - "async-trait", - "base64 0.13.1", - "bitpacking", - "byteorder", - "census", - "crc32fast", - "crossbeam-channel", - "downcast-rs", - "fail", - "fastdivide", - "fastfield_codecs", - "fs2", - "htmlescape", - "itertools", - "levenshtein_automata", - "log", - "lru", - "lz4_flex", - "measure_time", - "memmap2", - "murmurhash32", - "num_cpus", - "once_cell", - "oneshot", - "ownedbytes", - "rayon", - "regex", - "rust-stemmers", - "rustc-hash", - "serde", - "serde_json", - "smallvec", - "stable_deref_trait", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-fst", - "tantivy-query-grammar", - "tempfile", - "thiserror", - "time 0.3.28", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-bitpacker" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e71a0c95b82d4292b097a09b989a6380d28c3a86800c841a2d03bae1fc8b9fa6" - -[[package]] -name = "tantivy-common" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14fef4182bb60df9a4b92cd8ecab39ba2e50a05542934af17eef1f49660705cb" -dependencies = [ - "byteorder", - "ownedbytes", -] - -[[package]] -name = "tantivy-fst" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3c506b1a8443a3a65352df6382a1fb6a7afe1a02e871cee0d25e2c3d5f3944" -dependencies = [ - "byteorder", - "regex-syntax 0.6.29", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343e3ada4c1c480953f6960f8a21ce9c76611480ffdd4f4e230fdddce0fc5331" -dependencies = [ - "combine", - "once_cell", - "regex", -] - [[package]] name = "tap" version = "1.0.1" @@ -5811,15 +5355,6 @@ dependencies = [ "webrtc-util", ] -[[package]] -name = "type-map" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" -dependencies = [ - "rustc-hash", -] - [[package]] name = "typenum" version = "1.16.0" @@ -5925,12 +5460,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8-ranges" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" - [[package]] name = "utf8parse" version = "0.2.1" @@ -5999,23 +5528,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vergen" -version = "7.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21b881cd6636ece9735721cf03c1fe1e774fe258683d084bb2812ab67435749" -dependencies = [ - "anyhow", - "cfg-if 1.0.0", - "enum-iterator", - "getset", - "git2", - "rustc_version", - "rustversion", - "thiserror", - "time 0.3.28", -] - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 782aad01e..d763094ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,19 @@ [workspace] members = [ - "apps/doc_merger", - "apps/keck", - "libs/jwst", - # "libs/jwst-binding/jwst-ffi", - "libs/jwst-binding/jwst-jni", - # "libs/jwst-binding/jwst-py", - "libs/jwst-binding/jwst-swift", - "libs/jwst-binding/jwst-swift/jwst-swift-integrate", - # "libs/jwst-binding/jwst-wasm", - "libs/jwst-codec", - "libs/jwst-codec-utils", - "libs/jwst-core", - "libs/jwst-logger", - "libs/jwst-rpc", - "libs/jwst-storage", - "libs/jwst-storage/src/migration", + "apps/doc_merger", + "apps/keck", + "libs/jwst-binding/jwst-jni", + "libs/jwst-binding/jwst-swift", + "libs/jwst-binding/jwst-swift/jwst-swift-integrate", + "libs/jwst-binding/jwst-wasm", + "libs/jwst-codec", + "libs/jwst-codec-utils", + "libs/jwst-core", + "libs/jwst-logger", + "libs/jwst-rpc", + "libs/jwst-storage", + "libs/jwst-storage/src/migration", ] resolver = "2" diff --git a/apps/cloud/Cargo.toml b/apps/cloud/Cargo.toml index 8a3630bec..0b99fc6f7 100644 --- a/apps/cloud/Cargo.toml +++ b/apps/cloud/Cargo.toml @@ -26,16 +26,16 @@ serde = { version = "1.0.155", features = ["derive"] } serde_json = "1.0.94" tempfile = "3.4.0" tokio = { version = "1.26.0", features = [ - "macros", - "rt-multi-thread", - "signal", + "macros", + "rt-multi-thread", + "signal", ] } tower-http = { version = "0.4.0", features = [ - "auth", - "cors", - "propagate-header", - "request-id", - "trace", + "auth", + "cors", + "propagate-header", + "request-id", + "trace", ] } utoipa = { version = "3.1.0", features = ["axum_extras"] } form_urlencoded = "1.1.0" diff --git a/apps/keck/src/server/api/doc.rs b/apps/keck/src/server/api/doc.rs index ada123c65..f6f4d4d37 100644 --- a/apps/keck/src/server/api/doc.rs +++ b/apps/keck/src/server/api/doc.rs @@ -70,7 +70,7 @@ pub fn doc_apis(router: Router) -> Router { #[cfg(feature = "schema")] { let mut openapi = ApiDoc::openapi(); - openapi.info.description = Some(vec![README, DISTINCTIVE_FEATURES].join("\n")); + openapi.info.description = Some([README, DISTINCTIVE_FEATURES].join("\n")); let name = "jwst"; if cfg!(debug_assertions) || std::env::var("JWST_DEV").is_ok() { diff --git a/libs/cloud-database/Cargo.toml b/libs/cloud-database/Cargo.toml deleted file mode 100644 index 5c16ec3d6..000000000 --- a/libs/cloud-database/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "cloud-database" -version = "0.1.0" -edition = "2021" - -[features] -default = ["sqlite"] -server = [] -mysql = ["sqlx/mysql", "sea-orm/sqlx-mysql"] -postgres = ["sqlx/postgres", "sea-orm/sqlx-postgres"] -sqlite = ["sqlx/sqlite", "sea-orm/sqlx-sqlite"] - -[dependencies] -async-trait = "0.1.68" -chrono = { version = "0.4.24", features = ["serde"] } -nanoid = "0.4.0" -schemars = "0.8.12" -serde = { version = "1.0.160", features = ["derive"] } -serde_repr = "0.1.12" -sqlx = { version = "0.7.1", features = [ - "chrono", - "macros", - "runtime-tokio-rustls", -] } -sea-orm = { version = "0.12.2", features = ["runtime-tokio-rustls", "macros"] } -sea-orm-migration = { version = "0.12.2", default-features = false } -tokio = { version = "1.27.0", features = ["fs", "macros", "sync"] } -yrs = "0.16.5" - -# ======= workspace dependencies ======= -affine-cloud-migration = { path = "./migration" } -jwst = { workspace = true } -jwst-logger = { workspace = true } - -[dev-dependencies] -anyhow = "1.0.69" diff --git a/libs/cloud-database/migration/Cargo.toml b/libs/cloud-database/migration/Cargo.toml deleted file mode 100644 index 8f2b47042..000000000 --- a/libs/cloud-database/migration/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "affine-cloud-migration" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "affine_cloud_migration" -path = "src/lib.rs" - -[dependencies] -tokio = { version = "1.27.0", features = ["macros"] } - -[dependencies.sea-orm-migration] -version = "0.12.2" -default-features = false -features = ["runtime-tokio-rustls", "sqlx-postgres"] diff --git a/libs/cloud-database/migration/README.md b/libs/cloud-database/migration/README.md deleted file mode 100644 index b3ea53eb4..000000000 --- a/libs/cloud-database/migration/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Running Migrator CLI - -- Generate a new migration file - ```sh - cargo run -- migrate generate MIGRATION_NAME - ``` -- Apply all pending migrations - ```sh - cargo run - ``` - ```sh - cargo run -- up - ``` -- Apply first 10 pending migrations - ```sh - cargo run -- up -n 10 - ``` -- Rollback last applied migrations - ```sh - cargo run -- down - ``` -- Rollback last 10 applied migrations - ```sh - cargo run -- down -n 10 - ``` -- Drop all tables from the database, then reapply all migrations - ```sh - cargo run -- fresh - ``` -- Rollback all applied migrations, then reapply all migrations - ```sh - cargo run -- refresh - ``` -- Rollback all applied migrations - ```sh - cargo run -- reset - ``` -- Check the status of all migrations - ```sh - cargo run -- status - ``` diff --git a/libs/cloud-database/migration/src/lib.rs b/libs/cloud-database/migration/src/lib.rs deleted file mode 100644 index 0ea0c8591..000000000 --- a/libs/cloud-database/migration/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![forbid(unsafe_code)] -pub use sea_orm_migration::prelude::*; - -mod m20220101_000001_create_user_table; -mod m20230101_000002_create_google_user_table; -mod m20230101_000003_create_workspaces_table; -mod m20230101_000004_create_permissions_table; -mod m20230217_000001_update_permissions_table; - -use async_trait::async_trait; - -pub struct Migrator; - -#[async_trait::async_trait] -impl MigratorTrait for Migrator { - fn migrations() -> Vec> { - vec![ - Box::new(m20220101_000001_create_user_table::Migration), - Box::new(m20230101_000002_create_google_user_table::Migration), - Box::new(m20230101_000003_create_workspaces_table::Migration), - Box::new(m20230101_000004_create_permissions_table::Migration), - Box::new(m20230217_000001_update_permissions_table::Migration), - ] - } -} diff --git a/libs/cloud-database/migration/src/m20220101_000001_create_user_table.rs b/libs/cloud-database/migration/src/m20220101_000001_create_user_table.rs deleted file mode 100644 index 45b8579d3..000000000 --- a/libs/cloud-database/migration/src/m20220101_000001_create_user_table.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(Users::Table) - .if_not_exists() - .col(ColumnDef::new(Users::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Users::Name).string().not_null()) - .col(ColumnDef::new(Users::Email).string().not_null().unique_key()) - .col(ColumnDef::new(Users::AvatarUrl).string()) - .col(ColumnDef::new(Users::TokenNonce).small_integer().default(0)) - .col(ColumnDef::new(Users::Password).string()) - .col( - ColumnDef::new(Users::CreatedAt) - .timestamp_with_time_zone() - .default(Expr::current_timestamp()), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(Users::Table).to_owned()).await - } -} - -#[derive(Iden)] -pub enum Users { - Table, - Id, // STRING PRIMARY KEY, - Name, // TEXT NOT NULL, - Email, // TEXT NOT NULL Unique, - AvatarUrl, // TEXT, - TokenNonce, // SMALLINT DEFAULT 0, - Password, // TEXT, - CreatedAt, // TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -} diff --git a/libs/cloud-database/migration/src/m20230101_000002_create_google_user_table.rs b/libs/cloud-database/migration/src/m20230101_000002_create_google_user_table.rs deleted file mode 100644 index 3554d9a5e..000000000 --- a/libs/cloud-database/migration/src/m20230101_000002_create_google_user_table.rs +++ /dev/null @@ -1,48 +0,0 @@ -use sea_orm_migration::prelude::*; - -use super::m20220101_000001_create_user_table::Users; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(GoogleUsers::Table) - .if_not_exists() - .col(ColumnDef::new(GoogleUsers::Id).string().not_null().primary_key()) - .col(ColumnDef::new(GoogleUsers::UserId).string().not_null()) - .col(ColumnDef::new(GoogleUsers::GoogleId).string().not_null().unique_key()) - .foreign_key( - ForeignKey::create() - .name("google_users_user_id_fkey") - .from(GoogleUsers::Table, GoogleUsers::UserId) - .to(Users::Table, Users::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_foreign_key(ForeignKey::drop().name("google_users_user_id_fkey").to_owned()) - .await?; - manager - .drop_table(Table::drop().table(GoogleUsers::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -enum GoogleUsers { - Table, - Id, // STRING PRIMARY KEY, - UserId, // INTEGER REFERENCES users(id), - GoogleId, // TEXT NOT NULL UNIQUE (google_id) -} diff --git a/libs/cloud-database/migration/src/m20230101_000003_create_workspaces_table.rs b/libs/cloud-database/migration/src/m20230101_000003_create_workspaces_table.rs deleted file mode 100644 index 3b066bc70..000000000 --- a/libs/cloud-database/migration/src/m20230101_000003_create_workspaces_table.rs +++ /dev/null @@ -1,41 +0,0 @@ -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(Workspaces::Table) - .if_not_exists() - .col(ColumnDef::new(Workspaces::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Workspaces::Public).boolean().not_null()) - .col(ColumnDef::new(Workspaces::Type).small_integer().not_null()) - .col( - ColumnDef::new(Workspaces::CreatedAt) - .timestamp_with_time_zone() - .default(Expr::current_timestamp()), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Workspaces::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum Workspaces { - Table, - Id, // STRING PRIMARY KEY, - Public, // BOOL NOT NULL, - Type, // SMALLINT NOT NULL, - CreatedAt, // TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -} diff --git a/libs/cloud-database/migration/src/m20230101_000004_create_permissions_table.rs b/libs/cloud-database/migration/src/m20230101_000004_create_permissions_table.rs deleted file mode 100644 index 447bd4b72..000000000 --- a/libs/cloud-database/migration/src/m20230101_000004_create_permissions_table.rs +++ /dev/null @@ -1,92 +0,0 @@ -use sea_orm_migration::prelude::*; - -use super::m20230101_000003_create_workspaces_table::Workspaces; -use crate::m20220101_000001_create_user_table::Users; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(Permissions::Table) - .if_not_exists() - .col(ColumnDef::new(Permissions::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Permissions::WorkspaceId).string().not_null()) - .col(ColumnDef::new(Permissions::UserId).string()) - .col(ColumnDef::new(Permissions::UserEmail).text()) - .col(ColumnDef::new(Permissions::Type).small_integer().not_null()) - .col( - ColumnDef::new(Permissions::Accepted) - .boolean() - .not_null() - .default(false), - ) - .col( - ColumnDef::new(Permissions::CreatedAt) - .timestamp_with_time_zone() - .default(Expr::current_timestamp()), - ) - .foreign_key( - ForeignKey::create() - .name("permissions_workspace_id_fkey") - .from(Permissions::Table, Permissions::WorkspaceId) - .to(Workspaces::Table, Workspaces::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade), - ) - .foreign_key( - ForeignKey::create() - .name("permissions_user_id_fkey") - .from(Permissions::Table, Permissions::UserId) - .to(Users::Table, Users::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_foreign_key(ForeignKey::drop().name("permissions_workspace_id_fkey").to_owned()) - .await?; - manager - .drop_foreign_key(ForeignKey::drop().name("permissions_user_id_fkey").to_owned()) - .await?; - manager - .drop_index(Index::drop().name("permissions_workspace_id_user_id_unique").to_owned()) - .await?; - manager - .drop_index( - Index::drop() - .name("permissions_workspace_id_user_email_unique") - .to_owned(), - ) - .await?; - manager - .drop_table(Table::drop().table(Permissions::Table).to_owned()) - .await - } -} - -/// Learn more at https://docs.rs/sea-query#iden -#[derive(Iden)] -pub enum Permissions { - Table, - Id, // STRING PRIMARY KEY, - WorkspaceId, // CHAR(36), - UserId, // INTEGER, - UserEmail, // TEXT, - Type, // SMALLINT NOT NULL, - Accepted, // BOOL DEFAULT False, - CreatedAt, /* TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - * FOREIGN KEY(workspace_id) REFERENCES workspaces(id), - * FOREIGN KEY(user_id) REFERENCES users(id), - * UNIQUE (workspace_id, user_id), - * UNIQUE (workspace_id, user_email) */ -} diff --git a/libs/cloud-database/migration/src/m20230217_000001_update_permissions_table.rs b/libs/cloud-database/migration/src/m20230217_000001_update_permissions_table.rs deleted file mode 100644 index c497c5bc4..000000000 --- a/libs/cloud-database/migration/src/m20230217_000001_update_permissions_table.rs +++ /dev/null @@ -1,40 +0,0 @@ -use sea_orm_migration::{prelude::*, sea_orm::query::*}; - -use crate::{m20220101_000001_create_user_table::Users, m20230101_000004_create_permissions_table::Permissions}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let db = manager.get_connection(); - let builder = db.get_database_backend(); - - let trx = db.begin().await?; - - let stmt = Query::select() - .column(Users::Id) - .column(Users::Email) - .from(Users::Table) - .to_owned(); - let rows = trx.query_all(builder.build(&stmt)).await?; - - for row in rows.into_iter() { - let user_id = row.try_get::("", "id").unwrap(); - let user_email = row.try_get::("", "email").unwrap(); - - let stmt = Query::update() - .table(Permissions::Table) - .values(vec![(Permissions::UserId, user_id.into())]) - .cond_where(Cond::all().add(Expr::col(Permissions::UserEmail).eq(user_email))) - .to_owned(); - - trx.execute(builder.build(&stmt)).await?; - } - - trx.commit().await?; - - Ok(()) - } -} diff --git a/libs/cloud-database/migration/src/main.rs b/libs/cloud-database/migration/src/main.rs deleted file mode 100644 index b01a9d531..000000000 --- a/libs/cloud-database/migration/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![forbid(unsafe_code)] -use sea_orm_migration::prelude::*; - -#[tokio::main] -async fn main() { - cli::run_cli(affine_cloud_migration::Migrator).await; -} diff --git a/libs/cloud-database/src/database.rs b/libs/cloud-database/src/database.rs deleted file mode 100644 index faab0012d..000000000 --- a/libs/cloud-database/src/database.rs +++ /dev/null @@ -1,980 +0,0 @@ -use affine_cloud_migration::{Expr, JoinType, Migrator, MigratorTrait, Query}; -use jwst_logger::{info, instrument, tracing}; -use nanoid::nanoid; -use sea_orm::{prelude::*, ConnectionTrait, Database, DatabaseTransaction, QuerySelect, Set, TransactionTrait}; - -use super::{ - model::{ - CreateUser, FirebaseClaims, Member, MemberResult, PermissionType, RefreshToken, UpdateWorkspace, User, - UserCred, UserInWorkspace, UserLogin, Workspace, WorkspaceDetail, WorkspaceType, WorkspaceWithPermission, - }, - *, -}; - -// #[derive(FromRow)] -// struct PermissionQuery { -// #[sqlx(rename = "type")] -// type_: PermissionType, -// } - -pub struct CloudDatabase { - pub pool: DatabaseConnection, -} - -impl CloudDatabase { - pub async fn init_pool(database: &str) -> Result { - let pool = Database::connect(database).await?; - Migrator::up(&pool, None).await?; - Ok(Self { pool }) - } - - #[instrument(skip(self))] - pub async fn get_user_by_email(&self, email: &str) -> Result, DbErr> { - info!("database get_user_by_email enter"); - Users::find().filter(UsersColumn::Email.eq(email)).one(&self.pool).await - } - - #[instrument(skip(self))] - pub async fn get_workspace_owner(&self, workspace_id: String) -> Result, DbErr> { - info!("database get_workspace_owner enter"); - Permissions::find() - .column(UsersColumn::Id) - .column(UsersColumn::Name) - .column(UsersColumn::Email) - .column(UsersColumn::AvatarUrl) - .column(UsersColumn::CreatedAt) - .column(UsersColumn::Password) - .column(UsersColumn::TokenNonce) - .join_rev( - JoinType::InnerJoin, - Users::belongs_to(Permissions) - .from(UsersColumn::Id) - .to(PermissionColumn::UserId) - .into(), - ) - .filter(PermissionColumn::WorkspaceId.eq(workspace_id)) - .filter(PermissionColumn::Type.eq(PermissionType::Owner as i16)) - .into_model::() - .one(&self.pool) - .await - } - - #[instrument(skip(self))] - pub async fn user_login(&self, login: UserLogin) -> Result, DbErr> { - info!("database user_login enter"); - Users::find() - .filter(UsersColumn::Email.eq(login.email)) - .filter(UsersColumn::Password.eq(login.password)) - .one(&self.pool) - .await - } - - #[instrument(skip(self, token))] - pub async fn refresh_token(&self, token: RefreshToken) -> Result, DbErr> { - info!("database refresh_token enter"); - Users::find() - .filter(UsersColumn::Id.eq(token.user_id)) - .filter(UsersColumn::TokenNonce.eq(token.token_nonce)) - .one(&self.pool) - .await - } - - #[instrument(skip(self, token))] - pub async fn verify_refresh_token(&self, token: &RefreshToken) -> Result { - info!("database verify_refresh_token enter"); - Users::find() - .column(UsersColumn::Id) - .filter(UsersColumn::Id.eq(token.user_id.clone())) - .filter(UsersColumn::TokenNonce.eq(token.token_nonce)) - .one(&self.pool) - .await - .map(|r| r.is_some()) - } - - #[instrument(skip(trx))] - pub async fn update_cred( - trx: &DatabaseTransaction, - user_id: String, - user_email: &str, - ) -> Result, DbErr> { - info!("database update_cred enter"); - let model = Permissions::find() - .filter(PermissionColumn::UserEmail.eq(user_email)) - .one(trx) - .await?; - if model.is_none() { - return Ok(None); - } - - let id = model.unwrap().id; - Permissions::update(PermissionActiveModel { - id: Set(id.clone()), - user_id: Set(Some(user_id)), - user_email: Set(None), - ..Default::default() - }) - .filter(PermissionColumn::Id.eq(id)) - .exec(trx) - .await - .map(|_| Some(())) - } - - #[instrument(skip(self))] - pub async fn create_user(&self, user: CreateUser) -> Result { - info!("database create_user enter"); - let trx = self.pool.begin().await?; - - let id = nanoid!(); - let Ok(user) = Users::insert(UsersActiveModel { - id: Set(id.clone()), - name: Set(user.name), - password: Set(Some(user.password)), - email: Set(user.email), - avatar_url: Set(user.avatar_url), - ..Default::default() - }) - .exec_with_returning(&trx) - .await - else { - trx.rollback().await?; - return Err(DbErr::RecordNotUpdated); - }; - - Self::update_cred(&trx, id, &user.email).await?; - - trx.commit().await?; - - Ok(user) - } - - #[instrument(skip(self))] - pub async fn get_workspace_by_id(&self, workspace_id: String) -> Result, DbErr> { - info!("database get_workspace_by_id enter"); - let workspace = Workspaces::find() - .filter(WorkspacesColumn::Id.eq(workspace_id.clone())) - .one(&self.pool) - .await?; - - let workspace = match workspace { - Some(workspace) if workspace.r#type == WorkspaceType::Private as i16 => { - return Ok(Some(WorkspaceDetail { - owner: None, - member_count: 0, - workspace: Workspace { - id: workspace.id.clone(), - public: workspace.public, - r#type: workspace.r#type.into(), - created_at: workspace.created_at.unwrap_or_default().naive_local(), - }, - })) - } - Some(ws) => ws, - None => return Ok(None), - }; - - let owner = self - .get_workspace_owner(workspace_id.clone()) - .await? - .expect("owner not found"); - - let member_count = Permissions::find() - .filter(PermissionColumn::WorkspaceId.eq(workspace_id)) - .filter(PermissionColumn::Accepted.eq(true)) - .count(&self.pool) - .await?; - - Ok(Some(WorkspaceDetail { - owner: Some(User { - id: owner.id, - name: owner.name, - email: owner.email, - avatar_url: owner.avatar_url, - created_at: owner.created_at.unwrap_or_default().naive_local(), - }), - member_count, - workspace: Workspace { - id: workspace.id.clone(), - public: workspace.public, - r#type: workspace.r#type.into(), - created_at: workspace.created_at.unwrap_or_default().naive_local(), - }, - })) - } - - #[instrument(skip(self, trx))] - pub async fn create_workspace( - &self, - trx: &C, - user_id: String, - ws_type: WorkspaceType, - ) -> Result { - info!("database create_workspace enter"); - let id = nanoid!(); - let workspace = Workspaces::insert(WorkspacesActiveModel { - id: Set(id), - public: Set(false), - r#type: Set(ws_type as i16), - ..Default::default() - }) - .exec_with_returning(trx) - .await - .map(|ws| Workspace { - id: ws.id, - public: ws.public, - r#type: ws.r#type.into(), - created_at: ws.created_at.unwrap_or_default().naive_local(), - })?; - - let permissions_id = nanoid!(); - Permissions::insert(PermissionActiveModel { - id: Set(permissions_id), - user_id: Set(Some(user_id)), - workspace_id: Set(workspace.id.clone()), - r#type: Set(PermissionType::Owner as i16), - accepted: Set(true), - ..Default::default() - }) - .exec(trx) - .await?; - - Ok(workspace) - } - - #[instrument(skip(self))] - pub async fn create_normal_workspace(&self, user_id: String) -> Result { - info!("database create_normal_workspace enter"); - let trx = self.pool.begin().await?; - let workspace = self.create_workspace(&trx, user_id, WorkspaceType::Normal).await?; - - trx.commit().await?; - - Ok(workspace) - } - - #[instrument(skip(self))] - pub async fn update_workspace( - &self, - workspace_id: String, - data: UpdateWorkspace, - ) -> Result, DbErr> { - info!("database update_workspace enter"); - let model = Workspaces::find() - .filter(WorkspacesColumn::Id.eq(workspace_id.clone())) - .filter(WorkspacesColumn::Type.eq(WorkspaceType::Normal as i32)) - .one(&self.pool) - .await?; - if model.is_none() { - return Ok(None); - } - - let id = model.unwrap().id; - let workspace = Workspaces::update(WorkspacesActiveModel { - id: Set(id.clone()), - public: Set(data.public), - ..Default::default() - }) - .filter(WorkspacesColumn::Id.eq(id)) - .exec(&self.pool) - .await - .map(|ws| Workspace { - id: ws.id, - public: ws.public, - r#type: ws.r#type.into(), - created_at: ws.created_at.unwrap_or_default().naive_local(), - })?; - Ok(Some(workspace)) - } - - #[instrument(skip(self))] - pub async fn delete_workspace(&self, workspace_id: String) -> Result { - info!("database delete_workspace enter"); - let trx = self.pool.begin().await?; - - Permissions::delete_many() - .filter(PermissionColumn::WorkspaceId.eq(workspace_id.clone())) - .filter(Expr::exists( - Query::select() - .from(Workspaces) - .column(WorkspacesColumn::Id) - .and_where(Expr::col((Workspaces, WorkspacesColumn::Id)).eq(workspace_id.clone())) - .and_where(Expr::col((Workspaces, WorkspacesColumn::Type)).eq(WorkspaceType::Normal as i32)) - .limit(1) - .take(), - )) - .exec(&trx) - .await?; - - let success = Workspaces::delete_many() - .filter(WorkspacesColumn::Id.eq(workspace_id.clone())) - .filter(WorkspacesColumn::Type.eq(WorkspaceType::Normal as i32)) - .exec(&trx) - .await - .map(|r| r.rows_affected > 0)?; - - trx.commit().await?; - Ok(success) - } - - #[instrument(skip(self))] - pub async fn get_user_workspaces(&self, user_id: String) -> Result, DbErr> { - info!("database get_user_workspaces enter"); - Permissions::find() - .column_as(WorkspacesColumn::Id, "id") - .column_as(WorkspacesColumn::Public, "public") - .column_as(WorkspacesColumn::CreatedAt, "created_at") - .column_as(WorkspacesColumn::Type, "type") - .column_as(PermissionColumn::Type, "permission") - .join_rev( - JoinType::InnerJoin, - Workspaces::belongs_to(Permissions) - .from(WorkspacesColumn::Id) - .to(PermissionColumn::WorkspaceId) - .into(), - ) - .filter(PermissionColumn::UserId.eq(user_id)) - .filter(PermissionColumn::Accepted.eq(true)) - .into_model::() - .all(&self.pool) - .await - } - - #[instrument(skip(self))] - pub async fn get_workspace_members(&self, workspace_id: String) -> Result, DbErr> { - info!("database get_workspace_members enter"); - Permissions::find() - .column_as(PermissionColumn::Id, "id") - .column_as(PermissionColumn::Type, "type") - .column_as(PermissionColumn::UserEmail, "user_email") - .column_as(PermissionColumn::Accepted, "accepted") - .column_as(PermissionColumn::CreatedAt, "created_at") - .column_as(UsersColumn::Id, "user_id") - .column_as(UsersColumn::Name, "user_name") - .column_as(UsersColumn::Email, "user_table_email") - .column_as(UsersColumn::AvatarUrl, "user_avatar_url") - .column_as(UsersColumn::CreatedAt, "user_created_at") - .join_rev( - JoinType::LeftJoin, - Users::belongs_to(Permissions) - .from(UsersColumn::Id) - .to(PermissionColumn::UserId) - .into(), - ) - .filter(PermissionColumn::WorkspaceId.eq(workspace_id.clone())) - .into_model::() - .all(&self.pool) - .await - .map(|m| m.iter().map(|m| m.into()).collect()) - } - - #[instrument(skip(self))] - pub async fn get_permission(&self, user_id: String, workspace_id: String) -> Result, DbErr> { - info!("database get_permission enter"); - Permissions::find() - .filter(PermissionColumn::UserId.eq(user_id)) - .filter(PermissionColumn::WorkspaceId.eq(workspace_id)) - .one(&self.pool) - .await - .map(|p| p.map(|p| p.r#type.into())) - } - - #[instrument(skip(self))] - pub async fn get_permission_by_permission_id( - &self, - user_id: String, - permission_id: String, - ) -> Result, DbErr> { - info!("database get_permission_by_permission_id enter"); - Permissions::find() - .filter(PermissionColumn::UserId.eq(user_id)) - .filter( - PermissionColumn::WorkspaceId.in_subquery( - Query::select() - .from(Permissions) - .column(PermissionColumn::WorkspaceId) - .and_where(Expr::col((Permissions, PermissionColumn::Id)).eq(permission_id)) - .take(), - ), - ) - .one(&self.pool) - .await - .map(|p| p.map(|p| p.r#type.into())) - } - - #[instrument(skip(self))] - pub async fn get_permission_by_id(&self, permission_id: String) -> Result, DbErr> { - info!("database get_permission_by_id enter"); - Permissions::find() - .filter(PermissionColumn::Id.eq(permission_id)) - .one(&self.pool) - .await - } - - #[instrument(skip(self))] - pub async fn can_read_workspace(&self, user_id: String, workspace_id: String) -> Result { - info!("database can_read_workspace enter"); - Permissions::find() - .filter( - PermissionColumn::UserId - .eq(user_id) - .and(PermissionColumn::WorkspaceId.eq(workspace_id.clone())) - .and(PermissionColumn::Accepted.eq(true)) - .or(Expr::exists( - Query::select() - .from(Workspaces) - .column(WorkspacesColumn::Id) - .and_where(Expr::col((Workspaces, WorkspacesColumn::Id)).eq(workspace_id.clone())) - .and_where(Expr::col((Workspaces, WorkspacesColumn::Public)).eq(true)) - .limit(1) - .take(), - )), - ) - .one(&self.pool) - .await - .map(|p| p.is_some()) - } - - #[instrument(skip(self))] - pub async fn is_public_workspace(&self, workspace_id: String) -> Result { - info!("database is_public_workspace enter"); - Workspaces::find() - .filter(WorkspacesColumn::Id.eq(workspace_id.clone())) - .filter(WorkspacesColumn::Public.eq(true)) - .one(&self.pool) - .await - .map(|p| p.is_some()) - } - - #[instrument(skip(self))] - pub async fn create_permission( - &self, - email: &str, - workspace_id: String, - permission_type: PermissionType, - ) -> Result, DbErr> { - info!("database create_permission enter"); - let workspace = Workspaces::find() - .filter(WorkspacesColumn::Id.eq(workspace_id.clone())) - .filter(WorkspacesColumn::Type.eq(WorkspaceType::Normal as i32)) - .one(&self.pool) - .await?; - if workspace.is_none() { - return Ok(None); - } - - let user = self.get_user_by_email(email).await?; - let id = nanoid!(); - Permissions::insert(PermissionActiveModel { - id: Set(id.clone()), - user_id: Set(user.clone().map(|u| u.id)), - user_email: Set(user.clone().and(None).or(Some(email.to_string()))), - workspace_id: Set(workspace_id), - r#type: Set(permission_type as i16), - ..Default::default() - }) - .exec(&self.pool) - .await?; - - let user = match user { - Some(user) => UserCred::Registered(User { - id: user.id, - name: user.name, - email: user.email, - avatar_url: user.avatar_url, - created_at: user.created_at.unwrap_or_default().naive_local(), - }), - None => UserCred::UnRegistered { - email: email.to_owned(), - }, - }; - - Ok(Some((id, user))) - } - - #[instrument(skip(self))] - pub async fn accept_permission(&self, permission_id: String) -> Result, DbErr> { - info!("database accept_permission enter"); - let p = Permissions::find() - .filter(PermissionColumn::Id.eq(permission_id.clone())) - .one(&self.pool) - .await?; - - if p.is_none() { - return Ok(None); - } - - Ok(Some( - Permissions::update(PermissionActiveModel { - id: Set(permission_id.clone()), - accepted: Set(true), - ..Default::default() - }) - .filter(PermissionColumn::Id.eq(permission_id)) - .exec(&self.pool) - .await - .map(|op| Permission { - id: op.id, - r#type: op.r#type.into(), - workspace_id: op.workspace_id, - user_id: op.user_id, - user_email: op.user_email, - accepted: op.accepted, - created_at: op.created_at.unwrap_or_default().naive_local(), - })?, - )) - } - - #[instrument(skip(self))] - pub async fn delete_permission(&self, permission_id: String) -> Result { - info!("database delete_permission enter"); - Permissions::delete_many() - .filter(PermissionColumn::Id.eq(permission_id)) - .exec(&self.pool) - .await - .map(|q| q.rows_affected > 0) - } - - #[instrument(skip(self))] - pub async fn delete_permission_by_query(&self, user_id: String, workspace_id: String) -> Result { - info!("database delete_permission_by_query enter"); - Permissions::delete_many() - .filter(PermissionColumn::UserId.eq(user_id)) - .filter(PermissionColumn::WorkspaceId.eq(workspace_id.clone())) - .exec(&self.pool) - .await - .map(|q| q.rows_affected > 0) - } - - #[instrument(skip(self))] - pub async fn get_user_in_workspace_by_email( - &self, - workspace_id: String, - email: &str, - ) -> Result { - info!("database get_user_in_workspace_by_email enter"); - let user: Option = Users::find() - .filter(UsersColumn::Email.eq(email)) - .one(&self.pool) - .await?; - - Ok(if let Some(user) = user { - let in_workspace = Permissions::find() - .filter(PermissionColumn::UserId.eq(user.id.clone())) - .filter(PermissionColumn::WorkspaceId.eq(workspace_id)) - .one(&self.pool) - .await - .map(|p| p.is_some())?; - - UserInWorkspace { - user: UserCred::Registered(User { - id: user.id, - name: user.name, - email: user.email, - avatar_url: user.avatar_url, - created_at: user.created_at.unwrap_or_default().naive_local(), - }), - in_workspace, - } - } else { - let in_workspace = Permissions::find() - .filter(PermissionColumn::WorkspaceId.eq(workspace_id)) - .filter(PermissionColumn::UserEmail.eq(email)) - .one(&self.pool) - .await - .map(|p| p.is_some())?; - - UserInWorkspace { - user: UserCred::UnRegistered { - email: email.to_string(), - }, - in_workspace, - } - }) - } - - #[instrument(skip(self))] - pub async fn firebase_user_login(&self, claims: &FirebaseClaims) -> Result { - info!("database firebase_user_login enter"); - let firebase_user: Option = GoogleUsers::find() - .filter(GoogleUsersColumn::GoogleId.eq(claims.user_id.clone())) - .one(&self.pool) - .await?; - - if let Some(user_info) = &claims.user_info { - if let Some(firebase_user) = firebase_user { - let id = Users::find() - .filter(UsersColumn::Id.eq(firebase_user.user_id.clone())) - .one(&self.pool) - .await? - .ok_or_else(|| DbErr::RecordNotFound(firebase_user.user_id.clone()))? - .id; - - let user = Users::update(UsersActiveModel { - id: Set(id.clone()), - name: Set(user_info.name.clone().unwrap_or("Uname".into())), - email: Set(user_info.email.clone()), - avatar_url: Set(user_info.picture.clone()), - ..Default::default() - }) - .filter(UsersColumn::Id.eq(id)) - .exec(&self.pool) - .await?; - Ok(user) - } else { - let trx = self.pool.begin().await?; - let id = nanoid!(); - let user = Users::insert(UsersActiveModel { - id: Set(id), - name: Set(user_info.name.clone().unwrap_or("Uname".into())), - email: Set(user_info.email.clone()), - avatar_url: Set(user_info.picture.clone()), - ..Default::default() - }) - .exec_with_returning(&trx) - .await?; - let google_user_id = nanoid!(); - GoogleUsers::insert(GoogleUsersActiveModel { - id: Set(google_user_id), - user_id: Set(user.id.clone()), - google_id: Set(claims.user_id.clone()), - }) - .exec_with_returning(&trx) - .await?; - Permissions::update_many() - .set(PermissionActiveModel { - user_id: Set(Some(user.id.clone())), - ..Default::default() - }) - .filter(PermissionColumn::UserEmail.eq(user_info.email.clone())) - .exec(&trx) - .await?; - trx.commit().await?; - Ok(user) - } - } else { - Err(DbErr::RecordNotInserted) - } - } - - #[instrument(skip(self))] - pub async fn get_user_owner_workspaces(&self, user_id: String) -> Result, DbErr> { - info!("database get_user_owner_workspaces enter"); - Permissions::find() - .filter(PermissionColumn::UserId.eq(user_id)) - .filter(PermissionColumn::Type.eq(PermissionType::Owner as i16)) - .all(&self.pool) - .await - .map(|m| m.iter().map(|m| m.workspace_id.clone()).collect()) - } -} - -#[cfg(test)] -mod test { - - #[tokio::test] - async fn database_create_tables() -> anyhow::Result<()> { - use super::*; - let pool = CloudDatabase::init_pool("sqlite::memory:").await?; - // start test - let new_user = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx@xxx.xx".to_string(), - name: "xxx".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - let new_workspace = pool.create_normal_workspace(new_user.id.clone()).await.unwrap(); - assert_eq!(new_workspace.public, false); - - let new_workspace1_clone = pool - .get_workspace_by_id(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_user.id, new_workspace1_clone.owner.unwrap().id); - assert_eq!(new_workspace.id, new_workspace1_clone.workspace.id); - assert_eq!(new_workspace.created_at, new_workspace1_clone.workspace.created_at); - - assert_eq!( - new_workspace.id, - pool.get_user_workspaces(new_user.id).await.unwrap().get(0).unwrap().id - ); - - Ok(()) - } - - #[tokio::test] - async fn database_update_tables() -> anyhow::Result<()> { - use super::*; - let pool = CloudDatabase::init_pool("sqlite::memory:").await?; - // start test - let new_user = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx@xxx.xx".to_string(), - name: "xxx".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - - let mut new_workspace = pool.create_normal_workspace(new_user.id.clone()).await.unwrap(); - let is_published = pool.is_public_workspace(new_workspace.id.clone()).await.unwrap(); - let workspace_owner = pool - .get_workspace_owner(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - assert_eq!(workspace_owner.id, new_user.id); - assert!(!new_workspace.public); - assert!(!is_published); - new_workspace = pool - .update_workspace(new_workspace.id.clone(), UpdateWorkspace { public: true }) - .await - .unwrap() - .unwrap(); - let is_published = pool.is_public_workspace(new_workspace.id.clone()).await.unwrap(); - assert!(new_workspace.public); - assert!(is_published); - - Ok(()) - } - - #[tokio::test] - async fn database_delete_tables() -> anyhow::Result<()> { - use super::*; - let pool = CloudDatabase::init_pool("sqlite::memory:").await?; - // start test - let new_user = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx@xxx.xx".to_string(), - name: "xxx".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - - let new_workspace = pool.create_normal_workspace(new_user.id.clone()).await.unwrap(); - - let is_deleted = pool.delete_workspace(new_workspace.id.clone()).await.unwrap(); - assert_eq!(is_deleted, true); - - Ok(()) - } - - #[tokio::test] - async fn database_permission() -> anyhow::Result<()> { - use super::*; - let pool = CloudDatabase::init_pool("sqlite::memory:").await?; - // start test - let new_user = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx@xxx.xx".to_string(), - name: "xxx".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - let new_user2 = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx2@xxx.xx".to_string(), - name: "xxx2".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - let new_user3 = pool - .create_user(CreateUser { - avatar_url: Some("xxx".to_string()), - email: "xxx3@xxx.xx".to_string(), - name: "xxx3".to_string(), - password: "xxx".to_string(), - }) - .await - .unwrap(); - - let new_workspace = pool.create_normal_workspace(new_user.id.clone()).await.unwrap(); - - let workspace_owner = pool - .get_workspace_owner(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - assert_eq!(workspace_owner.id, new_user.id); - //Create permission - let new_permission = pool - .create_permission( - &new_user2.email.clone(), - new_workspace.id.clone(), - PermissionType::Admin, - ) - .await - .unwrap() - .unwrap(); - - //accept permission - let accept_permission = pool.accept_permission(new_permission.0.clone()).await.unwrap().unwrap(); - assert_eq!(accept_permission.workspace_id.clone(), new_workspace.id.clone()); - assert_eq!(accept_permission.r#type.clone(), PermissionType::Admin); - assert_eq!(accept_permission.user_id.unwrap(), new_user2.id.clone()); - assert_eq!(accept_permission.user_email.unwrap(), new_user2.email.clone()); - assert!(accept_permission.accepted); - - let workspace_owner = pool - .get_workspace_owner(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - assert_eq!(workspace_owner.id, new_user.id); - - //get permission by use id - let permission_by_user1_id = pool - .get_permission(new_user.id.clone(), new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - let permission_by_user2_id = pool - .get_permission(new_user2.id.clone(), new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - let permission_by_user3_id = pool - .get_permission(new_user3.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - assert_eq!(permission_by_user1_id, PermissionType::Owner); - assert_eq!(permission_by_user2_id, PermissionType::Admin); - assert_eq!(permission_by_user3_id, None); - - //get user workspace by user id - let user1_workspace = pool.get_user_workspaces(new_user.id.clone()).await.unwrap(); - let user2_workspace = pool.get_user_workspaces(new_user2.id.clone()).await.unwrap(); - assert_eq!(user1_workspace.len(), 1); - assert_eq!(user2_workspace.len(), 1); - assert_eq!(user1_workspace.get(0).unwrap().id, user2_workspace.get(0).unwrap().id); - - //get workspace members - let workspace_members = pool.get_workspace_members(new_workspace.id.clone()).await.unwrap(); - assert_eq!(workspace_members.len(), 2); - - let member1 = workspace_members.get(0).unwrap().user.clone(); - let member2 = workspace_members.get(1).unwrap().user.clone(); - if let UserCred::Registered(user) = member1 { - assert_eq!(user.id, new_user.id); - } else { - panic!("get workspace members failed, owner is not registered"); - } - if let UserCred::Registered(user2) = member2 { - assert_eq!(user2.id, new_user2.id); - } else { - panic!("get workspace members failed, member is not registered"); - } - - //get user in workspace by email - - let user1_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user.email.clone()) - .await - .unwrap(); - let user2_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user2.email.clone()) - .await - .unwrap(); - let user3_not_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user3.email.clone()) - .await - .unwrap(); - assert!(user1_in_workspace.in_workspace); - assert!(user2_in_workspace.in_workspace); - assert!(!user3_not_in_workspace.in_workspace); - - //can read workspace - let owner_can_read_workspace = pool - .can_read_workspace(new_user.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - let member_can_read_workspace = pool - .can_read_workspace(new_user2.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - let another_can_not_read_workspace = pool - .can_read_workspace(new_user3.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - assert_eq!(owner_can_read_workspace, true); - assert_eq!(member_can_read_workspace, true); - assert_eq!(another_can_not_read_workspace, false); - - //delete permission - let is_deleted = pool.delete_permission(new_permission.0.clone()).await.unwrap(); - assert!(is_deleted); - let workspace_owner = pool - .get_workspace_owner(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - assert_eq!(workspace_owner.id, new_user.id); - //delete permission by query - let _new_permission = pool - .create_permission( - &new_user2.email.clone(), - new_workspace.id.clone(), - PermissionType::Admin, - ) - .await - .unwrap() - .unwrap(); - - let is_deleted_by_query = pool - .delete_permission_by_query(new_user2.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - assert!(is_deleted_by_query); - let workspace_owner = pool - .get_workspace_owner(new_workspace.id.clone()) - .await - .unwrap() - .unwrap(); - assert_eq!(workspace_owner.id, new_user.id); - //if in workspace after delete permission - - let user1_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user.email.clone()) - .await - .unwrap(); - let user2_not_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user2.email.clone()) - .await - .unwrap(); - let user3_not_in_workspace = pool - .get_user_in_workspace_by_email(new_workspace.id.clone(), &new_user3.email.clone()) - .await - .unwrap(); - assert!(user1_in_workspace.in_workspace); - assert!(!user2_not_in_workspace.in_workspace); - assert!(!user3_not_in_workspace.in_workspace); - - //can read workspace after delete permission - let user1_can_read_workspace = pool - .can_read_workspace(new_user.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - let user2_can_read_workspace = pool - .can_read_workspace(new_user2.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - let user3_can_not_read_workspace = pool - .can_read_workspace(new_user3.id.clone(), new_workspace.id.clone()) - .await - .unwrap(); - assert_eq!(user1_can_read_workspace, true); - assert_eq!(user2_can_read_workspace, false); - assert_eq!(user3_can_not_read_workspace, false); - Ok(()) - } -} diff --git a/libs/cloud-database/src/entities/google_users.rs b/libs/cloud-database/src/entities/google_users.rs deleted file mode 100644 index d6bee1ea7..000000000 --- a/libs/cloud-database/src/entities/google_users.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "google_users")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub user_id: String, - #[sea_orm(unique)] - pub google_id: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::users::Entity", - from = "Column::UserId", - to = "super::users::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Users, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Users.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/libs/cloud-database/src/entities/mod.rs b/libs/cloud-database/src/entities/mod.rs deleted file mode 100644 index 4755c9560..000000000 --- a/libs/cloud-database/src/entities/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -pub mod prelude; - -pub mod google_users; -pub mod permissions; -pub mod users; -pub mod workspaces; diff --git a/libs/cloud-database/src/entities/permissions.rs b/libs/cloud-database/src/entities/permissions.rs deleted file mode 100644 index 7c0257ec9..000000000 --- a/libs/cloud-database/src/entities/permissions.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "permissions")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub workspace_id: String, - pub user_id: Option, - #[sea_orm(column_type = "Text", nullable)] - pub user_email: Option, - pub r#type: i16, - pub accepted: bool, - pub created_at: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::users::Entity", - from = "Column::UserId", - to = "super::users::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Users, - #[sea_orm( - belongs_to = "super::workspaces::Entity", - from = "Column::WorkspaceId", - to = "super::workspaces::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Workspaces, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Users.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Workspaces.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/libs/cloud-database/src/entities/prelude.rs b/libs/cloud-database/src/entities/prelude.rs deleted file mode 100644 index f1eef61f0..000000000 --- a/libs/cloud-database/src/entities/prelude.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -pub use super::{ - google_users::Entity as GoogleUsers, permissions::Entity as Permissions, users::Entity as Users, - workspaces::Entity as Workspaces, -}; diff --git a/libs/cloud-database/src/entities/users.rs b/libs/cloud-database/src/entities/users.rs deleted file mode 100644 index 810d665e2..000000000 --- a/libs/cloud-database/src/entities/users.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "users")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub name: String, - #[sea_orm(unique)] - pub email: String, - pub avatar_url: Option, - pub token_nonce: Option, - pub password: Option, - pub created_at: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::google_users::Entity")] - GoogleUsers, - #[sea_orm(has_many = "super::permissions::Entity")] - Permissions, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::GoogleUsers.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Permissions.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/libs/cloud-database/src/entities/workspaces.rs b/libs/cloud-database/src/entities/workspaces.rs deleted file mode 100644 index a401cf606..000000000 --- a/libs/cloud-database/src/entities/workspaces.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "workspaces")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub public: bool, - pub r#type: i16, - pub created_at: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::permissions::Entity")] - Permissions, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Permissions.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/libs/cloud-database/src/lib.rs b/libs/cloud-database/src/lib.rs deleted file mode 100644 index 0bec7099e..000000000 --- a/libs/cloud-database/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[forbid(unsafe_code)] -mod database; -mod entities; -mod model; - -pub use database::CloudDatabase; -use entities::prelude::*; -pub use model::*; -use sea_orm::EntityTrait; - -type UsersModel = ::Model; -type UsersActiveModel = entities::users::ActiveModel; -type UsersColumn = ::Column; -// type WorkspacesModel = ::Model; -type WorkspacesActiveModel = entities::workspaces::ActiveModel; -type WorkspacesColumn = ::Column; -type PermissionModel = ::Model; -type PermissionActiveModel = entities::permissions::ActiveModel; -type PermissionColumn = ::Column; -type GoogleUsersModel = ::Model; -type GoogleUsersActiveModel = entities::google_users::ActiveModel; -type GoogleUsersColumn = ::Column; diff --git a/libs/cloud-database/src/model.rs b/libs/cloud-database/src/model.rs deleted file mode 100644 index e3397e6ee..000000000 --- a/libs/cloud-database/src/model.rs +++ /dev/null @@ -1,370 +0,0 @@ -// use super::*; -// use sqlx::{postgres::PgRow, FromRow, Result, Row}; - -use chrono::{ - naive::serde::{ts_milliseconds, ts_seconds}, - DateTime, Utc, -}; -use jwst_logger::error; -use schemars::{JsonSchema, JsonSchema_repr}; -use sea_orm::{FromQueryResult, TryGetable}; -use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use sqlx::{self, types::chrono::NaiveDateTime, FromRow, Type}; - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserInfo { - pub email: String, - pub email_verified: bool, - pub name: Option, - // picture of avatar - pub picture: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct FirebaseClaims { - // name of project - pub aud: String, - pub auth_time: usize, - pub exp: usize, - pub iat: usize, - pub iss: String, - pub sub: String, - pub user_id: String, - #[serde(flatten)] - pub user_info: Option, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct User { - pub id: String, - pub name: String, - pub email: String, - pub avatar_url: Option, - #[serde(with = "ts_milliseconds")] - #[schemars(with = "i64")] - pub created_at: NaiveDateTime, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserWithNonce { - pub user: User, - pub token_nonce: i16, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserQuery { - pub email: Option, - pub workspace_id: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Claims { - #[serde(with = "ts_seconds")] - #[schemars(with = "i64")] - pub exp: NaiveDateTime, - #[serde(flatten)] - pub user: User, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type")] -pub enum MakeToken { - DebugCreateUser(CreateUser), - DebugLoginUser(UserLogin), - Refresh { token: String }, - Google { token: String }, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserLogin { - pub email: String, - pub password: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct CreateUser { - pub name: String, - pub avatar_url: Option, - pub email: String, - pub password: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserToken { - pub token: String, - pub refresh: String, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct RefreshToken { - #[serde(with = "ts_milliseconds")] - #[schemars(with = "i64")] - pub expires: NaiveDateTime, - pub user_id: String, - pub token_nonce: i16, -} - -#[derive(Type, Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Clone, Copy, JsonSchema_repr)] -#[repr(i32)] -pub enum WorkspaceType { - Private = 0, - Normal = 1, -} - -impl From for WorkspaceType { - fn from(i: i16) -> Self { - match i { - 0 => WorkspaceType::Private, - 1 => WorkspaceType::Normal, - _ => { - error!("invalid workspace type: {}", i); - WorkspaceType::Private - } - } - } -} - -impl TryGetable for WorkspaceType { - fn try_get_by(res: &sea_orm::QueryResult, index: I) -> Result { - let i: i16 = res.try_get_by(index).map_err(sea_orm::TryGetError::DbErr)?; - Ok(WorkspaceType::from(i)) - } - - fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result { - let i: i16 = res.try_get(pre, col).map_err(sea_orm::TryGetError::DbErr)?; - Ok(WorkspaceType::from(i)) - } - - fn try_get_by_index(res: &sea_orm::QueryResult, index: usize) -> Result { - Self::try_get_by(res, index) - } -} - -#[derive(FromQueryResult, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Workspace { - pub id: String, - pub public: bool, - #[serde(rename = "type")] - pub r#type: WorkspaceType, - #[serde(with = "ts_milliseconds")] - #[schemars(with = "i64")] - pub created_at: NaiveDateTime, -} - -#[derive(FromQueryResult, Clone, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceWithPermission { - pub permission: PermissionType, - // #[serde(flatten)] - // pub workspace: Workspace, - pub id: String, - pub public: bool, - #[serde(rename = "type")] - pub r#type: WorkspaceType, - // #[serde(with = "ts_milliseconds")] - // #[schemars(with = "i64")] - // pub created_at: NaiveDateTime, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceDetail { - // None if it's private - pub owner: Option, - pub member_count: u64, - #[serde(flatten)] - pub workspace: Workspace, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct CreateWorkspace { - pub name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceSearchInput { - pub query: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceSearchResults { - pub items: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceSearchResult { - pub block_id: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UpdateWorkspace { - pub public: bool, -} - -#[derive(Type, Serialize_repr, Deserialize_repr, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, JsonSchema_repr)] -#[repr(i16)] -pub enum PermissionType { - Read = 0, - Write = 1, - Admin = 10, - Owner = 99, -} - -impl From for PermissionType { - fn from(i: i16) -> Self { - match i { - 0 => PermissionType::Read, - 1 => PermissionType::Write, - 10 => PermissionType::Admin, - 99 => PermissionType::Owner, - _ => { - error!("invalid permission type: {}", i); - PermissionType::Read - } - } - } -} - -impl TryGetable for PermissionType { - fn try_get_by(res: &sea_orm::QueryResult, index: I) -> Result { - let i: i16 = res.try_get_by(index).map_err(sea_orm::TryGetError::DbErr)?; - Ok(PermissionType::from(i)) - } - - fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result { - let i: i16 = res.try_get(pre, col).map_err(sea_orm::TryGetError::DbErr)?; - Ok(PermissionType::from(i)) - } - - fn try_get_by_index(res: &sea_orm::QueryResult, index: usize) -> Result { - Self::try_get_by(res, index) - } -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Permission { - pub id: String, - #[serde(rename = "type")] - #[sqlx(rename = "type")] - pub r#type: PermissionType, - pub workspace_id: String, - pub user_id: Option, - pub user_email: Option, - pub accepted: bool, - #[serde(with = "ts_milliseconds")] - #[schemars(with = "i64")] - pub created_at: NaiveDateTime, -} - -impl PermissionType { - pub fn can_write(&self) -> bool { - *self >= Self::Write - } - - pub fn can_admin(&self) -> bool { - *self >= Self::Admin - } - - pub fn is_owner(&self) -> bool { - *self == Self::Owner - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct CreatePermission { - pub email: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type")] -pub enum UserCred { - Registered(User), - UnRegistered { email: String }, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Member { - pub id: String, - pub user: UserCred, - pub accepted: bool, - #[serde(rename = "type")] - pub r#type: PermissionType, - #[serde(with = "ts_milliseconds")] - #[schemars(with = "i64")] - pub created_at: NaiveDateTime, -} - -#[derive(FromQueryResult)] -pub struct MemberResult { - // .column_as(PermissionColumn::Id, "id") - // .column_as(PermissionColumn::Type, "type") - // .column_as(PermissionColumn::UserEmail, "user_email") - // .column_as(PermissionColumn::Accepted, "accepted") - // .column_as(PermissionColumn::CreatedAt, "created_at") - // .column_as(UsersColumn::Id, "user_id") - // .column_as(UsersColumn::Name, "user_name") - // .column_as(UsersColumn::Email, "user_table_email") - // .column_as(UsersColumn::AvatarUrl, "user_avatar_url") - // .column_as(UsersColumn::CreatedAt, "user_created_at") - pub id: String, - pub r#type: PermissionType, - pub user_email: Option, - pub accepted: bool, - pub created_at: Option>, - pub user_id: Option, - pub user_name: Option, - pub user_table_email: Option, - pub user_avatar_url: Option, - pub user_created_at: Option>, -} - -impl From<&MemberResult> for Member { - fn from(r: &MemberResult) -> Self { - let user = if let Some(id) = r.user_id.clone() { - UserCred::Registered(User { - id, - name: r.user_name.clone().unwrap(), - email: r.user_table_email.clone().unwrap(), - avatar_url: r.user_avatar_url.clone(), - created_at: r.user_created_at.unwrap_or_default().naive_local(), - }) - } else { - UserCred::UnRegistered { - email: r.user_email.clone().unwrap(), - } - }; - Member { - id: r.id.clone(), - user, - accepted: r.accepted, - r#type: r.r#type.clone(), - created_at: r.created_at.unwrap_or_default().naive_local(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct UserInWorkspace { - #[serde(flatten)] - pub user: UserCred, - pub in_workspace: bool, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Exist { - pub exists: bool, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Id { - pub id: i32, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct BigId { - pub id: i64, -} - -#[derive(FromRow, Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct Count { - pub count: i64, -} diff --git a/libs/cloud-infra/Cargo.toml b/libs/cloud-infra/Cargo.toml deleted file mode 100644 index 48bbf2089..000000000 --- a/libs/cloud-infra/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "cloud-infra" -version = "0.1.0" -authors = ["DarkSky "] -edition = "2021" -license = "AGPL-3.0-only" - -[dependencies] -aes-gcm = "0.10.1" -axum = "0.6.16" -chrono = "0.4.24" -dotenvy = "0.15.7" -handlebars = "4.3.6" -jsonwebtoken = "8.3.0" -lettre = { version = "0.10.4", default-features = false, features = [ - "builder", - "tokio1", - "pool", - "smtp-transport", - "tokio1-rustls-tls", -] } -nanoid = "0.4.0" -pem = "1.1.1" -rand = "0.8.5" -reqwest = { version = "0.11.16", default-features = false, features = [ - "json", - "rustls-tls", -] } -rust-embed = { version = "6.6.1", features = [ - "compression", - "include-exclude", - "mime-guess", -] } -serde = { version = "1.0.160", features = ["derive"] } -sha2 = "0.10.6" -thiserror = "1.0.40" -url = "2.3.1" -url-escape = "0.1.1" -utoipa = "3.3.0" -utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] } -x509-parser = "0.15.0" - -# ======= workspace dependencies ======= -cloud-database = { path = "../cloud-database" } -jwst = { workspace = true } diff --git a/libs/cloud-infra/assets/404-dev.html b/libs/cloud-infra/assets/404-dev.html deleted file mode 100644 index e417419e8..000000000 --- a/libs/cloud-infra/assets/404-dev.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - OctoBase - - - -

OctoBase

-
{{ERROR_BANNER}}
-

Directory

- - - diff --git a/libs/cloud-infra/assets/404.html b/libs/cloud-infra/assets/404.html deleted file mode 100644 index 4f1e76252..000000000 --- a/libs/cloud-infra/assets/404.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - Not found in OctoBase - - - -

Not found in OctoBase

- -

Directory

- - - diff --git a/libs/cloud-infra/assets/invite.html b/libs/cloud-infra/assets/invite.html deleted file mode 100644 index d62a53d95..000000000 --- a/libs/cloud-infra/assets/invite.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - - AFFiNE--Workspace invitation - - - - -
-
-
- affine-logo -
-
-
- You are invited! -
-
- - {{inviter_name}} - - invited you to join - {{workspace_avatar}} {{workspace_name}} -
-
- Click here to join this workspace -
- -
- or copy and paste this url -
- -
-
-
- -
- - Open Source, Privacy First - AffineOfficial - -
- Affine is the next-generation collaborative knowledge base for - professionals. -
- -
- affine-logo - -
- Copyright © {{current_year}} Toeverything -
- -
-
-
-
- - diff --git a/libs/cloud-infra/src/auth.rs b/libs/cloud-infra/src/auth.rs deleted file mode 100644 index c33281752..000000000 --- a/libs/cloud-infra/src/auth.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::collections::HashMap; - -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce}; -use axum::headers::{CacheControl, HeaderMapExt}; -use chrono::{Duration, NaiveDateTime, Utc}; -use cloud_database::{Claims, FirebaseClaims}; -use jsonwebtoken::{ - decode, decode_header, encode, errors::Error as JwtError, DecodingKey, EncodingKey, Header, Validation, -}; -use jwst::{warn, Base64DecodeError, Base64Engine, URL_SAFE_ENGINE}; -use pem::{encode as encode_pem, Pem}; -use rand::{thread_rng, Rng}; -use reqwest::{Client, RequestBuilder}; -use sha2::{Digest, Sha256}; -use thiserror::Error; -use x509_parser::prelude::{nom::Err as NomErr, parse_x509_pem, PEMError, X509Error}; - -use super::*; - -#[derive(Debug, Error)] -pub enum KeyError { - #[error("base64 error")] - Base64Error(#[from] Base64DecodeError), - #[error("jwt error")] - JwtError(#[from] JwtError), - #[error("invalid data")] - InvalidData, -} - -pub type KeyResult = Result; - -pub struct KeyContext { - jwt_encode: EncodingKey, - pub jwt_decode: DecodingKey, - aes: Aes256Gcm, -} - -impl KeyContext { - pub fn new(key: Option) -> KeyResult { - let key = key.unwrap_or_else(|| { - let key = nanoid!(); - warn!("!!! no sign key provided, use random key: `{key}` !!!"); - warn!("!!! please set SIGN_KEY in .env file or environmental variable to save your login status !!!"); - key - }); - let mut hasher = Sha256::new(); - hasher.update(key.as_bytes()); - let hash = hasher.finalize(); - - let aes = Aes256Gcm::new_from_slice(&hash[..]).map_err(|_| KeyError::InvalidData)?; - - let jwt_encode = EncodingKey::from_secret(key.as_bytes()); - let jwt_decode = DecodingKey::from_secret(key.as_bytes()); - Ok(Self { - jwt_encode, - jwt_decode, - aes, - }) - } - - pub fn sign_jwt(&self, user: &Claims) -> String { - encode(&Header::default(), user, &self.jwt_encode).expect("encode JWT error") - } - - #[allow(unused)] - pub fn decode_jwt(&self, token: &str) -> KeyResult { - let res = decode::(token, &self.jwt_decode, &Validation::default())?; - Ok(res.claims) - } - - pub fn encrypt_aes(&self, input: &[u8]) -> KeyResult> { - let rand_data: [u8; 12] = thread_rng().gen(); - let nonce = Nonce::from_slice(&rand_data); - - let mut encrypted = self.aes.encrypt(nonce, input).map_err(|_| KeyError::InvalidData)?; - encrypted.extend(nonce); - - Ok(encrypted) - } - - pub fn encrypt_aes_base64(&self, input: &[u8]) -> KeyResult { - let encrypted = self.encrypt_aes(input)?; - Ok(URL_SAFE_ENGINE.encode(encrypted)) - } - - pub fn decrypt_aes(&self, input: Vec) -> KeyResult> { - if input.len() < 12 { - return Err(KeyError::InvalidData); - } - let (content, nonce) = input.split_at(input.len() - 12); - - let Some(nonce) = nonce.try_into().ok() else { - return Err(KeyError::InvalidData); - }; - - self.aes.decrypt(nonce, content).map_err(|_| KeyError::InvalidData) - } - - pub fn decrypt_aes_base64(&self, input: String) -> KeyResult> { - let input = URL_SAFE_ENGINE.decode(input)?; - self.decrypt_aes(input) - } -} - -pub struct Endpoint { - endpoint: String, - password: Option, - http_client: Client, -} - -impl Endpoint { - fn new() -> Self { - Self { - endpoint: { - let mut endpoint = dotenvy::var("GOOGLE_ENDPOINT").unwrap_or_default(); - if endpoint.is_empty() { - endpoint.push_str("www.googleapis.com"); - } - endpoint - }, - password: dotenvy::var("GOOGLE_ENDPOINT_PASSWORD").ok(), - http_client: Client::new(), - } - } - - #[inline] - fn endpoint(&self) -> String { - format!( - "https://{}/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com", - &self.endpoint - ) - } - - fn connect(&self) -> RequestBuilder { - if let Some(password) = &self.password { - self.http_client - .get(self.endpoint()) - .basic_auth("affine", Some(password)) - } else { - self.http_client.get(self.endpoint()) - } - } -} - -#[derive(Debug, Error)] -pub enum FirebaseAuthError { - #[error("missing cache-control header")] - Header, - #[error("missing public key")] - MissingPubKey, - #[error("cannot find decoding key")] - NotFound, - #[error(transparent)] - Reqwest(#[from] reqwest::Error), - #[error(transparent)] - Jwt(#[from] jsonwebtoken::errors::Error), - #[error(transparent)] - DecodePEM(#[from] NomErr), - #[error(transparent)] - DecodeX509(#[from] NomErr), -} - -pub struct FirebaseContext { - endpoint: Endpoint, - project_ids: Vec, - expires: NaiveDateTime, - pub_key: HashMap, -} - -impl FirebaseContext { - pub fn new(project_ids: Vec) -> Self { - Self { - endpoint: Endpoint::new(), - project_ids, - expires: NaiveDateTime::MIN, - pub_key: HashMap::new(), - } - } - - async fn init_from_firebase(&mut self, expires_in: Duration) -> Result<(), FirebaseAuthError> { - let resp = self.endpoint.connect().send().await?; - - let cache = resp - .headers() - .typed_get::() - .ok_or(FirebaseAuthError::Header)?; - let now = Utc::now().naive_utc(); - let expires = now - + cache - .max_age() - .and_then(|d| Duration::from_std(d).ok()) - .unwrap_or(expires_in); - - let body = resp.json::>().await?; - - let mut pub_key = HashMap::new(); - - for (key, value) in body { - let (_, pem) = parse_x509_pem(value.as_bytes())?; - let cert = pem.parse_x509()?; - - let pbk = encode_pem(&Pem { - tag: String::from("PUBLIC KEY"), - contents: cert.public_key().raw.to_vec(), - }); - let decode = DecodingKey::from_rsa_pem(pbk.as_bytes())?; - - pub_key.insert(key, decode); - } - - self.expires = expires; - self.pub_key = pub_key; - - Ok(()) - } - - pub async fn decode_google_token( - &mut self, - token: String, - expires_in: Duration, - ) -> Result { - let header = decode_header(&token)?; - - if self.expires < Utc::now().naive_utc() { - self.init_from_firebase(expires_in).await?; - } - - let kid = header.kid.ok_or(FirebaseAuthError::MissingPubKey)?; - - let key = self.pub_key.get(&kid).ok_or(FirebaseAuthError::NotFound)?; - - let mut validation = Validation::new(header.alg); - - validation.set_audience(&self.project_ids); - - let token = decode(&token, key, &validation)?; - - Ok(token.claims) - } -} diff --git a/libs/cloud-infra/src/constants.rs b/libs/cloud-infra/src/constants.rs deleted file mode 100644 index 4677e3585..000000000 --- a/libs/cloud-infra/src/constants.rs +++ /dev/null @@ -1,11 +0,0 @@ -use super::hosting::{rust_embed, RustEmbed}; - -pub const MAIL_INVITE_TITLE: &str = "{{inviter_name}} invited you to join {{workspace_name}}"; -pub const MAIL_FROM: &str = "noreply@toeverything.info"; -pub const MAIL_PROVIDER: &str = "smtp.gmail.com"; -pub const MAIL_WHITELIST: [&str; 3] = ["affine.pro", "affine.live", "affine.systems"]; - -#[derive(RustEmbed)] -#[folder = "assets"] -#[include = "*"] -pub struct StaticFiles; diff --git a/libs/cloud-infra/src/hosting/api_doc.rs b/libs/cloud-infra/src/hosting/api_doc.rs deleted file mode 100644 index cc83d5348..000000000 --- a/libs/cloud-infra/src/hosting/api_doc.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::{env, sync::Arc}; - -use axum::{extract::Path, http::StatusCode, response::IntoResponse, routing::get, Extension, Json, Router}; -use utoipa::openapi::{License, OpenApi}; -use utoipa_swagger_ui::{serve, Config, Url}; - -async fn serve_swagger_ui( - tail: Option>, - Extension(state): Extension>>, -) -> impl IntoResponse { - match serve(&tail.map(|p| p.to_string()).unwrap_or("".into()), state) { - Ok(file) => file - .map(|file| (StatusCode::OK, [("Content-Type", file.content_type)], file.bytes).into_response()) - .unwrap_or_else(|| StatusCode::NOT_FOUND.into_response()), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), - } -} - -pub fn with_api_doc(router: Router, mut openapi: OpenApi, name: &'static str) -> Router { - if cfg!(debug_assertions) || std::env::var("JWST_DEV").is_ok() { - let config = Url::from(format!("/api/{name}.json")); - let config = Config::new(vec![config]); - openapi.info.license = Some(License::new(env!("CARGO_PKG_LICENSE"))); - router - .route(&format!("/{name}.json"), get(move || async { Json(openapi) })) - .route("/docs/", get(serve_swagger_ui)) - .route("/docs/*tail", get(serve_swagger_ui)) - .layer(Extension(Arc::new(config))) - } else { - router - } -} diff --git a/libs/cloud-infra/src/hosting/files.rs b/libs/cloud-infra/src/hosting/files.rs deleted file mode 100644 index a65542759..000000000 --- a/libs/cloud-infra/src/hosting/files.rs +++ /dev/null @@ -1,33 +0,0 @@ -use url_escape::decode; - -use super::{pages::*, *}; - -pub async fn fetch_static_response(uri: Uri, sap: bool, fetcher: Option) -> impl IntoResponse { - let path = uri.path().trim_start_matches('/'); - - // default page - if path.is_empty() || path == INDEX_HTML { - return default_page(fetcher, uri); - } - - match fetcher.and_then(|fetcher| fetcher(path).or_else(|| fetcher(&decode(path)))) { - Some(content) => { - let body = boxed(Full::from(content.data)); - - Response::builder() - .header(CONTENT_TYPE, content.metadata.mimetype()) - .body(body) - .unwrap() - } - None => default_page( - if !path.contains('.') && sap { - // return index if no extension and in sap mode - // fallback to not_found if no fetcher is provided - fetcher - } else { - None - }, - uri, - ), - } -} diff --git a/libs/cloud-infra/src/hosting/mod.rs b/libs/cloud-infra/src/hosting/mod.rs deleted file mode 100644 index 494f66b09..000000000 --- a/libs/cloud-infra/src/hosting/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod api_doc; -mod files; -mod pages; - -use axum::{ - body::{boxed, BoxBody, Full}, - http::{header::CONTENT_TYPE, StatusCode, Uri}, - response::{IntoResponse, Response}, -}; -use rust_embed::EmbeddedFile; - -use super::*; - -type StaticFileFetcher = fn(&str) -> Option; - -pub use api_doc::with_api_doc; -pub use files::fetch_static_response; -pub use rust_embed::{self, RustEmbed}; diff --git a/libs/cloud-infra/src/hosting/pages.rs b/libs/cloud-infra/src/hosting/pages.rs deleted file mode 100644 index 8301aeff1..000000000 --- a/libs/cloud-infra/src/hosting/pages.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::*; - -pub const INDEX_HTML: &str = "index.html"; - -pub fn create_404_page(message: impl AsRef) -> Response { - let message = message.as_ref(); - Response::builder() - .header("Content-Type", "text/html") - .status(if !message.is_empty() { 404 } else { 200 }) - .body(boxed( - StaticFiles::get(if cfg!(debug_assertions) { - "404-dev.html" - } else { - "404.html" - }) - .and_then(|e| { - std::str::from_utf8(e.data.as_ref()) - .map(|s| s.replace("{{ERROR_BANNER}}", message)) - .ok() - }) - .unwrap(), - )) - .unwrap() -} - -pub fn create_error_page(uri: Uri) -> Response { - if let "/" | "" = uri.path() { - create_404_page("") - } else if let "/index.html" = uri.path() { - create_404_page( - r#" - Failed to find built index for /index.html. - Make sure you've run pnpm install && pnpm build in apps/frontend to access this page. - "#, - ) - } else { - create_404_page(format!("Failed to find index file or asset (.{uri}).")) - } -} - -pub fn default_page(fetcher: Option, uri: Uri) -> Response { - fetcher - .and_then(|fetcher| { - fetcher(INDEX_HTML).map(|content| { - let body = boxed(Full::from(content.data)); - - Response::builder() - .header(CONTENT_TYPE, "text/html") - .body(body) - .unwrap() - }) - }) - .unwrap_or_else(|| { - if cfg!(debug_assertions) { - create_error_page(uri) - } else { - Response::builder() - .status(StatusCode::NOT_FOUND) - .body(boxed(Full::from("404"))) - .unwrap() - } - }) -} diff --git a/libs/cloud-infra/src/lib.rs b/libs/cloud-infra/src/lib.rs deleted file mode 100644 index 36db0faaa..000000000 --- a/libs/cloud-infra/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[forbid(unsafe_code)] -mod auth; -mod constants; -mod hosting; -mod mail; - -pub use auth::{FirebaseContext, KeyContext}; -use constants::*; -pub use hosting::{fetch_static_response, rust_embed, with_api_doc, RustEmbed}; -pub use mail::{Mail, MailContext}; -use nanoid::nanoid; diff --git a/libs/cloud-infra/src/mail.rs b/libs/cloud-infra/src/mail.rs deleted file mode 100644 index a58d5d449..000000000 --- a/libs/cloud-infra/src/mail.rs +++ /dev/null @@ -1,261 +0,0 @@ -use chrono::prelude::*; -use cloud_database::Claims; -use handlebars::{Handlebars, RenderError}; -use jwst::{warn, Base64Engine, WorkspaceMetadata, STANDARD_ENGINE}; -pub use lettre::transport::smtp::commands::Mail; -use lettre::{ - error::Error as MailConfigError, - message::{Mailbox, MultiPart, SinglePart}, - transport::smtp::{authentication::Credentials, Error as MailSmtpError}, - AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, -}; -use serde::Serialize; -use thiserror::Error; -use url::Url; - -use super::*; - -#[derive(Debug, Error)] -pub enum MailError { - #[error("Mail client not initialized")] - ClientNotInitialized, - #[error("Failed to render mail")] - RenderMail(#[from] RenderError), - #[error("Failed to config mail client")] - ConfigMail(#[from] MailConfigError), - #[error("Failed to send email")] - SendEmail(#[from] MailSmtpError), -} - -#[derive(Serialize)] -struct MailTitle { - inviter_name: String, - workspace_name: String, -} - -#[derive(Serialize)] -struct MailContent { - inviter_name: String, - site_url: String, - avatar_url: String, - workspace_name: String, - invite_code: String, - current_year: i32, - workspace_avatar: String, -} - -pub struct MailContext { - client: Option>, - mail_box: Mailbox, - template: Handlebars<'static>, -} - -impl MailContext { - pub fn new(email: Option, password: Option) -> Self { - let client = email - .and_then(|email| password.map(|password| (email, password))) - .map(|(email, password)| { - let creds = Credentials::new(email, password); - - // Open a remote connection to gmail - AsyncSmtpTransport::::relay(MAIL_PROVIDER) - .unwrap() - .credentials(creds) - .build() - }); - - if client.is_none() { - warn!("!!! no mail account provided, email will not be sent !!!"); - warn!( - "!!! please set MAIL_ACCOUNT and MAIL_PASSWORD in .env file or environmental variable to enable email \ - service !!!" - ); - } - - let mail_box = MAIL_FROM.parse().expect("should provide valid mail from"); - - let mut template = Handlebars::new(); - template - .register_template_string("MAIL_INVITE_TITLE", MAIL_INVITE_TITLE) - .expect("should provide valid email title"); - - let invite_file = StaticFiles::get("invite.html").unwrap(); - let invite_file = String::from_utf8_lossy(&invite_file.data); - template - .register_template_string("MAIL_INVITE_CONTENT", invite_file) - .expect("should provide valid email file"); - - Self { - client, - mail_box, - template, - } - } - - pub fn parse_host(&self, host: &str) -> Option { - if let Ok(url) = Url::parse(host) { - if let Some(host) = url.host_str() { - if MAIL_WHITELIST.iter().any(|&domain| host.ends_with(domain)) { - return Some(format!("https://{host}")); - } - } - } - None - } - - async fn make_invite_email_content( - &self, - metadata: WorkspaceMetadata, - site_url: String, - claims: &Claims, - invite_code: &str, - workspace_avatar: Vec, - ) -> Result<(String, MultiPart), RenderError> { - let base64_data = STANDARD_ENGINE.encode(workspace_avatar); - let workspace_avatar_url = format!("data:image/jpeg;base64,{}", base64_data); - fn string_to_color(s: &str) -> String { - let input = if s.is_empty() { "affine" } else { s }; - let mut hash: u64 = 0; - - for char in input.chars() { - hash = char as u64 + ((hash.wrapping_shl(5)).wrapping_sub(hash)); - } - - let mut color = String::from("#"); - for i in 0..3 { - let value = (hash >> (i * 8)) & 0xff; - color.push_str(&format!("{:02x}", value)); - } - - color - } - let workspace_avatar = if base64_data.is_empty() { - format!( - "
{}
", - string_to_color(&metadata.name.clone().unwrap_or_default()), - metadata - .name - .clone() - .unwrap_or_default() - .chars() - .next() - .unwrap_or_default() - ) - } else { - format!( - " \"\"", - workspace_avatar_url - ) - }; - let title = self.template.render( - "MAIL_INVITE_TITLE", - &MailTitle { - inviter_name: claims.user.name.clone(), - workspace_name: metadata.name.clone().unwrap_or_default(), - }, - )?; - - let content = self.template.render( - "MAIL_INVITE_CONTENT", - &MailContent { - inviter_name: claims.user.name.clone(), - site_url, - avatar_url: claims.user.avatar_url.to_owned().unwrap_or("".to_string()), - workspace_name: metadata.name.unwrap_or_default(), - invite_code: invite_code.to_string(), - current_year: Utc::now().year(), - workspace_avatar, - }, - )?; - - let msg_body = MultiPart::mixed() - .multipart(MultiPart::mixed().multipart(MultiPart::related().singlepart(SinglePart::html(content)))); - - Ok((title, msg_body)) - } - - async fn make_invite_email( - &self, - send_to: Mailbox, - metadata: WorkspaceMetadata, - site_url: String, - claims: &Claims, - invite_code: &str, - workspace_avatar: Vec, - ) -> Result { - let (title, msg_body) = self - .make_invite_email_content(metadata, site_url, claims, invite_code, workspace_avatar) - .await?; - - Ok(Message::builder() - .from(self.mail_box.clone()) - .to(send_to) - .subject(title) - .multipart(msg_body)?) - } - - pub async fn send_invite_email( - &self, - send_to: Mailbox, - metadata: WorkspaceMetadata, - site_url: String, - claims: &Claims, - invite_code: &str, - workspace_avatar: Vec, - ) -> Result<(), MailError> { - if let Some(client) = &self.client { - let email = self - .make_invite_email(send_to, metadata, site_url, claims, invite_code, workspace_avatar) - .await?; - - let mut retry = 3; - loop { - match client.send(email.clone()).await { - Ok(_) => return Ok(()), - // TODO: https://github.com/lettre/lettre/issues/743 - Err(e) if e.is_response() => { - if retry >= 0 { - retry -= 1; - continue; - } else { - Err(e)? - } - } - Err(e) => Err(e)?, - }; - } - } else { - Err(MailError::ClientNotInitialized) - } - } -} diff --git a/libs/jwst-binding/jwst-ffi/.gitignore b/libs/jwst-binding/jwst-ffi/.gitignore deleted file mode 100644 index 7a2cab5f8..000000000 --- a/libs/jwst-binding/jwst-ffi/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -generated -octobase \ No newline at end of file diff --git a/libs/jwst-binding/jwst-ffi/Cargo.toml b/libs/jwst-binding/jwst-ffi/Cargo.toml deleted file mode 100644 index 8eb627076..000000000 --- a/libs/jwst-binding/jwst-ffi/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "jwst-ffi" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -lib0 = "0.16.5" -yrs = "0.16.5" - -# ======= workspace dependencies ======= -jwst = { workspace = true } - -[lib] -crate-type = ["staticlib"] - -[build-dependencies] -cbindgen = "^0.24.3" diff --git a/libs/jwst-binding/jwst-ffi/binding.h b/libs/jwst-binding/jwst-ffi/binding.h deleted file mode 100644 index 80dff3961..000000000 --- a/libs/jwst-binding/jwst-ffi/binding.h +++ /dev/null @@ -1,105 +0,0 @@ - -#ifndef JWST_FFI_H -#define JWST_FFI_H -typedef struct JWSTWorkspace {} JWSTWorkspace; -typedef struct JWSTBlock {} JWSTBlock; -typedef struct YTransaction {} YTransaction; - - -#include -#include -#include -#include - -#define BLOCK_TAG_NUM 1 - -#define BLOCK_TAG_INT 2 - -#define BLOCK_TAG_BOOL 3 - -#define BLOCK_TAG_STR 4 - -typedef struct BlockChildren { - uintptr_t len; - char **data; -} BlockChildren; - -typedef union BlockValue { - double num; - int64_t int; - bool bool; - char *str; -} BlockValue; - -typedef struct BlockContent { - int8_t tag; - union BlockValue value; -} BlockContent; - -JWSTBlock *block_new(const JWSTWorkspace *workspace, - const char *block_id, - const char *flavour, - uint64_t operator_); - -void block_destroy(JWSTBlock *block); - -uint64_t block_get_created(const JWSTBlock *block); - -uint64_t block_get_updated(const JWSTBlock *block); - -char *block_get_flavour(const JWSTBlock *block); - -struct BlockChildren *block_get_children(const JWSTBlock *block); - -void block_push_children(const JWSTBlock *block, YTransaction *trx, const JWSTBlock *child); - -void block_insert_children_at(const JWSTBlock *block, - YTransaction *trx, - const JWSTBlock *child, - uint32_t pos); - -void block_insert_children_before(const JWSTBlock *block, - YTransaction *trx, - const JWSTBlock *child, - const char *reference); - -void block_insert_children_after(const JWSTBlock *block, - YTransaction *trx, - const JWSTBlock *child, - const char *reference); - -void block_children_destroy(struct BlockChildren *children); - -struct BlockContent *block_get_content(const JWSTBlock *block, const char *key); - -void block_set_content(JWSTBlock *block, - const char *key, - YTransaction *trx, - struct BlockContent content); - -void block_content_destroy(struct BlockContent *content); - -JWSTWorkspace *workspace_new(const char *id); - -void workspace_destroy(JWSTWorkspace *workspace); - -JWSTBlock *workspace_get_block(const JWSTWorkspace *workspace, const char *block_id); - -JWSTBlock *workspace_create_block(const JWSTWorkspace *workspace, - const char *block_id, - const char *flavour); - -bool workspace_remove_block(const JWSTWorkspace *workspace, const char *block_id); - -bool workspace_exists_block(const JWSTWorkspace *workspace, const char *block_id); - -void trx_commit(YTransaction *trx); - -Subscription *workspace_observe(JWSTWorkspace *workspace, - void *env, - void (*func)(void*, const YTransaction*, const UpdateEvent*)); - -void workspace_unobserve(Subscription *subscription); - - -#endif diff --git a/libs/jwst-binding/jwst-ffi/build.rs b/libs/jwst-binding/jwst-ffi/build.rs deleted file mode 100644 index 901e6ca6f..000000000 --- a/libs/jwst-binding/jwst-ffi/build.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::{collections::HashMap, env, path::PathBuf}; - -fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let output = PathBuf::from( - env::var("CARGO_TARGET_DIR").unwrap_or(env::var("CARGO_MANIFEST_DIR").unwrap()), - ) - .join("binding.h"); - - let rename = { - let mut map = HashMap::new(); - - map.insert("Workspace".to_string(), "JWSTWorkspace".to_string()); - map.insert("Block".to_string(), "JWSTBlock".to_string()); - map.insert("Transaction".to_string(), "YTransaction".to_string()); - - map - }; - - cbindgen::Builder::new() - .with_crate(crate_dir) - .with_config(cbindgen::Config { - language: cbindgen::Language::C, - header: Some(String::from( - r#" -#ifndef JWST_FFI_H -#define JWST_FFI_H -typedef struct JWSTWorkspace {} JWSTWorkspace; -typedef struct JWSTBlock {} JWSTBlock; -typedef struct YTransaction {} YTransaction; -"#, - )), - trailer: Some(String::from( - r#" -#endif -"#, - )), - export: cbindgen::ExportConfig { - rename, - ..Default::default() - }, - ..Default::default() - }) - .generate() - .expect("Unable to generate bindings") - .write_to_file(output); -} diff --git a/libs/jwst-binding/jwst-ffi/src/lib.rs b/libs/jwst-binding/jwst-ffi/src/lib.rs deleted file mode 100644 index da48e2778..000000000 --- a/libs/jwst-binding/jwst-ffi/src/lib.rs +++ /dev/null @@ -1,340 +0,0 @@ -use jwst::{Block, Workspace}; -use lib0::any::Any; -use std::{ - ffi::{c_void, CStr, CString}, - mem::forget, - os::raw::c_char, - ptr, -}; -use yrs::{Subscription, Transaction, UpdateEvent}; - -#[no_mangle] -pub unsafe extern "C" fn block_new( - workspace: *const Workspace, - block_id: *const c_char, - flavour: *const c_char, - operator: u64, -) -> *mut Block { - Box::into_raw(Box::new(Block::new( - workspace.as_ref().unwrap(), - CStr::from_ptr(block_id).to_str().unwrap(), - CStr::from_ptr(flavour).to_str().unwrap(), - operator, - ))) -} - -#[no_mangle] -pub unsafe extern "C" fn block_destroy(block: *mut Block) { - drop(Box::from_raw(block)); -} - -#[no_mangle] -pub unsafe extern "C" fn block_get_created(block: *const Block) -> u64 { - block.as_ref().unwrap().created() -} - -#[no_mangle] -pub unsafe extern "C" fn block_get_updated(block: *const Block) -> u64 { - block.as_ref().unwrap().updated() -} - -#[no_mangle] -pub unsafe extern "C" fn block_get_flavour(block: *const Block) -> *mut c_char { - CString::new(block.as_ref().unwrap().flavour()) - .unwrap() - .into_raw() -} - -#[repr(C)] -pub struct BlockChildren { - len: usize, - data: *mut *mut c_char, -} - -#[no_mangle] -pub unsafe extern "C" fn block_get_children(block: *const Block) -> *mut BlockChildren { - let mut child: Box<[*mut c_char]> = block - .as_ref() - .unwrap() - .children() - .into_iter() - .map(|id| CString::new(id).unwrap().into_raw()) - .collect(); - let len = child.len(); - let data = child.as_mut_ptr(); - - forget(child); - - Box::into_raw(BlockChildren { len, data }.into()) -} - -#[no_mangle] -pub unsafe extern "C" fn block_push_children( - block: *const Block, - trx: *mut Transaction, - child: *const Block, -) { - let block = block.as_ref().unwrap(); - let trx = trx.as_mut().unwrap(); - let child = child.as_ref().unwrap(); - - block.push_children(trx, child); -} - -#[no_mangle] -pub unsafe extern "C" fn block_insert_children_at( - block: *const Block, - trx: *mut Transaction, - child: *const Block, - pos: u32, -) { - let block = block.as_ref().unwrap(); - let trx = trx.as_mut().unwrap(); - let child = child.as_ref().unwrap(); - - block.insert_children_at(trx, child, pos); -} - -#[no_mangle] -pub unsafe extern "C" fn block_insert_children_before( - block: *const Block, - trx: *mut Transaction, - child: *const Block, - reference: *const c_char, -) { - let block = block.as_ref().unwrap(); - let trx = trx.as_mut().unwrap(); - let child = child.as_ref().unwrap(); - let reference = CStr::from_ptr(reference).to_str().unwrap(); - - block.insert_children_before(trx, child, reference); -} - -#[no_mangle] -pub unsafe extern "C" fn block_insert_children_after( - block: *const Block, - trx: *mut Transaction, - child: *const Block, - reference: *const c_char, -) { - let block = block.as_ref().unwrap(); - let trx = trx.as_mut().unwrap(); - let child = child.as_ref().unwrap(); - let reference = CStr::from_ptr(reference).to_str().unwrap(); - - block.insert_children_after(trx, child, reference); -} - -#[no_mangle] -pub unsafe extern "C" fn block_children_destroy(children: *mut BlockChildren) { - let children = children.as_mut().unwrap(); - let vec = Vec::from_raw_parts(children.data, children.len, children.len); - - for item in vec { - let str = CString::from_raw(item); - drop(str) - } -} - -pub const BLOCK_TAG_NUM: i8 = 1; -pub const BLOCK_TAG_INT: i8 = 2; -pub const BLOCK_TAG_BOOL: i8 = 3; -pub const BLOCK_TAG_STR: i8 = 4; - -#[repr(C)] -pub struct BlockContent { - tag: i8, - value: BlockValue, -} -#[repr(C)] -union BlockValue { - num: f64, - int: i64, - bool: bool, - str: *mut c_char, -} - -impl From for Any { - fn from(content: BlockContent) -> Self { - unsafe { - match content.tag { - BLOCK_TAG_BOOL => Self::Bool(content.value.bool), - BLOCK_TAG_NUM => Self::Number(content.value.num), - BLOCK_TAG_INT => Self::BigInt(content.value.int), - BLOCK_TAG_STR => Self::String( - CString::from_raw(content.value.str) - .into_string() - .unwrap() - .into_boxed_str(), - ), - _ => unreachable!("invalid tag value"), - } - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn block_get_content( - block: *const Block, - key: *const c_char, -) -> *mut BlockContent { - let res = block - .as_ref() - .unwrap() - .get(CStr::from_ptr(key).to_str().unwrap()); - - if let Some(res) = res { - match res { - Any::String(str) => Box::into_raw( - BlockContent { - tag: BLOCK_TAG_STR, - value: BlockValue { - str: CString::new(str.to_string()).unwrap().into_raw(), - }, - } - .into(), - ), - Any::Bool(b) => Box::into_raw( - BlockContent { - tag: BLOCK_TAG_BOOL, - value: BlockValue { bool: b }, - } - .into(), - ), - Any::Number(num) => Box::into_raw( - BlockContent { - tag: BLOCK_TAG_NUM, - value: BlockValue { num }, - } - .into(), - ), - Any::BigInt(int) => Box::into_raw( - BlockContent { - tag: BLOCK_TAG_INT, - value: BlockValue { int }, - } - .into(), - ), - Any::Null | Any::Undefined | Any::Array(_) | Any::Buffer(_) | Any::Map(_) => { - ptr::null_mut() - } - } - } else { - ptr::null_mut() - } -} - -#[no_mangle] -pub unsafe extern "C" fn block_set_content( - block: *mut Block, - key: *const c_char, - trx: *mut Transaction, - content: BlockContent, -) { - let block = block.as_mut().unwrap(); - let trx = trx.as_mut().unwrap(); - let key = CStr::from_ptr(key).to_str().unwrap(); - - let value = content; - - block.set(trx, key, value); -} - -#[no_mangle] -pub unsafe extern "C" fn block_content_destroy(content: *mut BlockContent) { - let content = Box::from_raw(content); - if content.tag == BLOCK_TAG_STR { - let str = content.value.str; - drop(CString::from_raw(str)); - } -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_new(id: *const c_char) -> *mut Workspace { - Box::into_raw(Box::new(Workspace::new( - CStr::from_ptr(id).to_str().unwrap(), - ))) -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_destroy(workspace: *mut Workspace) { - drop(Box::from_raw(workspace)); -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_get_block( - workspace: *const Workspace, - block_id: *const c_char, -) -> *mut Block { - let block = workspace - .as_ref() - .unwrap() - .get(CStr::from_ptr(block_id).to_str().unwrap()); - - if let Some(block) = block { - Box::leak(Box::new(block)) - } else { - ptr::null_mut() - } -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_create_block( - workspace: *const Workspace, - block_id: *const c_char, - flavour: *const c_char, -) -> *mut Block { - let block_id = CStr::from_ptr(block_id).to_str().unwrap(); - let flavour = CStr::from_ptr(flavour).to_str().unwrap(); - let block = workspace - .as_ref() - .unwrap() - .with_trx(|t| t.create(block_id, flavour)); - - Box::into_raw(Box::new(block)) -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_remove_block( - workspace: *const Workspace, - block_id: *const c_char, -) -> bool { - workspace - .as_ref() - .unwrap() - .with_trx(|mut t| t.remove(CStr::from_ptr(block_id).to_str().unwrap())) -} -#[no_mangle] -pub unsafe extern "C" fn workspace_exists_block( - workspace: *const Workspace, - block_id: *const c_char, -) -> bool { - workspace - .as_ref() - .unwrap() - .exists(CStr::from_ptr(block_id).to_str().unwrap()) -} - -#[no_mangle] -pub unsafe extern "C" fn trx_commit(trx: *mut Transaction) { - drop(Box::from_raw(trx)) -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_observe( - workspace: *mut Workspace, - env: *mut c_void, - func: extern "C" fn(*mut c_void, *const Transaction, *const UpdateEvent), -) -> *mut Subscription { - Box::into_raw(Box::new( - workspace - .as_mut() - .unwrap() - .observe(move |tx, upd| func(env, tx, upd)), - )) -} - -#[no_mangle] -pub unsafe extern "C" fn workspace_unobserve(subscription: *mut Subscription) { - drop(Box::from_raw(subscription)) -} diff --git a/libs/jwst-binding/jwst-py/.gitignore b/libs/jwst-binding/jwst-py/.gitignore deleted file mode 100644 index a25e63cfe..000000000 --- a/libs/jwst-binding/jwst-py/.gitignore +++ /dev/null @@ -1,73 +0,0 @@ -/target -.github - -# Byte-compiled / optimized / DLL files -__pycache__/ -.pytest_cache/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -.venv/ -env/ -bin/ -build/ -develop-eggs/ -dist/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -include/ -man/ -venv/ -*.egg-info/ -.installed.cfg -*.egg - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt -pip-selfcheck.json - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.cache -nosetests.xml -coverage.xml - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Rope -.ropeproject - -# Django stuff: -*.log -*.pot - -.DS_Store - -# Sphinx documentation -docs/_build/ - -# PyCharm -.idea/ - -# VSCode -.vscode/ - -# Pyenv -.python-version \ No newline at end of file diff --git a/libs/jwst-binding/jwst-py/Cargo.toml b/libs/jwst-binding/jwst-py/Cargo.toml deleted file mode 100644 index de1fea7d9..000000000 --- a/libs/jwst-binding/jwst-py/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "jwst-py" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "jwst_py" -crate-type = ["cdylib"] - - -[dependencies] -pyo3 = { version = "0.17.3" } - -# ======= workspace dependencies ======= -jwst = { workspace = true } -yrs = "=0.12.0" diff --git a/libs/jwst-binding/jwst-py/pyproject.toml b/libs/jwst-binding/jwst-py/pyproject.toml deleted file mode 100644 index 536fbdfad..000000000 --- a/libs/jwst-binding/jwst-py/pyproject.toml +++ /dev/null @@ -1,14 +0,0 @@ -[build-system] -requires = ["maturin>=0.14,<0.15"] -build-backend = "maturin" - -[project] -name = "jwst-py" -requires-python = ">=3.7" -classifiers = [ - "Programming Language :: Rust", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", -] - - diff --git a/libs/jwst-binding/jwst-py/src/lib.rs b/libs/jwst-binding/jwst-py/src/lib.rs deleted file mode 100644 index f3c29b075..000000000 --- a/libs/jwst-binding/jwst-py/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -use jwst::{Block as JwstBlock, Workspace as JwstWorkspace}; -use pyo3::prelude::*; -use pyo3::types::PyList; -use yrs::{Doc as YrsDoc, Map as YrsMap, Subscription as YrsSubscription, UpdateEvent}; - -#[pyclass(subclass)] -pub struct Doc(YrsDoc); - -#[pyclass(subclass, unsendable)] -pub struct Map(YrsMap); - -#[pyclass(subclass, unsendable)] -pub struct Subscription(YrsSubscription); - -#[pyclass(subclass)] -struct Workspace(JwstWorkspace); - -#[pymethods] -impl Workspace { - #[new] - fn new(id: String) -> Self { - Self(JwstWorkspace::new(id)) - } - - #[getter] - fn id(&self) -> String { - self.0.id().clone() - } - - #[getter] - fn client_id(&self) -> f64 { - self.0.client_id() as f64 - } - - pub fn create(&self, block_id: String, flavour: String) -> Block { - Block(self.0.with_trx(|mut t| t.create(block_id, &flavour))) - } - - pub fn get(&self, block_id: String) -> Option { - self.0.get(block_id).map(|b| Block(b)) - } - - pub fn block_count(&self) -> u32 { - self.0.block_count() - } - - pub fn block_iter(&self) -> Vec { - self.0.block_iter().map(|b| Block(b)).collect() - } - - pub fn exists(&self, block_id: String) -> bool { - self.0.exists(&block_id) - } - - pub fn observe(&mut self, callback: PyObject) -> Subscription { - Subscription(self.0.observe(move |_, u| { - Python::with_gil(|py| { - let update = PyList::new(py, u.update.as_slice()); - callback.call1(py, (update,)).unwrap(); - }) - })) - } -} - -#[pyclass(subclass)] -pub struct Block(JwstBlock); - -#[pymethods] -impl Block { - pub fn id(&self) -> String { - self.0.id().to_string() - } - - pub fn client_id(&self) -> Vec { - self.0.children().into_iter().collect::>() - } -} - -/// A Python module implemented in Rust. -#[pymodule] -fn jwst_py(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - Ok(()) -} diff --git a/libs/jwst-binding/jwst-py/tests/test_workspace.py b/libs/jwst-binding/jwst-py/tests/test_workspace.py deleted file mode 100644 index 869c1ab2a..000000000 --- a/libs/jwst-binding/jwst-py/tests/test_workspace.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest -from jwst_py import Workspace - - -def test_workspace(): - ws = Workspace("test") - assert ws.id == "test" - assert ws.block_count() == 0 - - ws.create("affine", "test") - ws.create("affine", "test1") - - assert ws.block_count() == 1 - assert ws.exists("affine") == True - - ws.create("jwst", "test") - ws.create("jwst", "test1") - assert ws.block_count() == 2 - assert ws.exists("jwst") == True diff --git a/libs/jwst-binding/jwst-swift/Cargo.toml b/libs/jwst-binding/jwst-swift/Cargo.toml index 61d78f551..61a4d77e3 100644 --- a/libs/jwst-binding/jwst-swift/Cargo.toml +++ b/libs/jwst-binding/jwst-swift/Cargo.toml @@ -29,7 +29,7 @@ swift-bridge-build = "0.1.51" [dev-dependencies] reqwest = { version = "0.11.14", default-features = false, features = [ - "json", - "rustls-tls", + "json", + "rustls-tls", ] } regex = "1.7.1" diff --git a/libs/jwst-binding/jwst-wasm/Cargo.toml b/libs/jwst-binding/jwst-wasm/Cargo.toml index fa2cdbdf1..d4e1aac61 100644 --- a/libs/jwst-binding/jwst-wasm/Cargo.toml +++ b/libs/jwst-binding/jwst-wasm/Cargo.toml @@ -6,21 +6,18 @@ edition = "2021" license = "AGPL-3.0-only" [features] -default = ["custom_alloc", "log_panic"] -custom_alloc = ["wee_alloc"] +default = ["log_panic"] log_panic = ["console_error_panic_hook"] - [dependencies] cfg-if = "1.0.0" console_error_panic_hook = { version = "0.1.7", optional = true } -js-sys = "0.3.61" -wasm-bindgen = "^0.2.83" -wee_alloc = { version = "^0.4.5", optional = true } +getrandom = { version = "0.2", features = ["js"] } +js-sys = "0.3.64" +wasm-bindgen = "0.2.87" # ======= workspace dependencies ======= -jwst = { workspace = true } -yrs = "=0.12.0" +jwst-core = { workspace = true } [lib] crate-type = ["cdylib"] diff --git a/libs/jwst-binding/jwst-wasm/src/lib.rs b/libs/jwst-binding/jwst-wasm/src/lib.rs index 10953b413..32d6cd7a7 100644 --- a/libs/jwst-binding/jwst-wasm/src/lib.rs +++ b/libs/jwst-binding/jwst-wasm/src/lib.rs @@ -1,14 +1,6 @@ -use js_sys::Uint8Array; -use jwst::{Block as JwstBlock, Workspace as JwstWorkspace}; +use jwst_core::{Block as JwstBlock, Workspace as JwstWorkspace}; use wasm_bindgen::{prelude::*, JsCast}; -use yrs::{Subscription as YrsSubscription, UpdateEvent}; -cfg_if::cfg_if! { - if #[cfg(feature = "custom_alloc")] { - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - } -} cfg_if::cfg_if! { if #[cfg(feature = "log_panic")] { #[wasm_bindgen(js_name = setPanicHook)] @@ -18,9 +10,6 @@ cfg_if::cfg_if! { } } -#[wasm_bindgen] -pub struct Subscription(YrsSubscription); - #[wasm_bindgen] pub struct Workspace(JwstWorkspace); @@ -28,7 +17,7 @@ pub struct Workspace(JwstWorkspace); impl Workspace { #[wasm_bindgen(constructor)] pub fn new(id: String) -> Self { - Self(JwstWorkspace::new(id)) + Self(JwstWorkspace::new(id).unwrap()) } #[wasm_bindgen(js_name = clientId, getter)] @@ -38,28 +27,25 @@ impl Workspace { /// Create and return a `Block` instance #[wasm_bindgen] - pub fn create(&self, block_id: String, flavour: String) -> Block { - Block(self.0.with_trx(|mut t| t.create(block_id, &flavour))) + pub fn create(&mut self, block_id: String, flavour: String) -> Block { + Block( + self.0 + .get_blocks() + .and_then(|mut s| s.create(block_id, &flavour)) + .unwrap(), + ) } /// Return a `Block` instance if block exists #[wasm_bindgen] - pub fn get(&self, block_id: String) -> Option { - self.0.get(block_id).map(|b| Block(b)) + pub fn get(mut self, block_id: String) -> Option { + self.0.get_blocks().ok().and_then(|s| s.get(block_id).map(Block)) } /// Check is a block exists #[wasm_bindgen] - pub fn exists(&self, block_id: String) -> bool { - self.0.exists(&block_id) - } - - #[wasm_bindgen] - pub fn observe(&mut self, f: js_sys::Function) -> Subscription { - Subscription(self.0.observe(move |_, u| { - let update = Uint8Array::from(u.update.as_slice()); - f.call1(&JsValue::UNDEFINED, &update).unwrap(); - })) + pub fn exists(&mut self, block_id: String) -> bool { + self.0.get_blocks().map(|s| s.exists(&block_id)).unwrap_or_default() } } diff --git a/libs/jwst-codec/Cargo.toml b/libs/jwst-codec/Cargo.toml index d9df07e9e..a5ff78310 100644 --- a/libs/jwst-codec/Cargo.toml +++ b/libs/jwst-codec/Cargo.toml @@ -2,32 +2,32 @@ authors = ["DarkSky "] edition = "2021" license = "AGPL-3.0-only" -name = "jwst-codec" +name = "jwst-codec" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitvec = "1.0.1" -byteorder = "1.4.3" -nanoid = "0.4.0" -nom = "7.1.3" +bitvec = "1.0.1" +byteorder = "1.4.3" +nanoid = "0.4.0" +nom = "7.1.3" ordered-float = "3.6.0" -rand = "0.8.5" -rand_chacha = "0.3.1" -serde = { version = "1.0.183" } -serde_json = "1.0.105" -thiserror = "1.0.40" +rand = "0.8.5" +rand_chacha = "0.3.1" +serde = { version = "1.0.183" } +serde_json = "1.0.105" +thiserror = "1.0.40" # ======= workspace dependencies ======= jwst-logger = { workspace = true } [features] -bench = [] +bench = [] large_refs = [] [target.'cfg(fuzzing)'.dependencies] -arbitrary = { version = "1.3.0", features = ["derive"] } +arbitrary = { version = "1.3.0", features = ["derive"] } ordered-float = { version = "3.6.0", features = ["arbitrary"] } [target.'cfg(loom)'.dependencies] @@ -35,14 +35,14 @@ loom = { version = "0.6", features = ["checkpoint"] } [dev-dependencies] assert-json-diff = "2.0.2" -criterion = { version = "0.5.1", features = ["html_reports"] } -lib0 = { version = "0.16.5", features = ["lib0-serde"] } -ordered-float = { version = "3.6.0", features = ["proptest"] } -path-ext = "0.1.0" -proptest = "1.1.0" -proptest-derive = "0.3.0" -y-sync = { git = "https://github.com/toeverything/y-sync", rev = "aeb0010" } -yrs = "0.16.5" +criterion = { version = "0.5.1", features = ["html_reports"] } +lib0 = { version = "0.16.5", features = ["lib0-serde"] } +ordered-float = { version = "3.6.0", features = ["proptest"] } +path-ext = "0.1.0" +proptest = "1.1.0" +proptest-derive = "0.3.0" +y-sync = { git = "https://github.com/toeverything/y-sync", rev = "aeb0010" } +yrs = "0.16.5" [[bin]] name = "memory_leak_test" @@ -50,23 +50,23 @@ path = "bin/memory_leak_test.rs" [[bench]] harness = false -name = "array_ops_benchmarks" +name = "array_ops_benchmarks" [[bench]] harness = false -name = "codec_benchmarks" +name = "codec_benchmarks" [[bench]] harness = false -name = "map_ops_benchmarks" +name = "map_ops_benchmarks" [[bench]] harness = false -name = "text_ops_benchmarks" +name = "text_ops_benchmarks" [[bench]] harness = false -name = "update_benchmarks" +name = "update_benchmarks" [lib] bench = true diff --git a/libs/jwst-codec/src/doc/types/array.rs b/libs/jwst-codec/src/doc/types/array.rs index f68123d66..5f1b1aeaa 100644 --- a/libs/jwst-codec/src/doc/types/array.rs +++ b/libs/jwst-codec/src/doc/types/array.rs @@ -83,8 +83,6 @@ impl serde::Serialize for Array { #[cfg(test)] mod tests { - use yrs::{Array, Options, Text, Transact}; - use super::*; #[test] @@ -108,6 +106,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] fn test_ytext_equal() { + use yrs::{Options, Text, Transact}; let options = DocOptions::default(); let yrs_options = Options::default(); @@ -166,6 +165,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] fn test_yrs_array_decode() { + use yrs::{Array, Transact}; let update = { let doc = yrs::Doc::new(); let array = doc.get_or_insert_array("abc"); diff --git a/libs/jwst-core/Cargo.toml b/libs/jwst-core/Cargo.toml index ec5b04b23..1f202fbf7 100644 --- a/libs/jwst-core/Cargo.toml +++ b/libs/jwst-core/Cargo.toml @@ -21,7 +21,6 @@ serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" thiserror = "1.0.40" tracing = { version = "0.1.37", features = ["log"] } -tokio = { version = "1.27.0", features = ["sync", "time"] } # ======= workspace dependencies ======= jwst-codec = { workspace = true } diff --git a/libs/jwst-core/src/workspaces/observe.rs b/libs/jwst-core/src/workspaces/observe.rs index 431d20d58..5034fa855 100644 --- a/libs/jwst-core/src/workspaces/observe.rs +++ b/libs/jwst-core/src/workspaces/observe.rs @@ -4,6 +4,6 @@ use super::*; impl Workspace { pub async fn on_awareness_update(&mut self, f: impl Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static) { - self.awareness.write().await.on_update(f); + self.awareness.write().unwrap().on_update(f); } } diff --git a/libs/jwst-core/src/workspaces/sync.rs b/libs/jwst-core/src/workspaces/sync.rs index 1edc82fa6..bb995069a 100644 --- a/libs/jwst-core/src/workspaces/sync.rs +++ b/libs/jwst-core/src/workspaces/sync.rs @@ -11,7 +11,7 @@ impl Workspace { Ok(self.doc.encode_state_as_update_v1(&StateVector::default())?) } - pub async fn sync_init_message(&self) -> JwstResult> { + pub fn sync_init_message(&self) -> JwstResult> { let mut buffer = Vec::new(); write_sync_message( @@ -24,13 +24,13 @@ impl Workspace { )?; write_sync_message( &mut buffer, - &SyncMessage::Awareness(self.awareness.read().await.get_states().clone()), + &SyncMessage::Awareness(self.awareness.read().unwrap().get_states().clone()), )?; Ok(buffer) } - pub async fn sync_messages(&mut self, buffers: Vec>) -> Vec> { + pub fn sync_messages(&mut self, buffers: Vec>) -> Vec> { let mut awareness = vec![]; let mut content = vec![]; @@ -52,16 +52,16 @@ impl Workspace { let mut result = vec![]; - result.extend(self.sync_awareness(awareness).await); + result.extend(self.sync_awareness(awareness)); result.extend(self.sync_content(content)); result } - async fn sync_awareness(&mut self, msgs: Vec) -> Vec> { + fn sync_awareness(&mut self, msgs: Vec) -> Vec> { let mut result = vec![]; if !msgs.is_empty() { - let mut awareness = self.awareness.write().await; + let mut awareness = self.awareness.write().unwrap(); for msg in msgs { match msg { SyncMessage::AwarenessQuery => { diff --git a/libs/jwst-core/src/workspaces/workspace.rs b/libs/jwst-core/src/workspaces/workspace.rs index 5cc93fe43..9e23c44d0 100644 --- a/libs/jwst-core/src/workspaces/workspace.rs +++ b/libs/jwst-core/src/workspaces/workspace.rs @@ -1,8 +1,7 @@ -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use jwst_codec::{Awareness, Doc, Map}; use serde::{ser::SerializeMap, Serialize, Serializer}; -use tokio::sync::RwLock; use super::*; diff --git a/libs/jwst-logger/Cargo.toml b/libs/jwst-logger/Cargo.toml index 5b21421cd..8e1bae960 100644 --- a/libs/jwst-logger/Cargo.toml +++ b/libs/jwst-logger/Cargo.toml @@ -11,8 +11,8 @@ chrono = "0.4.23" nu-ansi-term = "0.46.0" tracing = { version = "0.1.37", features = ["log"] } tracing-log = { version = "0.1.3", features = [ - "log-tracer", - "std", + "log-tracer", + "std", ], default-features = false } tracing-stackdriver = "0.6.2" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } diff --git a/libs/jwst-rpc/Cargo.toml b/libs/jwst-rpc/Cargo.toml index 71069feb1..8e3230617 100644 --- a/libs/jwst-rpc/Cargo.toml +++ b/libs/jwst-rpc/Cargo.toml @@ -27,15 +27,15 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] } # ======== websocket dependencies ======== axum = { version = "0.6.16", features = ["ws"], optional = true } tokio-tungstenite = { version = "0.20.0", features = [ - "rustls-tls-webpki-roots", + "rustls-tls-webpki-roots", ], optional = true } url = { version = "2.3.1", optional = true } # ======== webrtc dependencies ======== bytes = { version = "1.4", optional = true } reqwest = { version = "0.11.18", default-features = false, features = [ - "json", - "rustls-tls", + "json", + "rustls-tls", ], optional = true } webrtcrs = { package = "webrtc", version = "0.8.0", optional = true } diff --git a/libs/jwst-rpc/src/context.rs b/libs/jwst-rpc/src/context.rs index e0fc8adcd..73c644aa8 100644 --- a/libs/jwst-rpc/src/context.rs +++ b/libs/jwst-rpc/src/context.rs @@ -185,7 +185,7 @@ pub trait RpcContextImpl<'a> { let updates = std::mem::take(&mut updates); let updates_len = updates.len(); let ts = Instant::now(); - let message = workspace.sync_messages(updates).await; + let message = workspace.sync_messages(updates); if ts.elapsed().as_micros() > 50 { debug!( "apply {updates_len} remote update cost: {}ms", diff --git a/libs/jwst-rpc/src/handler.rs b/libs/jwst-rpc/src/handler.rs index 8b6dc5eea..7eceb11e1 100644 --- a/libs/jwst-rpc/src/handler.rs +++ b/libs/jwst-rpc/src/handler.rs @@ -48,7 +48,7 @@ pub async fn handle_connector( let mut server_rx = context.join_server_broadcast(&workspace_id).await; // Send initialization message. - match ws.sync_init_message().await { + match ws.sync_init_message() { Ok(init_data) => { debug!("send init data:{:?}", init_data); if tx.send(Message::Binary(init_data)).await.is_err() { diff --git a/libs/jwst-storage/Cargo.toml b/libs/jwst-storage/Cargo.toml index b9722e2e2..af1225957 100644 --- a/libs/jwst-storage/Cargo.toml +++ b/libs/jwst-storage/Cargo.toml @@ -31,8 +31,8 @@ url = "2.4.0" # ======== bucket dependencies ======= opendal = { version = "0.39.0", default-features = false, features = [ - "rustls", - "services-s3", + "rustls", + "services-s3", ], optional = true } dotenvy = { version = "0.15.7", optional = true } diff --git a/libs/jwst/Cargo.toml b/libs/jwst/Cargo.toml deleted file mode 100644 index 9592b1257..000000000 --- a/libs/jwst/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "jwst" -version = "0.1.1" -authors = ["DarkSky "] -edition = "2021" -license = "AGPL-3.0-only" - -[features] -workspace-search = ["dep:tantivy"] -workspace-auto-subscribe = [] -default = ["workspace-search"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -async-trait = "0.1.68" -base64 = "0.21.0" -bytes = "1.4.0" -cang-jie = "0.15.0" -chrono = "0.4.24" -convert_case = "0.6.0" -futures = "0.3.28" -lib0 = { version = "0.16.5", features = ["lib0-serde"] } -nanoid = "0.4.0" -utoipa = "3.3.0" -schemars = "0.8.12" -serde = { version = "1.0.160", features = ["derive"] } -serde_json = "1.0.96" -thiserror = "1.0.40" -tracing = { version = "0.1.37", features = ["log"] } -type-map = "0.5.0" -tantivy = { version = "0.19.2", optional = true } -tokio = { version = "1.27.0", features = [ - "sync", - "time", - "rt", - "rt-multi-thread", - "net", -] } -yrs = "0.16.5" - -# ======= workspace dependencies ======= -jwst-codec = { workspace = true } - -[dev-dependencies] -assert-json-diff = "2.0.2" - -[build-dependencies] -vergen = { version = "7.5.1", default-features = false, features = [ - "cargo", - "git", - "rustc", -] } diff --git a/libs/jwst/build.rs b/libs/jwst/build.rs deleted file mode 100644 index b72de18b7..000000000 --- a/libs/jwst/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -use vergen::{vergen, Config}; - -fn main() { - vergen(Config::default()).unwrap(); -} diff --git a/libs/jwst/fixtures/test_multi_layer.bin b/libs/jwst/fixtures/test_multi_layer.bin deleted file mode 100644 index 949878530b36acacc96b1ca6bbb7ff312781e06f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2520 zcma)8+fLg+5Y6TS{f7QSzolM#$MG6REeI()vpZ+z%$W)3`p?hbkA=hm%9%4Pk@l2WI5WdzVkWi| zz7M_eKcJ){Bgbq1+A zB8yw*!faEnIXA1Pn5W*6is!m*ASYb(f!~y3mONN(iKwcHz?An)x@u2dj=@-Fvh>oas!LxlZN(y`3l%TMhHh)}4^Jv7IU-RJuZhQ1bknkO@JtlGp$ zcXW+pBwf`FXZv-pPJh?VtMktJ+S%)tyXC-{y6Im8KG)Oh^`fcQ;!>}pdB&$D`$3T= zr7KtEe%e{XJ-@Zwb0qU<-1=rcHb5HPSZOp^MqOSS(V1uJ=<@0ate!et&gft!m?HWq zzH8j6#+~Yr#NK*p-+9r_=vs3!S9CwarTyro8!8;+DhKij4?oN1nAc}xGht02Y!FLW zlch1>345~ASuTTE#s>a8(ayqly3WuOtOvN@&IfYR)Whm{&)3o1T!#pC3}fpMSslaw zt78~j$M9(#*sBBPA1%gg6l`VVqiVo`2&rX z$G^VOpYL%PB4A9z7&XAqHekRUYYf9i!4^oztO$n8VblTRegj6Gy}I`?KqqFZT&Z?E z60J1-zhbijS^%+(RX~d=VEKOvSn>*h$4mk12D7x6A!+zMzuZ zEUQqXgk3&lv)vMnvkw^()?1B; z3tZOc04^;_d|H=bM*N{*1je%w!$#;npFe0!@`StwTrJ>H91iQI-fHUMHk*t-NvMLW zmDHI!{E?__^hiFF&rFb)8qVOj*Wcy$8m3J`Ul@TF!)wFoi%6&GLzmbjXVP4HRBzEE z36V=V2Szj3wHX~9q4ZeD?C`oh;Xw1;tP)R2C%DdXf#aB-rn=5x7(rjB}c& zp)*jBpI)4u*$L+`PhK=M(jA%P_lloKB!2dF=v|>sQ-C8Apr_5>;Wdq*K!=CTPV4Zu`Xgt~yWKv~VW4hPu@G*2*iil7$LVin#*?S*00s z8)DT>oNnQes6X0ah+%GzY3Na-1pkApisJEa3$72lXskQZ*^*f8a&H9PF^caN;C52qGnQYX6aKjBV9St*?VbSg|Mnf%X z#G^Ev3lLGb0Rc2XYG($_CASxz;g8G+g`%`&AkGGK<5^?>m+gG!n&%W{*UnB48^wt; z2d*<5PiFemjFP5`g2qhiWP)pp^JLNV_U_rW>9ecq*ktEAPS#K~d5Yl=Rh7rts659} zp_ZP!p4|LgofB(x992`*oS)mAS3A9c%{9|;l%5|fHZr?IlV`9|b&jJNLV4BRZd0!a zvr$!!qq@pUn%V;Oz9}I#>Q|1V%(^M{0#9pb( z-Pu&xrnjdD1Fghos62O4$n^|OK6lBn*P=Nz`N)l9whXn$-j&usMTP+zowC9}c~@!W zj24~fjFfbwkkc8NOlLr3wG@p^X2i6QaMbQA@Z=Q7vL@;iN&9H2T?)DO(aG9VBCDl) zbh2*x@I;Z7eRrbBYK<6$AD}OR8BoiCIv=Gf^39`176x61_yk^qm=_~=Fl>@za>zdm zAUQJe1hfDKW}q8D#!DNg^tchtomMil)=Qk0*f>ck=d>h+lXDVUl0u7Z%f-$N>ZV7c zp()kbRmH?$sf~e@at2FN7&s-Or78NwrzcHCOW~xsXdz%Mm$aB|w>aD8i`O<6#&Tli z$4ov<#(AS!oeaY`+h9 zh`1jp-aSCjgFu004MA&xLd!!0tpkd_4-@nVP`p`B(4#=15KB&$2_FZF^iL46+hR~f)10=z#8!V! zBb|~%wo@XdT+N|VJSB%xP6-c=hv<|f&caX@yT5?prr|TN&Ezr0Uo|#ySspuSY%Oc# zjGZu+^>YxChK#+Mx!^D^-3``(`qDOu7VPZk5PSz5%RcLaRf!WQ&0`+hkKk_|a$1;o zYS^gNI7)UYGBa{!v82`u#EA)`OzrPQ%6&>+R=eot$V}0l+{q`l-1;{ z?!34kcWF*!i0kJ(mFO1U?VrL3MqRy*draK{|7+BdeiYvYue)m7e*Q{c@J7pvno zPEyJ_9Z%upoYjt}&|(quM`s4U%$CNQrXIaW+(YImGS^EfXE0Am&h<`NZJv^x?8T=i z&1&$&74NgzCZ(KUUy5ymWwkzM`}&jt$A_O} zA+cq(>lCy@Qebw>S9n(IMX6I(`$1&2B?`|mHz}0WZYJm!g|gaGf^JnPtKCM>?FwbJ zWdz-!P*%H>pydi>wG{;2rBGJ8o1lAy#(N1`sZds1MbLdh<7$HL7t=pL(1QwPqBR7q zRVdLtM9@0%;ll(yB6zJQ=ut7(V+1`e=6ZslCl$&>8wh$zp*;FDLC**qHxjf-cy%*D z&k7s25cHhz>hp@qg7H=XZBr;;EmBzdZ?&mPWf3g9{O+km;j)JMirF(gw4ry}Hnf!T z4ZZUeH}uYw4b8XhPTJ761hh*z#RMdS93`N)aK%^x+HIS}o`8fx*#qvgyb;H%Ubn`KSWyB z6k04oe&NhuPH9b#-qaWm&m;y%Yz(B7GdPmMz$uFzNzpGpJ!uv@a`G(pSt9yNTFkbu zoNe>rlU}R0B(uUK+xl&`Nhv4TpJLl!S*+jLzJ5CN@(l8DASAXd_I1JzNrBmMjI!7_ zgJiMK6>2N}Ofm8o5tZkuJh5G)(&@dHpzBn+)b$c{y-Mlt27>0Rl>Yh%TA)(;`zt|z zQz`u|BxsRJ>2EPXO9Y7<3A#z8^mj8sw+M|(3A$CK^miLUw+oHS2)aW|eXKbv{t3`_YgtrgpCgq^oW4g z6ZEKX^kXW|b&m^Z1C4r0IQnT7*rs{Sw)t%tuVQLzLsv~v_2g{Y{F_x-o=Pd-{F~L} z@^rAxzgbN#QTZ0%tnw1hmhqlfPch@kfJhl{D=retcrVx{v1dG?P@Xn+<$5P^u%vnH zGPM`M!Jo$2m`$p6k(Fc^{3#weoKa?Fale%#-8CIK{)XbJ3^RkAy~E~gDdo=Iaf;5~ z;q2@k4$gkv=BE?hZ>1sR>^Fq7-?U9)ceYTN>g=Bq&X(pek8M;T_M6UNQri|=wsgH^ z1;ByCuVh%Y#vYXw03AhoQ%xYZxjw`TfIVt*0kB6MPyj5xa1=cmqSRIZ;G3Z}`FE^4 zA?FsWC;i~gpFXpyuC%APUgU{=w$+nTPI%ubuHHW9)!XN=dLP*wf5O$1hS2JLELQJ; zZ4&$H35BVvw>YtS(mdv|j}fHq1Ui&;-EGS!94M){!^Vtiucx-MIY^6o(B?oXYPhMK7Cv6aXhUeBTY4Os^*|x@#`svjfb!J?w zc)!gyDdhzFQ*0Y7yYxHT*H1@A?qDjdNMbLo5_U)mytGnzgWwyKIyDGB$MDaF{;VqO zuKA0|_}6H>w7OQK^nM*dy&44Ahu0HygBUem)7V^n0$NC;7KslR6SPF50_;YDZqlf# zxtX9_H0WR&mukFJxK%*QXw)47x>Fm*JG;kKB^Y9amTOdut)egP7up{n=s}H&u{8v( z)u{Y?NE^cDTBlKU`529QTqAcrLC}*LRZANPdP<{8^=X2h5xh1Mv`O&VOwhA}*A{}F z6TF@$Xsek11%h4_>#&WWmo$3r-cHcV;=@-6+97Owm7v!&s-Slg^tzz>20?FXR6*|| z=q*8YH$iV}R6*|{=p8{~FG24L67Lc8zHszDf<6$A-cQhnVy=$}`dB#n06~8j>^~vs zAHva}5_C|oKSau38P7;mVNF8O2ZvUhuWCx? zT+}oVG<-tF_ngH5_}Yo@gZNf>A1L_dhi`oNriX8M>jChM4&UVP4G!Pj@Qn@M)bI@r z-^}ohY#RW+f#I7MzH#B37QSKa0>C#ae3QaAD139mH>UldVM`zDGHk13_qi84LJBNV z6zsub&lP*D*i*$GD)vmVM~XesRSK+D0DGL+)5IPo_AIeSi9Jc|L1NDldyLpq#2(@n zW!QEF@GgXBBRlu7s-gNP1i#J)k zx#CR~Z>D$?#hWMIH1TGMH%YuX;!P26hIkXin;+iv@MecMx%KKWyn}626)Zr$P~j^T zFtr?0;b#?oQL`}WVU)wDhEWWo7Dg$IN*IMO>R^(&self, trx: &T, state: &mut MarkdownState) -> Option - where - T: ReadTxn, - { - match self.get(trx, "text").map(|t| t.to_string()) { - Some(text) => match self.flavour(trx).as_str() { - "affine:code" => { - state.numbered_count = 0; - match self.get(trx, "language").map(|v| v.to_string()).as_deref() { - Some(language) => Some(format!("``` {}\n{}\n```\n", language, text)), - None => Some(format!("```\n{}\n```\n", text)), - } - } - format @ "affine:paragraph" => { - state.numbered_count = 0; - match self.get(trx, "type").map(|v| v.to_string()).as_deref() { - Some(head @ "h1" | head @ "h2" | head @ "h3" | head @ "h4" | head @ "h5") => { - Some(format!("{} {}\n", "#".repeat(head[1..].parse().unwrap()), text)) - } - Some("quote") => Some(format!("> {text}\n")), - Some("text") => Some(format!("{text}\n")), - r#type @ Some(_) | r#type @ None => { - if let Some(r#type) = r#type { - warn!("Unprocessed format: {format}, {}", r#type); - } else { - warn!("Unprocessed format: {format}"); - } - Some(text) - } - } - } - format @ "affine:list" => match self.get(trx, "type").map(|v| v.to_string()).as_deref() { - Some("numbered") => { - state.numbered_count += 1; - Some(format!("{}. {text}\n", state.numbered_count)) - } - Some("todo") => { - state.numbered_count += 1; - let clicked = self - .get(trx, "checked") - .map(|v| v.to_string() == "true") - .unwrap_or(false); - Some(format!("[{}] {text}\n", if clicked { "x" } else { " " })) - } - Some("bulleted") => { - state.numbered_count += 1; - Some(format!("- {text}\n")) - } - r#type @ Some("text") | r#type @ Some(_) | r#type @ None => { - state.numbered_count = 0; - if let Some(r#type) = r#type { - warn!("Unprocessed format: {format}, {}", r#type); - } else { - warn!("Unprocessed format: {format}"); - } - Some(text) - } - }, - format => { - state.numbered_count = 0; - warn!("Unprocessed format: {format}"); - Some(text) - } - }, - None => match self.flavour(trx).as_str() { - "affine:divider" => { - state.numbered_count = 0; - Some("---\n".into()) - } - "affine:embed" => { - state.numbered_count = 0; - match self.get(trx, "type").map(|v| v.to_string()).as_deref() { - Some("image") => self - .get(trx, "sourceId") - .map(|v| format!("![](/api/workspace/{}/blob/{})\n", self.id, v)), - _ => None, - } - } - format => { - state.numbered_count = 0; - warn!("Unprocessed format: {format}"); - None - } - }, - } - } - - fn clone_text( - &self, - stack: u8, - trx: &T, - text: TextRef, - new_trx: &mut TransactionMut, - new_text: TextRef, - ) -> JwstResult<()> - where - T: ReadTxn, - { - if stack > MAX_STACK { - warn!("clone_text: stack overflow"); - return Ok(()); - } - - for Diff { insert, attributes, .. } in text.diff(trx, YChange::identity) { - match insert { - Value::Any(Any::String(str)) => { - let str = str.as_ref(); - if let Some(attr) = attributes { - new_text.insert_with_attributes(new_trx, new_text.len(new_trx), str, *attr)?; - } else { - new_text.insert(new_trx, new_text.len(new_trx), str)?; - } - } - val => { - warn!("unexpected embed type: {:?}", val); - } - } - } - - Ok(()) - } - - fn clone_array( - &self, - stack: u8, - trx: &T, - array: ArrayRef, - new_trx: &mut TransactionMut, - new_array: ArrayRef, - ) -> JwstResult<()> - where - T: ReadTxn, - { - if stack > MAX_STACK { - warn!("clone_array: stack overflow"); - return Ok(()); - } - - for item in array.iter(trx) { - match item { - Value::Any(any) => { - new_array.push_back(new_trx, any)?; - } - Value::YText(text) => { - let new_text = new_array.push_back(new_trx, TextPrelim::new(""))?; - self.clone_text(stack + 1, trx, text, new_trx, new_text)?; - } - Value::YMap(map) => { - let new_map = new_array.push_back(new_trx, MapPrelim::::new())?; - for (key, value) in map.iter(trx) { - self.clone_value(stack + 1, key, trx, value, new_trx, new_map.clone())?; - } - } - Value::YArray(array) => { - let new_array = new_array.push_back(new_trx, ArrayPrelim::default())?; - self.clone_array(stack + 1, trx, array, new_trx, new_array)?; - } - val => { - warn!("unexpected prop type: {:?}", val); - } - } - } - - Ok(()) - } - - fn clone_value( - &self, - stack: u8, - key: &str, - trx: &T, - value: Value, - new_trx: &mut TransactionMut, - new_map: MapRef, - ) -> JwstResult<()> - where - T: ReadTxn, - { - if stack > MAX_STACK { - warn!("clone_value: stack overflow"); - return Ok(()); - } - - match value { - Value::Any(any) => { - new_map.insert(new_trx, key, any)?; - } - Value::YText(text) => { - let new_text = new_map.insert(new_trx, key, TextPrelim::new(""))?; - self.clone_text(stack + 1, trx, text, new_trx, new_text)?; - } - Value::YMap(map) => { - let new_map = new_map.insert(new_trx, key, MapPrelim::::new())?; - for (key, value) in map.iter(trx) { - self.clone_value(stack + 1, key, trx, value, new_trx, new_map.clone())?; - } - } - Value::YArray(array) => { - let new_array = new_map.insert(new_trx, key, ArrayPrelim::default())?; - self.clone_array(stack + 1, trx, array, new_trx, new_array)?; - } - val => { - warn!("unexpected prop type: {:?}", val); - } - } - - Ok(()) - } - - pub fn clone_block(&self, orig_trx: &T, new_trx: &mut TransactionMut, new_blocks: MapRef) -> JwstResult<()> - where - T: ReadTxn, - { - // init base struct - let block = new_blocks.insert(new_trx, &*self.block_id, MapPrelim::::new())?; - - // init default schema - block.insert(new_trx, sys::ID, self.block_id.as_ref())?; - block.insert(new_trx, sys::FLAVOUR, self.flavour(orig_trx).as_ref())?; - let children = block.insert(new_trx, sys::CHILDREN, ArrayPrelim::, String>::from(vec![]))?; - // block.insert(new_trx, sys::CREATED, self.created(orig_trx) as f64)?; - - // clone children - for block_id in self.children(orig_trx) { - if let Err(e) = children.push_back(new_trx, block_id) { - warn!("failed to push block: {}", e); - } - } - - // clone props - for key in self - .block - .keys(orig_trx) - .filter(|k| k.starts_with("prop:") || k.starts_with("ext:")) - { - match self.block.get(orig_trx, key) { - Some(value) => { - self.clone_value(0, key, orig_trx, value, new_trx, block.clone())?; - } - None => { - warn!("failed to get key: {}", key); - } - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use yrs::{updates::decoder::Decode, Update}; - - use super::*; - - #[test] - fn test_multiple_layer_space_clone() { - let doc1 = Doc::new(); - doc1.transact_mut() - .apply_update(Update::decode_v1(include_bytes!("../../fixtures/test_multi_layer.bin")).unwrap()); - - let ws1 = Workspace::from_doc(doc1, "test"); - - let new_update = ws1.with_trx(|mut t| { - ws1.metadata.insert(&mut t.trx, "name", Some("test1")).unwrap(); - ws1.metadata.insert(&mut t.trx, "avatar", Some("test2")).unwrap(); - let space = t.get_exists_space("page0").unwrap(); - space.to_single_page(&t.trx).unwrap() - }); - - let doc2 = Doc::new(); - doc2.transact_mut() - .apply_update(Update::decode_v1(&new_update).unwrap()); - - let doc1 = ws1.doc(); - let doc1_trx = doc1.transact(); - let doc2_trx = doc2.transact(); - assert_json_diff::assert_json_eq!( - doc1_trx.get_map("space:meta").unwrap().to_json(&doc1_trx), - doc2_trx.get_map("space:meta").unwrap().to_json(&doc2_trx) - ); - assert_json_diff::assert_json_eq!( - doc1_trx.get_map("space:page0").unwrap().to_json(&doc1_trx), - doc2_trx.get_map("space:page0").unwrap().to_json(&doc2_trx) - ); - } -} diff --git a/libs/jwst/src/block/mod.rs b/libs/jwst/src/block/mod.rs deleted file mode 100644 index 1edce7719..000000000 --- a/libs/jwst/src/block/mod.rs +++ /dev/null @@ -1,680 +0,0 @@ -mod convert; - -use std::{ - collections::HashMap, - fmt, - sync::{Arc, RwLock}, -}; - -use lib0::any::Any; -use serde::{Serialize, Serializer}; -use serde_json::Value as JsonValue; -use yrs::{ - types::{ - text::{Diff, YChange}, - DeepEventsSubscription, ToJson, Value, - }, - Array, ArrayPrelim, ArrayRef, DeepObservable, Doc, Map, MapPrelim, MapRef, ReadTxn, Text, TextPrelim, TextRef, - Transact, TransactionMut, -}; - -use super::{constants::sys, utils::JS_INT_RANGE, *}; - -#[derive(Clone)] -pub struct Block { - id: String, - space_id: String, - block_id: String, - doc: Doc, - operator: u64, - block: MapRef, - children: ArrayRef, - updated: Option, - sub: Arc>>, -} - -unsafe impl Send for Block {} -unsafe impl Sync for Block {} - -impl fmt::Debug for Block { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MyStruct") - .field("id", &self.id) - .field("space_id", &self.space_id) - .field("block_id", &self.block_id) - .field("doc", &self.doc) - .field("operator", &self.operator) - .field("block", &self.block) - .field("children", &self.children) - .field("updated", &self.updated) - .finish() - } -} - -impl PartialEq for Block { - fn eq(&self, other: &Self) -> bool { - if self.id != other.id - || self.space_id != other.space_id - || self.block_id != other.block_id - || self.doc != other.doc - || self.operator != other.operator - || self.block != other.block - || self.children != other.children - || self.updated != other.updated - { - return false; - } - true - } -} - -impl Block { - // Create a new block, skip create if block is already created. - pub fn new( - trx: &mut TransactionMut<'_>, - space: &Space, - block_id: B, - flavour: F, - operator: u64, - ) -> JwstResult - where - B: AsRef, - F: AsRef, - { - let block_id = block_id.as_ref(); - - if let Some(block) = Self::from(trx, space, block_id, operator) { - Ok(block) - } else { - // init base struct - space.blocks.insert(trx, block_id, MapPrelim::::new())?; - let block = space.blocks.get(trx, block_id).and_then(|b| b.to_ymap()).unwrap(); - - // init default schema - block.insert(trx, sys::FLAVOUR, flavour.as_ref())?; - block.insert(trx, sys::CHILDREN, ArrayPrelim::, String>::from(vec![]))?; - block.insert(trx, sys::CREATED, chrono::Utc::now().timestamp_millis() as f64)?; - - space.updated.insert(trx, block_id, ArrayPrelim::<_, Any>::from([]))?; - - let children = block.get(trx, sys::CHILDREN).and_then(|c| c.to_yarray()).unwrap(); - let updated = space.updated.get(trx, block_id).and_then(|c| c.to_yarray()); - - let block = Self { - id: space.id(), - space_id: space.space_id(), - doc: space.doc(), - block_id: block_id.to_string(), - operator, - block, - children, - updated, - sub: Arc::default(), - }; - - block.log_update(trx, HistoryOperation::Add); - - Ok(block) - } - } - - pub fn from(trx: &T, space: &Space, block_id: B, operator: u64) -> Option - where - T: ReadTxn, - B: AsRef, - { - let block = space.blocks.get(trx, block_id.as_ref())?.to_ymap()?; - let updated = space.updated.get(trx, block_id.as_ref()).and_then(|a| a.to_yarray()); - let children = block.get(trx, sys::CHILDREN)?.to_yarray()?; - - Some(Self { - id: space.id(), - space_id: space.space_id(), - block_id: block_id.as_ref().to_string(), - doc: space.doc(), - operator, - block, - children, - updated, - sub: Arc::default(), - }) - } - - pub fn subscribe(&mut self, block_observer_config: Arc) { - let block_id = self.block_id.clone(); - let tx = block_observer_config.tx.clone(); - let handle = block_observer_config.handle.clone(); - match self.sub.read() { - Ok(sub_read_guard) => { - if sub_read_guard.is_none() { - trace!("subscribe block: {}", self.block_id); - let sub = self.block.observe_deep(move |_trx, _e| { - if handle.lock().unwrap().is_some() { - tx.send(block_id.clone()).expect("send block observe message error"); - } - }); - drop(sub_read_guard); - *self.sub.write().unwrap() = Some(sub); - } - } - Err(e) => { - error!("subscribe block error: {}", e); - } - } - } - - #[allow(clippy::too_many_arguments)] - pub fn from_raw_parts( - trx: &T, - id: String, - space_id: String, - block_id: String, - doc: &Doc, - block: MapRef, - updated: Option, - operator: u64, - ) -> Block { - let children = block.get(trx, sys::CHILDREN).unwrap().to_yarray().unwrap(); - Self { - id, - space_id, - block_id, - doc: doc.clone(), - operator, - block, - children, - updated, - sub: Arc::default(), - } - } - - pub(crate) fn log_update(&self, trx: &mut TransactionMut, action: HistoryOperation) { - let array = ArrayPrelim::from([ - Any::Number(self.operator as f64), - Any::Number(chrono::Utc::now().timestamp_millis() as f64), - Any::String(Box::from(action.to_string())), - ]); - - self.updated.as_ref().map(|a| a.push_back(trx, array)); - } - - pub fn get(&self, trx: &T, key: &str) -> Option - where - T: ReadTxn, - { - let key = format!("prop:{key}"); - self.block.get(trx, &key).and_then(|v| match v.to_json(trx) { - Any::Null | Any::Undefined | Any::Array(_) | Any::Buffer(_) | Any::Map(_) => { - error!("get wrong value at key {}", key); - None - } - v => Some(v), - }) - } - - pub fn set(&self, trx: &mut TransactionMut, key: &str, value: T) -> JwstResult<()> - where - T: Into, - { - let key = format!("prop:{key}"); - match value.into() { - Any::Bool(bool) => { - self.block.insert(trx, key, bool)?; - self.log_update(trx, HistoryOperation::Update); - } - Any::String(text) => { - self.block.insert(trx, key, text.to_string())?; - self.log_update(trx, HistoryOperation::Update); - } - Any::Number(number) => { - self.block.insert(trx, key, number)?; - self.log_update(trx, HistoryOperation::Update); - } - Any::BigInt(number) => { - if JS_INT_RANGE.contains(&number) { - self.block.insert(trx, key, number)?; - } else { - self.block.insert(trx, key, number as f64)?; - } - self.log_update(trx, HistoryOperation::Update); - } - Any::Null | Any::Undefined => { - self.block.remove(trx, &key); - self.log_update(trx, HistoryOperation::Delete); - } - Any::Buffer(_) | Any::Array(_) | Any::Map(_) => {} - } - Ok(()) - } - - pub fn block_id(&self) -> String { - self.block_id.clone() - } - - // start with a namespace - // for example: affine:text - pub fn flavour(&self, trx: &T) -> String - where - T: ReadTxn, - { - self.block.get(trx, sys::FLAVOUR).unwrap_or_default().to_string(trx) - } - - pub fn created(&self, trx: &T) -> u64 - where - T: ReadTxn, - { - self.block - .get(trx, sys::CREATED) - .and_then(|c| match c.to_json(trx) { - Any::Number(n) => Some(n as u64), - _ => None, - }) - .unwrap_or_default() - } - - pub fn updated(&self, trx: &T) -> u64 - where - T: ReadTxn, - { - self.updated - .as_ref() - .and_then(|a| { - a.iter(trx).filter_map(|v| v.to_yarray()).last().and_then(|a| { - a.get(trx, 1).and_then(|i| match i.to_json(trx) { - Any::Number(n) => Some(n as u64), - _ => None, - }) - }) - }) - .unwrap_or_else(|| self.created(trx)) - } - - pub fn history(&self, trx: &T) -> Vec - where - T: ReadTxn, - { - self.updated - .as_ref() - .map(|a| { - a.iter(trx) - .filter_map(|v| v.to_yarray()) - .map(|v| (trx, v, self.block_id.clone()).into()) - .collect::>() - }) - .unwrap_or_default() - } - - pub fn parent(&self, trx: &T) -> Option - where - T: ReadTxn, - { - self.block.get(trx, sys::PARENT).and_then(|c| match c.to_json(trx) { - Any::String(s) => Some(s.to_string()), - _ => None, - }) - } - - pub fn children(&self, trx: &T) -> Vec - where - T: ReadTxn, - { - self.children.iter(trx).map(|v| v.to_string(trx)).collect() - } - - #[inline] - pub fn children_iter(&self, cb: impl FnOnce(Box + '_>) -> T) -> T { - let trx = self.doc.transact(); - let iterator = self.children.iter(&trx).map(|v| v.to_string(&trx)); - - cb(Box::new(iterator)) - } - - pub fn children_len(&self) -> u32 { - let trx = self.doc.transact(); - self.children.len(&trx) - } - - pub fn children_exists(&self, trx: &T, block_id: S) -> bool - where - T: ReadTxn, - S: AsRef, - { - self.children - .iter(trx) - .map(|v| v.to_string(trx)) - .any(|bid| bid == block_id.as_ref()) - } - - pub(crate) fn content(&self, trx: &T) -> HashMap - where - T: ReadTxn, - { - self.block - .iter(trx) - .filter_map(|(key, val)| { - key.strip_prefix("prop:") - .map(|stripped| (stripped.to_owned(), val.to_json(trx))) - }) - .collect() - } - - fn set_parent(&self, trx: &mut TransactionMut, block_id: String) -> JwstResult<()> { - self.block.insert(trx, sys::PARENT, block_id)?; - Ok(()) - } - - pub fn push_children(&self, trx: &mut TransactionMut, block: &Block) -> JwstResult<()> { - self.remove_children(trx, block)?; - block.set_parent(trx, self.block_id.clone())?; - - self.children.push_back(trx, block.block_id.clone())?; - - self.log_update(trx, HistoryOperation::Add); - - Ok(()) - } - - pub fn insert_children_at(&self, trx: &mut TransactionMut, block: &Block, pos: u32) -> JwstResult<()> { - self.remove_children(trx, block)?; - block.set_parent(trx, self.block_id.clone())?; - - let children = &self.children; - - if children.len(trx) > pos { - children.insert(trx, pos, block.block_id.clone())?; - } else { - children.push_back(trx, block.block_id.clone())?; - } - - self.log_update(trx, HistoryOperation::Add); - - Ok(()) - } - - pub fn insert_children_before(&self, trx: &mut TransactionMut, block: &Block, reference: &str) -> JwstResult<()> { - self.remove_children(trx, block)?; - block.set_parent(trx, self.block_id.clone())?; - - let children = &self.children; - - if let Some(pos) = children.iter(trx).position(|c| c.to_string(trx) == reference) { - children.insert(trx, pos as u32, block.block_id.clone())?; - } else { - children.push_back(trx, block.block_id.clone())?; - } - - self.log_update(trx, HistoryOperation::Add); - - Ok(()) - } - - pub fn insert_children_after(&self, trx: &mut TransactionMut, block: &Block, reference: &str) -> JwstResult<()> { - self.remove_children(trx, block)?; - block.set_parent(trx, self.block_id.clone())?; - - let children = &self.children; - - match children.iter(trx).position(|c| c.to_string(trx) == reference) { - Some(pos) if (pos as u32) < children.len(trx) => { - children.insert(trx, pos as u32 + 1, block.block_id.clone())?; - } - _ => { - children.push_back(trx, block.block_id.clone())?; - } - } - - self.log_update(trx, HistoryOperation::Add); - - Ok(()) - } - - pub fn remove_children(&self, trx: &mut TransactionMut, block: &Block) -> JwstResult<()> { - let children = &self.children; - block.set_parent(trx, self.block_id.clone())?; - - if let Some(current_pos) = children.iter(trx).position(|c| c.to_string(trx) == block.block_id) { - children.remove(trx, current_pos as u32)?; - self.log_update(trx, HistoryOperation::Delete); - } - - Ok(()) - } - - pub fn exists_children(&self, trx: &T, block_id: &str) -> Option - where - T: ReadTxn, - { - self.children.iter(trx).position(|c| c.to_string(trx) == block_id) - } -} - -#[derive(Default)] -pub struct MarkdownState { - numbered_count: usize, -} - -impl Serialize for Block { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let trx = self.doc.transact(); - let any = self.block.to_json(&trx); - - let mut buffer = String::new(); - any.to_json(&mut buffer); - let any: JsonValue = serde_json::from_str(&buffer).unwrap(); - - let mut block = any.as_object().unwrap().clone(); - block.insert(constants::sys::ID.to_string(), JsonValue::String(self.block_id.clone())); - - JsonValue::Object(block).serialize(serializer) - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - - use super::*; - - #[test] - fn init_block() { - let workspace = Workspace::new("workspace"); - - // new block - workspace.with_trx(|mut t| { - let space = t.get_space("space"); - - let block = space.create(&mut t.trx, "test", "affine:text").unwrap(); - - assert_eq!(block.id, "workspace"); - assert_eq!(block.space_id, "space"); - assert_eq!(block.block_id(), "test"); - assert_eq!(block.flavour(&t.trx), "affine:text"); - }); - - // get exist block - workspace.with_trx(|mut t| { - let space = t.get_space("space"); - - let block = space.get(&t.trx, "test").unwrap(); - - assert_eq!(block.flavour(&t.trx), "affine:text"); - }); - } - - #[test] - fn set_value() { - let workspace = Workspace::new("test"); - - workspace.with_trx(|mut t| { - let space = t.get_space("space"); - - let block = space.create(&mut t.trx, "test", "affine:text").unwrap(); - - // normal type set - block.set(&mut t.trx, "bool", true).unwrap(); - block.set(&mut t.trx, "text", "hello world").unwrap(); - block.set(&mut t.trx, "text_owned", "hello world".to_owned()).unwrap(); - block.set(&mut t.trx, "num", 123_i32).unwrap(); - block.set(&mut t.trx, "bigint", 9007199254740992_i64).unwrap(); - - assert_eq!(block.get(&t.trx, "bool").unwrap().to_string(), "true"); - assert_eq!(block.get(&t.trx, "text").unwrap().to_string(), "hello world"); - assert_eq!(block.get(&t.trx, "text_owned").unwrap().to_string(), "hello world"); - assert_eq!(block.get(&t.trx, "num").unwrap().to_string(), "123"); - assert_eq!(block.get(&t.trx, "bigint").unwrap().to_string(), "9007199254740992"); - - assert_eq!( - block.content(&t.trx), - vec![ - ("bool".to_owned(), Any::Bool(true)), - ("text".to_owned(), Any::String("hello world".into())), - ("text_owned".to_owned(), Any::String("hello world".into())), - ("num".to_owned(), Any::Number(123.0)), - ("bigint".to_owned(), Any::Number(9007199254740992.0)), - ] - .iter() - .cloned() - .collect::>() - ); - }); - } - - #[test] - fn insert_remove_children() { - let workspace = Workspace::new("text"); - - workspace.with_trx(|mut t| { - let space = t.get_space("space"); - - let block = space.create(&mut t.trx, "a", "affine:text").unwrap(); - let b = space.create(&mut t.trx, "b", "affine:text").unwrap(); - let c = space.create(&mut t.trx, "c", "affine:text").unwrap(); - let d = space.create(&mut t.trx, "d", "affine:text").unwrap(); - let e = space.create(&mut t.trx, "e", "affine:text").unwrap(); - let f = space.create(&mut t.trx, "f", "affine:text").unwrap(); - - block.push_children(&mut t.trx, &b).unwrap(); - block.insert_children_at(&mut t.trx, &c, 0).unwrap(); - block.insert_children_before(&mut t.trx, &d, "b").unwrap(); - block.insert_children_after(&mut t.trx, &e, "b").unwrap(); - block.insert_children_after(&mut t.trx, &f, "c").unwrap(); - - assert_eq!( - block.children(&t.trx), - vec![ - "c".to_owned(), - "f".to_owned(), - "d".to_owned(), - "b".to_owned(), - "e".to_owned() - ] - ); - - block.remove_children(&mut t.trx, &d).unwrap(); - - assert_eq!( - block.children(&t.trx), - vec!["c".to_owned(), "f".to_owned(), "b".to_owned(), "e".to_owned()] - ); - }); - } - - #[test] - fn updated() { - let workspace = Workspace::new("test"); - - workspace.with_trx(|mut t| { - let space = t.get_space("space"); - - let block = space.create(&mut t.trx, "a", "affine:text").unwrap(); - - block.set(&mut t.trx, "test", 1).unwrap(); - - assert!(block.created(&t.trx) <= block.updated(&t.trx)) - }); - } - - #[test] - fn history() { - use yrs::Doc; - - let doc = Doc::with_client_id(123); - - let workspace = Workspace::from_doc(doc, "test"); - - let (block, b, history) = workspace.with_trx(|mut t| { - let space = t.get_space("space"); - let block = space.create(&mut t.trx, "a", "affine:text").unwrap(); - let b = space.create(&mut t.trx, "b", "affine:text").unwrap(); - - block.set(&mut t.trx, "test", 1).unwrap(); - - let history = block.history(&t.trx); - - (block, b, history) - }); - - assert_eq!(history.len(), 2); - - // let history = history.last().unwrap(); - - assert_eq!( - history, - vec![ - BlockHistory { - block_id: "a".to_owned(), - client: 123, - timestamp: history.get(0).unwrap().timestamp, - operation: HistoryOperation::Add, - }, - BlockHistory { - block_id: "a".to_owned(), - client: 123, - timestamp: history.get(1).unwrap().timestamp, - operation: HistoryOperation::Update, - } - ] - ); - - let history = workspace.with_trx(|mut t| { - block.push_children(&mut t.trx, &b).unwrap(); - - assert_eq!(block.exists_children(&t.trx, "b"), Some(0)); - - block.remove_children(&mut t.trx, &b).unwrap(); - - assert_eq!(block.exists_children(&t.trx, "b"), None); - - block.history(&t.trx) - }); - - assert_eq!(history.len(), 4); - - if let [.., insert, remove] = history.as_slice() { - assert_eq!( - insert, - &BlockHistory { - block_id: "a".to_owned(), - client: 123, - timestamp: insert.timestamp, - operation: HistoryOperation::Add, - } - ); - assert_eq!( - remove, - &BlockHistory { - block_id: "a".to_owned(), - client: 123, - timestamp: remove.timestamp, - operation: HistoryOperation::Delete, - } - ); - } else { - unreachable!(); - } - } -} diff --git a/libs/jwst/src/constants.rs b/libs/jwst/src/constants.rs deleted file mode 100644 index f73f18542..000000000 --- a/libs/jwst/src/constants.rs +++ /dev/null @@ -1,36 +0,0 @@ -pub mod metadata { - /// `name` - pub const NAME: &str = "name"; - - /// `avatar` - pub const AVATAR: &str = "avatar"; - - /// `search_index` - pub const SEARCH_INDEX: &str = "search_index"; -} - -/// The sys constants. -pub mod sys { - /// `sys:id` - pub const ID: &str = "sys:id"; - - /// `sys:children` - pub const CHILDREN: &str = "sys:children"; - - /// `sys:created` - pub const CREATED: &str = "sys:created"; - - /// `sys:flavour` - pub const FLAVOUR: &str = "sys:flavour"; - - /// `sys:parent` - pub const PARENT: &str = "sys:parent"; -} - -pub mod space { - /// `space:updated` - pub const UPDATED: &str = "space:updated"; - - /// `space:meta` - pub const META: &str = "space:meta"; -} diff --git a/libs/jwst/src/history/mod.rs b/libs/jwst/src/history/mod.rs deleted file mode 100644 index 85985dd64..000000000 --- a/libs/jwst/src/history/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod raw; -mod record; - -pub use raw::{parse_history, parse_history_client, RawHistory}; -pub use record::{BlockHistory, HistoryOperation}; diff --git a/libs/jwst/src/history/raw.rs b/libs/jwst/src/history/raw.rs deleted file mode 100644 index 9d5b14431..000000000 --- a/libs/jwst/src/history/raw.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::collections::{HashMap, HashSet, VecDeque}; - -use serde::Serialize; -use tracing::{debug, info}; -use utoipa::ToSchema; -use yrs::{ - block::{Item, ItemContent, ID}, - types::TypePtr, - updates::decoder::Decode, - Doc, ReadTxn, StateVector, Transact, Update, -}; - -struct ParentMap(HashMap); - -impl ParentMap { - fn parse_parent(name_map: &HashMap, parent: TypePtr) -> Option { - match parent { - TypePtr::Unknown => Some("unknown".to_owned()), - TypePtr::Branch(ptr) => ptr.item_id().and_then(|item_id| name_map.get(&item_id)).cloned(), - TypePtr::Named(name) => Some(name.to_string()), - TypePtr::ID(ptr_id) => name_map.get(&ptr_id).cloned(), - } - } - - fn from(items: &[&Item]) -> Self { - let mut name_map: HashMap = HashMap::new(); - let mut padding_ptr: VecDeque<(&Item, usize)> = - VecDeque::from(items.iter().map(|i| (<&Item>::clone(i), 0)).collect::>()); - - while let Some((item, retry)) = padding_ptr.pop_back() { - if retry > 5 { - debug!("retry failed: {:?}, {:?}, {:?}", item, retry, padding_ptr); - break; - } - let (parent, parent_sub) = { - let parent = if item.parent == TypePtr::Unknown { - if let Some((parent, parent_sub)) = item.resolve_parent() { - Self::parse_parent(&name_map, parent).map(|parent| (parent, parent_sub)) - } else { - Some(("unknown".to_owned(), None)) - } - } else { - Self::parse_parent(&name_map, item.parent.clone()).map(|parent| (parent, item.parent_sub.clone())) - }; - - if let Some(parent) = parent { - parent - } else { - padding_ptr.push_front((item, retry + 1)); - continue; - } - }; - - let parent = if let Some(parent_sub) = parent_sub { - format!("{parent}.{parent_sub}") - } else { - parent - }; - - name_map.insert(item.id, parent.clone()); - } - - Self(name_map) - } - - fn get(&self, id: &ID) -> Option { - self.0.get(id).cloned() - } -} - -#[derive(Debug, Serialize, ToSchema, PartialEq)] -pub struct RawHistory { - id: String, - parent: String, - content: String, -} - -pub fn parse_history_client(doc: &Doc) -> Option> { - let update = doc - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .and_then(|update| Update::decode_v1(&update)) - .ok()?; - - Some( - update - .as_items() - .iter() - .map(|i| i.id.client) - .collect::>() - .into_iter() - .collect::>(), - ) -} - -pub fn parse_history(doc: &Doc, client: u64) -> Option> { - let update = doc - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .and_then(|update| Update::decode_v1(&update)) - .ok()?; - let items = update.as_items(); - - let mut histories = vec![]; - let parent_map = ParentMap::from(&items); - - for item in items { - if let ItemContent::Deleted(_) = item.content { - continue; - } - if let Some(parent) = parent_map.get(&item.id) { - if item.id.client == client || client == 0 { - let id = format!("{}:{}", item.id.clock, item.id.client); - histories.push(RawHistory { - id, - parent, - content: item.content.to_string(), - }) - } - } else { - info!("headless id: {:?}", item.id); - } - } - - Some(histories) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::Workspace; - - #[test] - fn parse_history_client_test() { - let workspace = Workspace::new("test"); - workspace.with_trx(|mut t| { - let space = t.get_space("test"); - - let block = space.create(&mut t.trx, "test", "text").unwrap(); - block.set(&mut t.trx, "test", "test").unwrap(); - }); - - let doc = workspace.doc(); - - let client = parse_history_client(&doc).unwrap(); - - assert_eq!(client[0], doc.client_id()); - } - - #[test] - fn parse_history_test() { - let workspace = Workspace::new("test"); - workspace.with_trx(|mut t| { - t.get_space("test").create(&mut t.trx, "test", "text").unwrap(); - }); - let doc = workspace.doc(); - - let history = parse_history(&doc, 0).unwrap(); - - let update = doc - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap(); - let update = Update::decode_v1(&update).unwrap(); - let items = update.as_items(); - - let mut mock_histories: Vec = vec![]; - let parent_map = ParentMap::from(&items); - for item in items { - if let Some(parent) = parent_map.get(&item.id) { - let id = format!("{}:{}", item.id.clock, item.id.client); - mock_histories.push(RawHistory { - id, - parent, - content: item.content.to_string(), - }) - } - } - - assert_eq!(history, mock_histories); - } -} diff --git a/libs/jwst/src/history/record.rs b/libs/jwst/src/history/record.rs deleted file mode 100644 index fb99a1f3c..000000000 --- a/libs/jwst/src/history/record.rs +++ /dev/null @@ -1,86 +0,0 @@ -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; -use yrs::{Array, ArrayRef, ReadTxn}; - -#[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq)] -pub enum HistoryOperation { - Undefined, - Add, - Update, - Delete, -} - -impl From for HistoryOperation { - fn from(str: String) -> Self { - match str.as_str() { - "add" => Self::Add, - "update" => Self::Update, - "delete" => Self::Delete, - _ => Self::Undefined, - } - } -} - -impl From for f64 { - fn from(op: HistoryOperation) -> f64 { - match op { - HistoryOperation::Undefined => 0.0, - HistoryOperation::Add => 1.0, - HistoryOperation::Update => 2.0, - HistoryOperation::Delete => 3.0, - } - } -} - -impl From for HistoryOperation { - fn from(num: f64) -> Self { - if (0.0..1.0).contains(&num) { - Self::Undefined - } else if (1.0..2.0).contains(&num) { - Self::Add - } else if (2.0..3.0).contains(&num) { - Self::Update - } else if (3.0..4.0).contains(&num) { - Self::Delete - } else { - Self::Undefined - } - } -} - -impl ToString for HistoryOperation { - fn to_string(&self) -> String { - match self { - Self::Add => "add".to_owned(), - Self::Update => "update".to_owned(), - Self::Delete => "delete".to_owned(), - Self::Undefined => "undefined".to_owned(), - } - } -} - -#[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq)] -pub struct BlockHistory { - pub block_id: String, - pub client: u64, - pub timestamp: u64, - pub operation: HistoryOperation, -} - -impl From<(&'_ T, ArrayRef, String)> for BlockHistory { - fn from(params: (&'_ T, ArrayRef, String)) -> Self { - let (trx, array, block_id) = params; - Self { - block_id, - client: array - .get(trx, 0) - .and_then(|i| i.to_string(trx).parse::().ok()) - .unwrap_or_default(), - timestamp: array - .get(trx, 1) - .and_then(|i| i.to_string(trx).parse::().ok()) - .unwrap_or_default(), - operation: array.get(trx, 2).map(|i| i.to_string(trx)).unwrap_or_default().into(), - } - } -} diff --git a/libs/jwst/src/lib.rs b/libs/jwst/src/lib.rs deleted file mode 100644 index 300845735..000000000 --- a/libs/jwst/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -mod block; -mod history; -mod space; -mod types; -mod utils; -mod workspace; - -pub mod constants; - -pub use block::Block; -pub use history::{parse_history, parse_history_client, BlockHistory, HistoryOperation, RawHistory}; -pub use space::Space; -pub use tracing::{debug, error, info, log::LevelFilter, trace, warn}; -pub use types::{BlobMetadata, BlobStorage, BucketBlobStorage, DocStorage, JwstError, JwstResult}; -pub use utils::{sync_encode_update, Base64DecodeError, Base64Engine, STANDARD_ENGINE, URL_SAFE_ENGINE}; -pub use workspace::{BlockObserverConfig, MapSubscription, Workspace, WorkspaceMetadata, WorkspaceTransaction}; -#[cfg(feature = "workspace-search")] -pub use workspace::{SearchResult, SearchResults}; - -const RETRY_NUM: i32 = 512; - -#[inline] -pub fn print_versions(pkg_name: &str, pkg_version: &str) { - use convert_case::{Case, Casing}; - info!("{}-{}", pkg_name.to_case(Case::Pascal), pkg_version); - info!( - "Based on OctoBase-{}-{}-{}", - env!("CARGO_PKG_VERSION"), - &env!("VERGEN_GIT_COMMIT_TIMESTAMP")[0..10], - &env!("VERGEN_GIT_SHA")[0..7] - ); - info!( - "Built with rust {}-{}-{}", - env!("VERGEN_RUSTC_SEMVER"), - env!("VERGEN_RUSTC_COMMIT_DATE"), - &env!("VERGEN_RUSTC_COMMIT_HASH")[0..7], - ); -} - -#[test] -fn test_print_versions() { - // just for test coverage - print_versions("jwst", "0.1.0"); -} diff --git a/libs/jwst/src/space/convert.rs b/libs/jwst/src/space/convert.rs deleted file mode 100644 index 71cdd4d2c..000000000 --- a/libs/jwst/src/space/convert.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::collections::HashMap; - -use chrono::Utc; -use lib0::any::Any; -use yrs::{types::ToJson, Array, ArrayPrelim, ArrayRef, Map, MapPrelim, TextPrelim}; - -use super::*; - -impl Space { - pub fn to_markdown(&self, trx: &T) -> Option - where - T: ReadTxn, - { - if let Some(title) = self.get_blocks_by_flavour(trx, "affine:page").first() { - let mut markdown = String::new(); - - if let Some(title) = title.get(trx, "title") { - markdown.push_str(&format!("# {title}")); - markdown.push('\n'); - } - - for frame in title.children(trx) { - if let Some(frame) = self.get(trx, &frame) { - let mut state = MarkdownState::default(); - for child in frame.children(trx) { - if let Some(text) = self - .get(trx, &child) - .and_then(|child| child.to_markdown(trx, &mut state)) - { - markdown.push_str(&text); - markdown.push('\n'); - } - } - } - } - - Some(markdown) - } else { - None - } - } - - fn init_workspace(&self, trx: &mut TransactionMut, meta: WorkspaceMetadata) -> JwstResult<()> { - self.metadata.insert(trx, "name", meta.name)?; - self.metadata.insert(trx, "avatar", meta.avatar)?; - - Ok(()) - } - - fn init_pages(&self, trx: &mut TransactionMut) -> JwstResult { - self.pages(trx) - .or_else(|_| Ok(self.metadata.insert(trx, "pages", ArrayPrelim::default())?)) - } - - // TODO: clone from origin doc - fn init_version(&self, trx: &mut TransactionMut) -> JwstResult { - self.metadata - .get(trx, "versions") - .and_then(|v| v.to_ymap()) - .ok_or(JwstError::VersionNotFound(self.id())) - .or_else(|_| { - Ok(self.metadata.insert( - trx, - "versions", - MapPrelim::::from([ - ("affine:code".to_owned(), 1.into()), - ("affine:database".to_owned(), 1.into()), - ("affine:divider".to_owned(), 1.into()), - ("affine:embed".to_owned(), 1.into()), - ("affine:frame".to_owned(), 1.into()), - ("affine:list".to_owned(), 1.into()), - ("affine:page".to_owned(), 2.into()), - ("affine:paragraph".to_owned(), 1.into()), - ("affine:surface".to_owned(), 1.into()), - ]), - )?) - }) - } - - fn pages(&self, trx: &T) -> JwstResult { - self.metadata - .get(trx, "pages") - .and_then(|pages| pages.to_yarray()) - .ok_or(JwstError::PageTreeNotFound(self.id())) - } - - fn page_item(&self, trx: &T) -> JwstResult { - self.pages(trx)? - .iter(trx) - .find(|page| { - page.clone() - .to_ymap() - .and_then(|page| page.get(trx, "id")) - .map(|id| id.to_string(trx) == self.space_id()) - .unwrap_or(false) - }) - .and_then(|v| v.to_ymap()) - .ok_or(JwstError::PageItemNotFound(self.space_id())) - } - - pub fn to_single_page(&self, trx: &T) -> JwstResult> { - let ws = Workspace::new(self.id()); - let page_item = self.page_item(trx)?; - - ws.with_trx(|mut t| { - let space = t.get_space(self.space_id()); - let new_blocks = space.blocks.clone(); - self.blocks(trx, |blocks| { - // TODO: hacky logic for BlockSuite's special case - let (roots, blocks): (Vec<_>, _) = - blocks.partition(|b| ["affine:surface", "affine:page"].contains(&b.flavour(trx).as_str())); - - for block in roots { - block.clone_block(trx, &mut t.trx, new_blocks.clone())?; - } - - for block in blocks { - block.clone_block(trx, &mut t.trx, new_blocks.clone())?; - } - Ok::<_, JwstError>(()) - })?; - - space.init_workspace(&mut t.trx, (trx, self.metadata.clone()).into())?; - space.init_version(&mut t.trx)?; - - let title = self - .get_blocks_by_flavour(trx, "affine:page") - .first() - .and_then(|b| b.get(trx, "title").map(|t| t.to_string())) - .unwrap_or("Untitled".into()); - - let page_item = MapPrelim::from(HashMap::from([ - ("id".into(), Any::String(Box::from(self.space_id()))), - ( - "createDate".into(), - page_item - .get(trx, "createDate") - .map(|c| c.to_json(trx)) - .unwrap_or_else(|| Any::Number(Utc::now().timestamp_millis() as f64)), - ), - ( - "subpageIds".into(), - Any::Array(Box::from( - page_item - .get(trx, "subpageIds") - .map(|c| c.to_json(trx)) - .and_then(|v| match v { - Any::Array(a) => Some(a.to_vec()), - _ => None, - }) - .unwrap_or_default(), - )), - ), - ])); - - let page_item = space.init_pages(&mut t.trx)?.push_back(&mut t.trx, page_item)?; - - page_item.insert(&mut t.trx, "title", TextPrelim::new(title))?; - - Ok::<_, JwstError>(()) - })?; - - ws.sync_migration() - } -} diff --git a/libs/jwst/src/space/mod.rs b/libs/jwst/src/space/mod.rs deleted file mode 100644 index 5aeb47745..000000000 --- a/libs/jwst/src/space/mod.rs +++ /dev/null @@ -1,345 +0,0 @@ -mod convert; -mod transaction; - -use std::sync::Arc; - -use serde::{ser::SerializeMap, Serialize, Serializer}; -use transaction::SpaceTransaction; -use yrs::{Doc, Map, MapRef, ReadTxn, Transact, TransactionMut, WriteTxn}; - -use super::{block::MarkdownState, workspace::Pages, *}; - -// Workspace -// / \ -// Space ... Space -// / | \ / \ -//Block .. Block Block ..Block -pub struct Space { - workspace_id: String, - space_id: String, - doc: Doc, - pub(super) blocks: MapRef, - pub(super) updated: MapRef, - pub(super) metadata: MapRef, - pages: Pages, - block_observer_config: Option>, -} - -impl Space { - pub fn new( - trx: &mut TransactionMut, - doc: Doc, - pages: Pages, - workspace_id: I, - space_id: S, - block_observer_config: Option>, - ) -> Self - where - I: AsRef, - S: AsRef, - { - let space_id = space_id.as_ref().into(); - let store = trx.store_mut(); - let blocks = doc.get_or_insert_map_with_trx(store, &format!("space:{}", space_id)); - let updated = doc.get_or_insert_map_with_trx(store, constants::space::UPDATED); - let metadata = doc.get_or_insert_map_with_trx(store, constants::space::META); - - Self { - workspace_id: workspace_id.as_ref().into(), - space_id, - doc, - blocks, - updated, - metadata, - pages, - block_observer_config, - } - } - - pub fn from_exists( - trx: &TransactionMut, - doc: Doc, - workspace_id: I, - space_id: S, - block_observer_config: Option>, - ) -> Option - where - I: AsRef, - S: AsRef, - { - let space_id = space_id.as_ref().into(); - let blocks = trx.get_map(&format!("space:{}", space_id))?; - let updated = trx.get_map(constants::space::UPDATED)?; - let metadata = trx.get_map(constants::space::META)?; - let pages = Pages::new(metadata.get(trx, "pages").and_then(|v| v.to_yarray())?); - - Some(Self { - workspace_id: workspace_id.as_ref().into(), - space_id, - doc, - blocks, - updated, - metadata, - pages, - block_observer_config, - }) - } - - pub fn id(&self) -> String { - self.workspace_id.clone() - } - - pub fn space_id(&self) -> String { - self.space_id.clone() - } - - pub fn client_id(&self) -> u64 { - self.doc.client_id() - } - - pub fn doc(&self) -> Doc { - self.doc.clone() - } - - pub fn with_trx(&self, f: impl FnOnce(SpaceTransaction) -> T) -> T { - let doc = self.doc(); - let trx = SpaceTransaction { - trx: doc.transact_mut(), - space: self, - }; - - f(trx) - } - - pub fn try_with_trx(&self, f: impl FnOnce(SpaceTransaction) -> T) -> Option { - match self.doc().try_transact_mut() { - Ok(trx) => { - let trx = SpaceTransaction { trx, space: self }; - Some(f(trx)) - } - Err(e) => { - info!("try_with_trx error: {}", e); - None - } - } - } - - // get a block if exists - pub fn get(&self, trx: &T, block_id: S) -> Option - where - T: ReadTxn, - S: AsRef, - { - let mut block = Block::from(trx, self, block_id, self.client_id())?; - if let Some(block_observer_config) = self.block_observer_config.clone() { - block.subscribe(block_observer_config); - } - Some(block) - } - - pub fn block_count(&self) -> u32 { - self.blocks.len(&self.doc.transact()) - } - - #[inline] - pub fn blocks(&self, trx: &T, cb: impl FnOnce(Box + '_>) -> R) -> R - where - T: ReadTxn, - { - let iterator = self.blocks.iter(trx).map(|(id, block)| { - Block::from_raw_parts( - trx, - self.id(), - self.space_id(), - id.to_owned(), - &self.doc, - block.to_ymap().unwrap(), - self.updated.get(trx, id).and_then(|u| u.to_yarray()), - self.client_id(), - ) - }); - - cb(Box::new(iterator)) - } - - pub fn create(&self, trx: &mut TransactionMut, block_id: B, flavour: F) -> JwstResult - where - B: AsRef, - F: AsRef, - { - info!("create block: {}, flavour: {}", block_id.as_ref(), flavour.as_ref()); - let mut block = Block::new(trx, self, block_id, flavour, self.client_id())?; - if let Some(block_observer_config) = self.block_observer_config.clone() { - block.subscribe(block_observer_config); - } - Ok(block) - } - - pub fn remove>(&self, trx: &mut TransactionMut, block_id: S) -> bool { - info!("remove block: {}", block_id.as_ref()); - self.blocks.remove(trx, block_id.as_ref()).is_some() && self.updated.remove(trx, block_id.as_ref()).is_some() - } - - pub fn get_blocks_by_flavour(&self, trx: &T, flavour: &str) -> Vec - where - T: ReadTxn, - { - self.blocks(trx, |blocks| { - blocks - .filter(|block| block.flavour(trx) == flavour) - .map(|mut block| { - if let Some(block_observer_config) = self.block_observer_config.clone() { - block.subscribe(block_observer_config); - } - block - }) - .collect::>() - }) - } - - /// Check if the block exists in this workspace's blocks. - pub fn exists(&self, trx: &T, block_id: &str) -> bool - where - T: ReadTxn, - { - self.blocks.contains_key(trx, block_id.as_ref()) - } - - pub fn shared(&self, trx: &T) -> bool - where - T: ReadTxn, - { - self.pages.check_shared(trx, &self.space_id) - } -} - -impl Serialize for Space { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let doc = self.doc(); - let trx = doc.transact(); - let mut map = serializer.serialize_map(None)?; - self.blocks(&trx, |blocks| { - let blocks = blocks.collect::>(); - for block in blocks { - map.serialize_entry(&block.block_id(), &block)?; - } - Ok(()) - })?; - - map.end() - } -} - -#[cfg(test)] -mod test { - use tracing::info; - use yrs::{types::ToJson, updates::decoder::Decode, ArrayPrelim, Doc, StateVector, Update}; - - use super::*; - - #[test] - fn doc_load_test() { - let space_id = "space"; - let space_string = format!("space:{}", space_id); - - let doc = Doc::new(); - - let space = { - let mut trx = doc.transact_mut(); - let metadata = doc.get_or_insert_map_with_trx(trx.store_mut(), constants::space::META); - let pages = metadata.insert(&mut trx, "pages", ArrayPrelim::default()).unwrap(); - Space::new(&mut trx, doc.clone(), Pages::new(pages), "workspace", space_id, None) - }; - space.with_trx(|mut t| { - let block = t.create("test", "text").unwrap(); - - block.set(&mut t.trx, "test", "test").unwrap(); - }); - - let doc = space.doc(); - - let new_doc = { - let update = doc - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .and_then(|update| Update::decode_v1(&update)); - let doc = Doc::default(); - { - let mut trx = doc.transact_mut(); - match update { - Ok(update) => trx.apply_update(update), - Err(err) => info!("failed to decode update: {:?}", err), - } - trx.commit(); - } - doc - }; - - assert_json_diff::assert_json_eq!( - doc.get_or_insert_map(&space_string).to_json(&doc.transact()), - new_doc.get_or_insert_map(&space_string).to_json(&doc.transact()) - ); - - assert_json_diff::assert_json_eq!( - doc.get_or_insert_map("space:updated").to_json(&doc.transact()), - new_doc.get_or_insert_map("space:updated").to_json(&doc.transact()) - ); - } - - #[test] - fn space() { - let doc = Doc::new(); - let space = { - let mut trx = doc.transact_mut(); - let metadata = doc.get_or_insert_map_with_trx(trx.store_mut(), constants::space::META); - let pages = metadata.insert(&mut trx, "pages", ArrayPrelim::default()).unwrap(); - Space::new(&mut trx, doc.clone(), Pages::new(pages), "workspace", "space", None) - }; - - space.with_trx(|t| { - assert_eq!(space.id(), "workspace"); - assert_eq!(space.space_id(), "space"); - assert_eq!(space.blocks.len(&t.trx), 0); - assert_eq!(space.updated.len(&t.trx), 0); - }); - - space.with_trx(|mut t| { - let block = t.create("block", "text").unwrap(); - - assert_eq!(space.blocks.len(&t.trx), 1); - assert_eq!(space.updated.len(&t.trx), 1); - assert_eq!(block.block_id(), "block"); - assert_eq!(block.flavour(&t.trx), "text"); - - assert_eq!( - space.get(&t.trx, "block").map(|b| b.block_id()), - Some("block".to_owned()) - ); - - assert!(space.exists(&t.trx, "block")); - - assert!(t.remove("block")); - - assert_eq!(space.blocks.len(&t.trx), 0); - assert_eq!(space.updated.len(&t.trx), 0); - assert_eq!(space.get(&t.trx, "block"), None); - assert!(!space.exists(&t.trx, "block")); - }); - - space.with_trx(|mut t| { - Block::new(&mut t.trx, &space, "test", "test", 1).unwrap(); - let vec = space.get_blocks_by_flavour(&t.trx, "test"); - assert_eq!(vec.len(), 1); - }); - - let doc = Doc::with_client_id(123); - let mut trx = doc.transact_mut(); - let metadata = doc.get_or_insert_map_with_trx(trx.store_mut(), constants::space::META); - let pages = metadata.insert(&mut trx, "pages", ArrayPrelim::default()).unwrap(); - let space = Space::new(&mut trx, doc.clone(), Pages::new(pages), "space", "test", None); - assert_eq!(space.client_id(), 123); - } -} diff --git a/libs/jwst/src/space/transaction.rs b/libs/jwst/src/space/transaction.rs deleted file mode 100644 index 50f2ca730..000000000 --- a/libs/jwst/src/space/transaction.rs +++ /dev/null @@ -1,59 +0,0 @@ -use lib0::any::Any; -use yrs::{Map, TransactionMut}; - -use super::*; -use crate::utils::JS_INT_RANGE; - -pub struct SpaceTransaction<'a> { - pub space: &'a Space, - pub trx: TransactionMut<'a>, -} - -impl SpaceTransaction<'_> { - pub fn remove>(&mut self, block_id: S) -> bool { - self.space.remove(&mut self.trx, block_id) - } - - // create a block with specified flavour - // if block exists, return the exists block - pub fn create(&mut self, block_id: B, flavour: F) -> JwstResult - where - B: AsRef, - F: AsRef, - { - self.space.create(&mut self.trx, block_id, flavour) - } - - pub fn set_metadata(&mut self, key: &str, value: impl Into) -> JwstResult<()> { - info!("set metadata: {}", key); - let key = key.to_string(); - match value.into() { - Any::Bool(bool) => { - self.space.metadata.insert(&mut self.trx, key, bool)?; - } - Any::String(text) => { - self.space.metadata.insert(&mut self.trx, key, text.to_string())?; - } - Any::Number(number) => { - self.space.metadata.insert(&mut self.trx, key, number)?; - } - Any::BigInt(number) => { - if JS_INT_RANGE.contains(&number) { - self.space.metadata.insert(&mut self.trx, key, number as f64)?; - } else { - self.space.metadata.insert(&mut self.trx, key, number)?; - } - } - Any::Null | Any::Undefined => { - self.space.metadata.remove(&mut self.trx, &key); - } - Any::Buffer(_) | Any::Array(_) | Any::Map(_) => {} - } - - Ok(()) - } - - pub fn commit(&mut self) { - self.trx.commit(); - } -} diff --git a/libs/jwst/src/types/blob.rs b/libs/jwst/src/types/blob.rs deleted file mode 100644 index aa6ce010a..000000000 --- a/libs/jwst/src/types/blob.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::collections::HashMap; - -use bytes::Bytes; -use chrono::NaiveDateTime; -use futures::Stream; - -use super::*; - -#[derive(Debug)] -pub struct BlobMetadata { - pub content_type: String, - pub last_modified: NaiveDateTime, - pub size: i64, -} - -#[async_trait] -pub trait BlobStorage { - async fn list_blobs(&self, workspace: Option) -> JwstResult, E>; - async fn check_blob(&self, workspace: Option, id: String) -> JwstResult; - async fn get_blob( - &self, - workspace: Option, - id: String, - params: Option>, - ) -> JwstResult, E>; - async fn get_metadata( - &self, - workspace: Option, - id: String, - params: Option>, - ) -> JwstResult; - async fn put_blob_stream( - &self, - workspace: Option, - stream: impl Stream + Send, - ) -> JwstResult; - async fn put_blob(&self, workspace: Option, blob: Vec) -> JwstResult; - async fn delete_blob(&self, workspace: Option, id: String) -> JwstResult; - async fn delete_workspace(&self, workspace_id: String) -> JwstResult<(), E>; - async fn get_blobs_size(&self, workspaces: Vec) -> JwstResult; -} - -#[async_trait] -pub trait BucketBlobStorage { - async fn get_blob(&self, workspace: Option, id: String) -> JwstResult, E>; - async fn put_blob(&self, workspace: Option, hash: String, blob: Vec) -> JwstResult<(), E>; - async fn delete_blob(&self, workspace: Option, id: String) -> JwstResult; - async fn delete_workspace(&self, workspace_id: String) -> JwstResult<(), E>; -} diff --git a/libs/jwst/src/types/doc.rs b/libs/jwst/src/types/doc.rs deleted file mode 100644 index b53ca4b8b..000000000 --- a/libs/jwst/src/types/doc.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yrs::Doc; - -use super::*; - -#[async_trait] -pub trait DocStorage { - /// check if the workspace exists - async fn detect_workspace(&self, workspace_id: &str) -> JwstResult; - /// get a workspace or create a new one if not exists - async fn get_or_create_workspace(&self, workspace_id: String) -> JwstResult; - /// create a new workspace with given data or overwrite all update if exists - async fn flush_workspace(&self, workspace_id: String, data: Vec) -> JwstResult; - /// delete a workspace - async fn delete_workspace(&self, workspace_id: &str) -> JwstResult<(), E>; - - /// check if the doc exists - async fn detect_doc(&self, guid: &str) -> JwstResult; - /// get a doc by it's guid - async fn get_doc(&self, guid: String) -> JwstResult, E>; - /// delete doc - async fn delete_doc(&self, guid: &str) -> JwstResult<(), E>; - /// integrate update into doc - async fn update_doc(&self, workspace_id: String, guid: String, data: &[u8]) -> JwstResult<(), E>; - /// integrate update with subdoc information - async fn update_doc_with_guid(&self, workspace_id: String, data: &[u8]) -> JwstResult<(), E>; -} diff --git a/libs/jwst/src/types/error.rs b/libs/jwst/src/types/error.rs deleted file mode 100644 index 8a3fe9bd8..000000000 --- a/libs/jwst/src/types/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum JwstError { - #[error("io error")] - Io(#[from] std::io::Error), - #[error("doc codec error")] - DocCodec(#[from] lib0::error::Error), - #[error("doc transaction error")] - DocTransaction(String), - #[error("workspace {0} not initialized")] - WorkspaceNotInitialized(String), - #[error("workspace indexing error")] - WorkspaceReIndex, - // version metadata - #[error("workspace {0} has no version")] - VersionNotFound(String), - // page metadata - #[error("workspace {0} has no page tree")] - PageTreeNotFound(String), - #[error("page item {0} not found")] - PageItemNotFound(String), - #[error("failed to get state vector")] - SyncInitTransaction, - #[error("external error: {0}")] - ExternalError(String), -} - -pub type JwstResult = Result; diff --git a/libs/jwst/src/types/mod.rs b/libs/jwst/src/types/mod.rs deleted file mode 100644 index f140d0674..000000000 --- a/libs/jwst/src/types/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod blob; -mod doc; -mod error; - -use async_trait::async_trait; -pub use blob::{BlobMetadata, BlobStorage, BucketBlobStorage}; -pub use doc::DocStorage; -pub use error::{JwstError, JwstResult}; - -use super::Workspace; diff --git a/libs/jwst/src/utils.rs b/libs/jwst/src/utils.rs deleted file mode 100644 index 52e7169ae..000000000 --- a/libs/jwst/src/utils.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::ops::RangeInclusive; - -use base64::{ - alphabet::{STANDARD, URL_SAFE}, - engine::{general_purpose::PAD, GeneralPurpose}, -}; -pub use base64::{DecodeError as Base64DecodeError, Engine as Base64Engine}; -use lib0::encoding::Write; -use yrs::updates::encoder::{Encoder, EncoderV1}; - -const MSG_SYNC: usize = 0; -const MSG_SYNC_UPDATE: usize = 2; - -fn write_sync(encoder: &mut E) { - encoder.write_var(MSG_SYNC); -} - -pub fn sync_encode_update(update: &[u8]) -> Vec { - let mut encoder = EncoderV1::new(); - - write_sync(&mut encoder); - - encoder.write_var(MSG_SYNC_UPDATE); - encoder.write_buf(update); - - encoder.to_vec() -} - -const MAX_JS_INT: i64 = 0x001F_FFFF_FFFF_FFFF; -// The smallest int in js number. -const MIN_JS_INT: i64 = -MAX_JS_INT; -pub const JS_INT_RANGE: RangeInclusive = MIN_JS_INT..=MAX_JS_INT; - -pub const URL_SAFE_ENGINE: GeneralPurpose = GeneralPurpose::new(&URL_SAFE, PAD); -pub const STANDARD_ENGINE: GeneralPurpose = GeneralPurpose::new(&STANDARD, PAD); diff --git a/libs/jwst/src/workspace/block_observer.rs b/libs/jwst/src/workspace/block_observer.rs deleted file mode 100644 index e7e537565..000000000 --- a/libs/jwst/src/workspace/block_observer.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::{ - collections::HashSet, - sync::{ - atomic::{ - AtomicBool, - Ordering::{Acquire, Release}, - }, - mpsc::{Receiver, Sender}, - Arc, Mutex, - }, - thread::JoinHandle, - time::Duration, -}; - -use tokio::{ - runtime::{self, Runtime}, - sync::RwLock, - time::sleep, -}; -use tracing::debug; - -use super::Workspace; - -type CallbackFn = Arc) + Send + Sync>>>>>; -pub struct BlockObserverConfig { - pub(crate) workspace_id: String, - pub(super) callback: CallbackFn, - pub(super) runtime: Arc, - pub(crate) tx: Sender, - pub(super) rx: Arc>>, - // modified_block_ids can be consumed either automatically by callback or - // manually retrieval identified by is_manually_tracking_block_changes - pub(super) modified_block_ids: Arc>>, - pub(crate) handle: Arc>>>, - pub(crate) is_manually_tracking_block_changes: Arc, - pub(crate) is_observing: Arc, -} - -impl BlockObserverConfig { - pub fn new(workspace_id: String) -> Self { - let runtime = Arc::new( - runtime::Builder::new_multi_thread() - .worker_threads(2) - .enable_time() - .enable_io() - .build() - .unwrap(), - ); - let (tx, rx) = std::sync::mpsc::channel::(); - let modified_block_ids = Arc::new(RwLock::new(HashSet::new())); - let callback = Arc::new(RwLock::new(None)); - let mut block_observer_config = BlockObserverConfig { - workspace_id, - callback: callback.clone(), - runtime, - tx, - rx: Arc::new(Mutex::new(rx)), - modified_block_ids, - handle: Arc::new(Mutex::new(None)), - is_manually_tracking_block_changes: Arc::default(), - is_observing: Arc::default(), - }; - - block_observer_config.handle = Arc::new(Mutex::new(Some(block_observer_config.start_callback_thread()))); - - block_observer_config - } - - pub fn is_consuming(&self) -> bool { - self.is_observing.load(Acquire) - } - - pub fn set_callback(&self, cb: Arc) + Send + Sync>>) { - self.is_observing.store(true, Release); - let callback = self.callback.clone(); - self.runtime.spawn(async move { - *callback.write().await = Some(cb); - }); - self.is_manually_tracking_block_changes.store(false, Release); - } - - pub fn set_tracking_block_changes(&self, if_tracking: bool) { - self.is_observing.store(true, Release); - self.is_manually_tracking_block_changes.store(if_tracking, Release); - let callback = self.callback.clone(); - self.runtime.spawn(async move { - *callback.write().await = None; - }); - } - - pub fn retrieve_modified_blocks(&self) -> HashSet { - let modified_block_ids = self.modified_block_ids.clone(); - self.runtime.block_on(async move { - let mut guard = modified_block_ids.write().await; - let modified_block_ids = guard.clone(); - guard.clear(); - modified_block_ids - }) - } - - fn start_callback_thread(&self) -> JoinHandle<()> { - let rx = self.rx.clone(); - let modified_block_ids = self.modified_block_ids.clone(); - let callback = self.callback.clone(); - let runtime = self.runtime.clone(); - let is_tracking_block_changes = self.is_manually_tracking_block_changes.clone(); - let workspace_id = self.workspace_id.clone(); - std::thread::spawn(move || { - let rx = rx.lock().unwrap(); - let rt = runtime.clone(); - while let Ok(block_id) = rx.recv() { - debug!("received block change from {}", block_id); - let modified_block_ids = modified_block_ids.clone(); - let callback = callback.clone(); - let is_tracking_block_changes = is_tracking_block_changes.clone(); - let workspace_id = workspace_id.clone(); - rt.spawn(async move { - if let Some(callback) = callback.read().await.as_ref() { - let mut guard = modified_block_ids.write().await; - guard.insert(block_id); - drop(guard); - // merge changed blocks in between 200 ms - sleep(Duration::from_millis(200)).await; - let mut guard = modified_block_ids.write().await; - if !guard.is_empty() { - let block_ids = guard.iter().map(|item| item.to_owned()).collect::>(); - debug!("invoking callback with block ids: {:?}", block_ids); - callback(workspace_id, block_ids); - guard.clear(); - } - } else if is_tracking_block_changes.load(Acquire) { - let mut guard = modified_block_ids.write().await; - guard.insert(block_id); - } - }); - } - }) - } -} - -impl Workspace { - pub fn init_block_observer_config(&mut self) { - self.block_observer_config = Some(Arc::new(BlockObserverConfig::new(self.id()))); - } - - pub fn set_callback(&self, cb: Arc) + Send + Sync>>) -> bool { - if let Some(block_observer_config) = self.block_observer_config.clone() { - if !block_observer_config.is_consuming() { - block_observer_config.set_callback(cb); - } - return true; - } - false - } - - pub fn set_tracking_block_changes(&self, if_tracking: bool) { - if let Some(block_observer_config) = self.block_observer_config.clone() { - block_observer_config.set_tracking_block_changes(if_tracking); - } - } - - pub fn retrieve_modified_blocks(&self) -> Option> { - self.block_observer_config.clone().and_then(|block_observer_config| { - block_observer_config - .is_manually_tracking_block_changes - .load(Acquire) - .then(|| block_observer_config.retrieve_modified_blocks()) - }) - } - - pub fn get_tokio_runtime(&self) -> Option> { - self.block_observer_config - .clone() - .map(|block_observer_config| block_observer_config.runtime.clone()) - } -} diff --git a/libs/jwst/src/workspace/metadata/meta.rs b/libs/jwst/src/workspace/metadata/meta.rs deleted file mode 100644 index bad0117d5..000000000 --- a/libs/jwst/src/workspace/metadata/meta.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::collections::HashMap; - -use lib0::any::Any; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use yrs::{ArrayPrelim, ArrayRef, Map, MapRef, ReadTxn, Transact, TransactionMut}; - -use super::*; - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize, PartialEq)] -pub struct WorkspaceMetadata { - pub name: Option, - pub avatar: Option, - pub search_index: Vec, -} - -impl From<(&T, MapRef)> for WorkspaceMetadata { - fn from((trx, map): (&T, MapRef)) -> Self { - Self { - name: map.get(trx, constants::metadata::NAME).map(|s| s.to_string(trx)), - avatar: map.get(trx, "avatar").map(|s| s.to_string(trx)), - search_index: match map.get(trx, constants::metadata::SEARCH_INDEX) { - Some(value) => serde_json::from_str::>(&value.to_string(trx)).unwrap(), - None => vec!["title".to_string(), "text".to_string()], - }, - } - } -} - -impl From for Any { - fn from(val: WorkspaceMetadata) -> Self { - let mut map = HashMap::new(); - if let Some(name) = val.name { - map.insert(constants::metadata::NAME.to_owned(), name.into()); - } - if let Some(avatar) = val.avatar { - map.insert(constants::metadata::AVATAR.to_owned(), avatar.into()); - } - map.insert(constants::metadata::SEARCH_INDEX.to_owned(), val.search_index.into()); - Any::Map(map.into()) - } -} - -impl Workspace { - pub fn metadata(&self) -> WorkspaceMetadata { - (&self.doc().transact(), self.metadata.clone()).into() - } - - pub fn pages(&self, trx: &mut TransactionMut) -> JwstResult { - Ok( - if let Some(pages) = self.metadata.get(trx, "pages").and_then(|v| v.to_yarray()) { - pages - } else { - self.metadata.insert(trx, "pages", ArrayPrelim::default())? - }, - ) - } -} - -#[cfg(test)] -mod tests { - use yrs::Doc; - - use super::*; - - #[test] - fn test_workspace_metadata() { - let doc = Doc::new(); - let ws = Workspace::from_doc(doc, "test"); - ws.set_search_index(vec!["test1".to_string(), "test2".to_string()]) - .unwrap(); - ws.with_trx(|mut t| { - t.set_metadata(constants::metadata::NAME, "test_name").unwrap(); - }); - ws.with_trx(|mut t| { - t.set_metadata(constants::metadata::AVATAR, "test_avatar").unwrap(); - }); - assert_eq!( - ws.metadata(), - WorkspaceMetadata { - name: Some("test_name".to_string()), - avatar: Some("test_avatar".to_string()), - search_index: vec!["test1".to_string(), "test2".to_string()], - } - ); - } -} diff --git a/libs/jwst/src/workspace/metadata/mod.rs b/libs/jwst/src/workspace/metadata/mod.rs deleted file mode 100644 index c2f841136..000000000 --- a/libs/jwst/src/workspace/metadata/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod meta; -mod pages; - -pub use meta::WorkspaceMetadata; -pub use pages::Pages; - -use super::*; diff --git a/libs/jwst/src/workspace/metadata/pages.rs b/libs/jwst/src/workspace/metadata/pages.rs deleted file mode 100644 index 0f7e5c504..000000000 --- a/libs/jwst/src/workspace/metadata/pages.rs +++ /dev/null @@ -1,199 +0,0 @@ -use std::collections::HashMap; - -use lib0::any::Any; -use yrs::{types::Value, Array, ArrayRef, Map, MapRef, ReadTxn}; - -pub struct PageMeta { - pub id: String, - pub favorite: Option, - pub is_pinboard: Option, - pub is_shared: Option, - pub init: Option, - pub sub_page_ids: Vec, - pub title: Option, - pub trash: Option, - pub trash_date: Option, -} - -impl PageMeta { - fn get_string_array(trx: &T, map: &MapRef, key: &str) -> Vec { - map.get(trx, key) - .and_then(|v| { - if let Value::Any(Any::Array(a)) = v { - Some( - a.iter() - .filter_map(|sub_page| { - if let Any::String(str) = sub_page { - Some(str.to_string()) - } else { - None - } - }) - .collect(), - ) - } else { - None - } - }) - .unwrap_or_default() - } - - fn get_bool(trx: &T, map: &MapRef, key: &str) -> Option { - map.get(trx, key).and_then(|v| { - if let Value::Any(Any::Bool(b)) = v { - Some(b) - } else { - None - } - }) - } - - fn get_number(trx: &T, map: &MapRef, key: &str) -> Option { - map.get(trx, key).and_then(|v| { - if let Value::Any(Any::Number(n)) = v { - Some(n) - } else { - None - } - }) - } -} - -impl From<(&T, MapRef)> for PageMeta { - fn from((trx, map): (&T, MapRef)) -> Self { - Self { - id: map.get(trx, "id").unwrap().to_string(trx), - favorite: Self::get_bool(trx, &map, "favorite"), - is_pinboard: Self::get_bool(trx, &map, "isRootPinboard"), - is_shared: Self::get_bool(trx, &map, "isPublic").or_else(|| { - Self::get_number(trx, &map, "isPublic").map(|exp| { - // if isPublic is a number, it is a expire time timestamp - let exp = exp as i64; - let now = chrono::Utc::now().timestamp(); - exp > now - }) - }), - init: Self::get_bool(trx, &map, "init"), - sub_page_ids: Self::get_string_array(trx, &map, "subpageIds"), - title: map.get(trx, "title").map(|s| s.to_string(trx)), - trash: Self::get_bool(trx, &map, "trash"), - trash_date: Self::get_number(trx, &map, "trashDate") - .map(|v| v as usize) - .filter(|v| *v > 0), - } - } -} - -#[derive(Clone)] -pub struct Pages { - pages: ArrayRef, -} - -impl Pages { - pub fn new(pages: ArrayRef) -> Self { - Self { pages } - } - - fn pages(&self, trx: &T) -> HashMap { - self.pages - .iter(trx) - .filter_map(|v| { - v.to_ymap().map(|v| { - let meta = PageMeta::from((trx, v)); - (meta.id.clone(), meta) - }) - }) - .collect() - } - - fn check_pinboard(pages: &HashMap, page_id: &str) -> bool { - if let Some(root_pinboard_page) = pages - .values() - .find(|meta| meta.is_pinboard.unwrap_or(false) && meta.is_shared.unwrap_or(false)) - { - let mut visited = vec![]; - let mut stack = vec![root_pinboard_page.id.clone()]; - while let Some(current_page_id) = stack.pop() { - if visited.contains(¤t_page_id) { - continue; - } - visited.push(current_page_id.clone()); - if let Some(page) = pages.get(¤t_page_id) { - if page.id == page_id { - return true; - } - stack.extend(page.sub_page_ids.clone()); - } - } - } - false - } - - pub fn check_shared(&self, trx: &T, page_id: &str) -> bool { - let pages = self.pages(trx); - if pages.contains_key(page_id) { - Self::check_pinboard(&pages, page_id) - || pages - .values() - .any(|meta| meta.is_shared.unwrap_or(false) && meta.id == page_id) - } else { - false - } - } -} - -#[cfg(test)] -mod tests { - use yrs::{updates::decoder::Decode, ArrayPrelim, Doc, Transact, Update}; - - use super::*; - use crate::Workspace; - - #[test] - fn test_page_meta() { - let doc = Doc::new(); - let map = doc.get_or_insert_map("test"); - let mut trx = doc.transact_mut(); - map.insert(&mut trx, "id", "test_page").unwrap(); - map.insert(&mut trx, "favorite", true).unwrap(); - map.insert(&mut trx, "isRootPinboard", true).unwrap(); - map.insert(&mut trx, "init", true).unwrap(); - map.insert(&mut trx, "subpageIds", ArrayPrelim::default()).unwrap(); - map.insert(&mut trx, "title", "test_title").unwrap(); - map.insert(&mut trx, "trash", true).unwrap(); - map.insert(&mut trx, "trashDate", 1234567890).unwrap(); - - let meta = PageMeta::from((&trx, map)); - assert_eq!(meta.id, "test_page"); - assert_eq!(meta.favorite, Some(true)); - assert_eq!(meta.is_pinboard, Some(true)); - assert_eq!(meta.init, Some(true)); - assert_eq!(meta.sub_page_ids, Vec::::new()); - assert_eq!(meta.title, Some("test_title".to_string())); - assert_eq!(meta.trash, Some(true)); - assert_eq!(meta.trash_date, Some(1234567890)); - } - - #[test] - fn test_shared_page() { - let doc = Doc::new(); - doc.transact_mut() - .apply_update(Update::decode_v1(include_bytes!("../../../fixtures/test_shared_page.bin")).unwrap()); - let ws = Workspace::from_doc(doc, "test"); - // test page - - // - test page (shared page not in Pinboard) - assert!(ws.with_trx(|mut t| t.get_space("X83xzrb4Yr").shared(&t.trx))); - // - test page (unshared sub page of X83xzrb4Yr ) - assert!(!ws.with_trx(|mut t| t.get_space("ZISRn1STfy").shared(&t.trx))); - - // - test page (RootPinboard without shared) - assert!(!ws.with_trx(|mut t| t.get_space("m92E0qWwPY").shared(&t.trx))); - // - test page (unshared sub page of m92E0qWwPY in Pinboard) - assert!(!ws.with_trx(|mut t| t.get_space("2HadvFQVk3").shared(&t.trx))); - // - test page (shared sub page of 2HadvFQVk3 in Pinboard) - assert!(ws.with_trx(|mut t| t.get_space("ymMTOFx8tt").shared(&t.trx))); - // - test page (unshared sub page of ymMTOFx8tt in Pinboard) - assert!(!ws.with_trx(|mut t| t.get_space("lBaYQm5ZVo").shared(&t.trx))); - } -} diff --git a/libs/jwst/src/workspace/mod.rs b/libs/jwst/src/workspace/mod.rs deleted file mode 100644 index 7c5166ccc..000000000 --- a/libs/jwst/src/workspace/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod block_observer; -mod metadata; -mod observe; -mod plugins; -mod sync; -mod transaction; -mod workspace; - -pub use block_observer::BlockObserverConfig; -pub use metadata::{Pages, WorkspaceMetadata}; -#[cfg(feature = "workspace-search")] -pub use plugins::{SearchResult, SearchResults}; -pub use transaction::WorkspaceTransaction; -pub use workspace::{MapSubscription, Workspace}; - -use super::{constants, error, info, trace, warn, JwstError, JwstResult, Space}; diff --git a/libs/jwst/src/workspace/observe.rs b/libs/jwst/src/workspace/observe.rs deleted file mode 100644 index 5cf598d45..000000000 --- a/libs/jwst/src/workspace/observe.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{ - panic::{catch_unwind, AssertUnwindSafe}, - thread::sleep, - time::Duration, -}; - -use jwst_codec::{Awareness, AwarenessEvent}; -use nanoid::nanoid; -use yrs::{TransactionMut, UpdateEvent}; - -use super::*; - -impl Workspace { - pub async fn on_awareness_update(&mut self, f: impl Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static) { - self.awareness.write().await.on_update(f); - } - - /// Subscribe to update events. - pub fn observe(&mut self, f: impl Fn(&TransactionMut, &UpdateEvent) + Clone + 'static) -> Option { - info!("workspace observe enter"); - let doc = self.doc(); - match catch_unwind(AssertUnwindSafe(move || { - let mut retry = 10; - let cb = move |trx: &TransactionMut, evt: &UpdateEvent| { - trace!("workspace observe: observe_update_v1, {:?}", &evt.update); - if let Err(e) = catch_unwind(AssertUnwindSafe(|| f(trx, evt))) { - error!("panic in observe callback: {:?}", e); - } - }; - - loop { - match doc.observe_update_v1(cb.clone()) { - Ok(sub) => break Ok(sub), - Err(e) if retry <= 0 => break Err(e), - _ => { - sleep(Duration::from_micros(100)); - retry -= 1; - continue; - } - } - } - })) { - Ok(sub) => match sub { - Ok(sub) => { - let id = nanoid!(); - match self.sub.lock() { - Ok(mut guard) => { - guard.insert(id.clone(), sub); - } - Err(e) => { - error!("failed to lock sub: {:?}", e); - } - } - Some(id) - } - Err(e) => { - error!("failed to observe: {:?}", e); - None - } - }, - Err(e) => { - error!("panic in observe callback: {:?}", e); - None - } - } - } -} diff --git a/libs/jwst/src/workspace/plugins/indexing/indexer.rs b/libs/jwst/src/workspace/plugins/indexing/indexer.rs deleted file mode 100644 index d1d4abfd5..000000000 --- a/libs/jwst/src/workspace/plugins/indexing/indexer.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::{ - collections::HashMap, - rc::Rc, - sync::{atomic::AtomicU32, Arc}, -}; - -use lib0::any::Any; -use serde::{Deserialize, Serialize}; -use tantivy::{collector::TopDocs, query::QueryParser, schema::*, Index, ReloadPolicy}; -use utoipa::ToSchema; - -use super::{PluginImpl, Workspace}; - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct SearchResult { - pub block_id: String, - pub score: f32, -} - -/// Returned from [`Workspace::search`] -/// -/// [`Workspace::search`]: crate::Workspace::search -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct SearchResults(Vec); - -impl SearchResults { - pub fn into_inner(self) -> Vec { - self.0 - } -} - -pub struct IndexingPluginImpl { - // /// `true` if the text search has not yet populated the Tantivy index - // /// `false` if there should only be incremental changes necessary to the blocks. - // first_index: bool, - pub(super) queue_reindex: Arc, - pub(super) schema: Schema, - pub(super) index: Rc, - pub(super) query_parser: QueryParser, - pub(super) search_index: Vec, -} - -impl IndexingPluginImpl { - pub fn search>(&self, query: S) -> Result> { - let mut items = Vec::new(); - if self.search_index.is_empty() { - return Ok(SearchResults(items)); - } - - let reader = self - .index - .reader_builder() - .reload_policy(ReloadPolicy::OnCommit) - .try_into()?; - let searcher = reader.searcher(); - let query = self.query_parser.parse_query(query.as_ref())?; - let top_docs = searcher.search(&query, &TopDocs::with_limit(10))?; - // The actual documents still need to be retrieved from Tantivy’s store. - // Since the body field was not configured as stored, the document returned will - // only contain a title. - - if !top_docs.is_empty() { - let block_id_field = self.schema.get_field("block_id").unwrap(); - - for (score, doc_address) in top_docs { - let retrieved_doc = searcher.doc(doc_address)?; - if let Some(Value::Str(id)) = retrieved_doc.get_first(block_id_field) { - items.push(SearchResult { - block_id: id.to_string(), - score, - }); - } else { - let to_json = self.schema.to_json(&retrieved_doc); - eprintln!("Unexpected non-block doc in Tantivy result set: {to_json}"); - } - } - } - - Ok(SearchResults(items)) - } -} - -impl PluginImpl for IndexingPluginImpl { - fn on_update(&mut self, ws: &Workspace) -> Result<(), Box> { - let curr = self.queue_reindex.load(std::sync::atomic::Ordering::SeqCst); - if curr > 0 { - // TODO: reindex - - let re_index_list = ws.with_trx(|t| { - t.spaces(|spaces| { - spaces - .flat_map(|space| { - space.blocks(&t.trx, |blocks| { - blocks - .map(|block| { - ( - format!("{}:{}", space.space_id(), block.block_id()), - self.search_index - .iter() - .map(|field| { - block.content(&t.trx).get(field).map(ToOwned::to_owned).and_then( - |a| match a { - Any::String(str) => Some(str.to_string()), - _ => None, - }, - ) - }) - .collect(), - ) - }) - .collect::>() - }) - }) - .collect::>() - }) - }); - - // dbg!((txn, upd)); - // println!("got update event: {items}"); - // just re-index stupidly - self.re_index_content(re_index_list) - .map_err(|err| format!("Error during reindex: {err:?}"))?; - } - - // reset back down now that the update was applied - self.queue_reindex.fetch_sub(curr, std::sync::atomic::Ordering::SeqCst); - - Ok(()) - } -} - -impl IndexingPluginImpl { - fn re_index_content( - &mut self, - blocks: BlockIdTitleAndTextIter, - ) -> Result<(), Box> - where - // TODO: use a structure with better names than tuples? - BlockIdTitleAndTextIter: IntoIterator>)>, - { - let block_id_field = self.schema.get_field("block_id").unwrap(); - - let mut writer = self - .index - .writer(50_000_000) - .map_err(|err| format!("Error creating writer: {err:?}"))?; - - let search_index = self - .search_index - .iter() - .map(|filed| self.schema.get_field(filed).unwrap()) - .collect::>(); - - for (block_id, fields) in blocks { - let mut block_doc = Document::new(); - block_doc.add_text(block_id_field, block_id); - fields.iter().enumerate().for_each(|(index, field)| { - if let Some(field_text) = field { - let index_field = search_index.get(index).unwrap().to_owned(); - block_doc.add_text(index_field, field_text); - } - }); - writer.add_document(block_doc)?; - } - - // If .commit() returns correctly, then all of the documents that have been - // added are guaranteed to be persistently indexed. - writer.commit()?; - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::{super::*, *}; - - // out of order for now, in the future, this can be made in order by sorting - // before we reduce to just the block ids. Then maybe we could first sort on - // score, then sort on block id. - macro_rules! expect_result_ids { - ($search_results:ident, $id_str_array:expr) => { - let mut sorted_ids = $search_results.0.iter().map(|i| &i.block_id).collect::>(); - sorted_ids.sort(); - assert_eq!( - sorted_ids, $id_str_array, - "Expected found ids (left) match expected ids (right) for search results" - ); - }; - } - macro_rules! expect_search_gives_ids { - ($search_plugin:ident, $query_text:expr, $id_str_array:expr) => { - let search_result = $search_plugin - .search($query_text) - .expect("no error searching"); - - let line = line!(); - println!("Search results (workspace.rs:{line}): {search_result:#?}"); // will show if there is an issue running the test - - expect_result_ids!(search_result, $id_str_array); - }; - } - - #[test] - fn basic_search_test() { - let workspace = { - let workspace = Workspace::from_doc(Default::default(), "wk-load"); - // even though the plugin is added by default, - super::super::super::insert_plugin(workspace, IndexingPluginRegister::ram()) - .expect("failed to insert plugin") - }; - - workspace.with_trx(|mut t| { - let space1 = t.get_space("space1"); - - let block = space1.create(&mut t.trx, "b1", "text").unwrap(); - - block.set(&mut t.trx, "test", "test").unwrap(); - - let block = space1.create(&mut t.trx, "a", "affine:text").unwrap(); - let b = space1.create(&mut t.trx, "b", "affine:text").unwrap(); - let c = space1.create(&mut t.trx, "c", "affine:text").unwrap(); - let d = space1.create(&mut t.trx, "d", "affine:text").unwrap(); - let e = space1.create(&mut t.trx, "e", "affine:text").unwrap(); - let f = space1.create(&mut t.trx, "f", "affine:text").unwrap(); - - b.set(&mut t.trx, "title", "Title B content").unwrap(); - b.set(&mut t.trx, "text", "Text B content bbb xxx").unwrap(); - - c.set(&mut t.trx, "title", "Title C content").unwrap(); - c.set(&mut t.trx, "text", "Text C content ccc xxx yyy").unwrap(); - - d.set(&mut t.trx, "title", "Title D content").unwrap(); - d.set(&mut t.trx, "text", "Text D content ddd yyy").unwrap(); - - e.set(&mut t.trx, "title", "人民日报").unwrap(); - e.set( - &mut t.trx, - "text", - "张华考上了北京大学;李萍进了中等技术学校;我在百货公司当售货员:我们都有光明的前途", - ) - .unwrap(); - - f.set(&mut t.trx, "title", "美国首次成功在核聚变反应中实现“净能量增益”") - .unwrap(); - f.set( - &mut t.trx, - "text", - "当地时间13日,美国能源部官员宣布,由美国政府资助的加州劳伦斯·利弗莫尔国家实验室(LLNL),\ - 首次成功在核聚变反应中实现“净能量增益”,即聚变反应产生的能量大于促发该反应的镭射能量。", - ) - .unwrap(); - - // pushing blocks in - block.push_children(&mut t.trx, &b).unwrap(); - block.insert_children_at(&mut t.trx, &c, 0).unwrap(); - block.insert_children_before(&mut t.trx, &d, "b").unwrap(); - block.insert_children_after(&mut t.trx, &e, "b").unwrap(); - block.insert_children_after(&mut t.trx, &f, "c").unwrap(); - - assert_eq!( - block.children(&t.trx), - vec![ - "c".to_owned(), - "f".to_owned(), - "d".to_owned(), - "b".to_owned(), - "e".to_owned() - ] - ); - - // Question: Is this supposed to indicate that since this block is detached, - // then we should not be indexing it? For example, should we walk up - // the parent tree to check if each block is actually attached? - block.remove_children(&mut t.trx, &d).unwrap(); - - println!("Blocks: {:#?}", space1.blocks); // shown if there is an - // issue running the test. - }); - - workspace.with_trx(|mut t| { - let space2 = t.get_space("space2"); - - let block = space2.create(&mut t.trx, "b1", "text").unwrap(); - - block.set(&mut t.trx, "test", "test").unwrap(); - - let block = space2.create(&mut t.trx, "a1", "affine:text").unwrap(); - let b = space2.create(&mut t.trx, "b1", "affine:text").unwrap(); - let c = space2.create(&mut t.trx, "c1", "affine:text").unwrap(); - let d = space2.create(&mut t.trx, "d1", "affine:text").unwrap(); - let e = space2.create(&mut t.trx, "e1", "affine:text").unwrap(); - let f = space2.create(&mut t.trx, "f1", "affine:text").unwrap(); - - b.set(&mut t.trx, "title", "Title B content").unwrap(); - b.set(&mut t.trx, "text", "Text B content bbb xxx").unwrap(); - - c.set(&mut t.trx, "title", "Title C content").unwrap(); - c.set(&mut t.trx, "text", "Text C content ccc xxx yyy").unwrap(); - - d.set(&mut t.trx, "title", "Title D content").unwrap(); - d.set(&mut t.trx, "text", "Text D content ddd yyy").unwrap(); - - e.set(&mut t.trx, "title", "人民日报").unwrap(); - e.set( - &mut t.trx, - "text", - "张华考上了北京大学;李萍进了中等技术学校;我在百货公司当售货员:我们都有光明的前途", - ) - .unwrap(); - - f.set(&mut t.trx, "title", "美国首次成功在核聚变反应中实现“净能量增益”") - .unwrap(); - f.set( - &mut t.trx, - "text", - "当地时间13日,美国能源部官员宣布,由美国政府资助的加州劳伦斯·利弗莫尔国家实验室(LLNL),\ - 首次成功在核聚变反应中实现“净能量增益”,即聚变反应产生的能量大于促发该反应的镭射能量。", - ) - .unwrap(); - - // pushing blocks in - block.push_children(&mut t.trx, &b).unwrap(); - block.insert_children_at(&mut t.trx, &c, 0).unwrap(); - block.insert_children_before(&mut t.trx, &d, "b1").unwrap(); - block.insert_children_after(&mut t.trx, &e, "b1").unwrap(); - block.insert_children_after(&mut t.trx, &f, "c1").unwrap(); - - assert_eq!( - block.children(&t.trx), - vec![ - "c1".to_owned(), - "f1".to_owned(), - "d1".to_owned(), - "b1".to_owned(), - "e1".to_owned() - ] - ); - - // Question: Is this supposed to indicate that since this block is detached, - // then we should not be indexing it? For example, should we walk up - // the parent tree to check if each block is actually attached? - block.remove_children(&mut t.trx, &d).unwrap(); - - println!("Blocks: {:#?}", space2.blocks); // shown if there is an - // issue running the test. - }); - - workspace - .update_plugin::() - .expect("update text search plugin"); - - assert!(workspace - .with_plugin::(|search_plugin| { - expect_search_gives_ids!( - search_plugin, - "content", - &[ - "space1:b", - "space1:c", - "space1:d", - "space2:b1", - "space2:c1", - "space2:d1" - ] - ); - expect_search_gives_ids!(search_plugin, "bbb", &["space1:b", "space2:b1",]); - expect_search_gives_ids!(search_plugin, "ccc", &["space1:c", "space2:c1"]); - expect_search_gives_ids!( - search_plugin, - "xxx", - &["space1:b", "space1:c", "space2:b1", "space2:c1"] - ); - expect_search_gives_ids!( - search_plugin, - "yyy", - &["space1:c", "space1:d", "space2:c1", "space2:d1"] - ); - - expect_search_gives_ids!(search_plugin, "人民日报", &["space1:e", "space2:e1"]); - expect_search_gives_ids!(search_plugin, "技术学校", &["space1:e", "space2:e1"]); - - expect_search_gives_ids!(search_plugin, "核聚变反应", &["space1:f", "space2:f1"]); - expect_search_gives_ids!(search_plugin, "镭射能量", &["space1:f", "space2:f1"]); - }) - .is_some()); - } -} diff --git a/libs/jwst/src/workspace/plugins/indexing/mod.rs b/libs/jwst/src/workspace/plugins/indexing/mod.rs deleted file mode 100644 index 18f207dbd..000000000 --- a/libs/jwst/src/workspace/plugins/indexing/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod indexer; -mod register; -mod tokenizer; - -pub use indexer::{IndexingPluginImpl, SearchResult, SearchResults}; -pub(super) use register::IndexingPluginRegister; -use tokenizer::{tokenizers_register, GRAM_TOKENIZER}; - -use super::{PluginImpl, PluginRegister, Workspace}; diff --git a/libs/jwst/src/workspace/plugins/indexing/register.rs b/libs/jwst/src/workspace/plugins/indexing/register.rs deleted file mode 100644 index 35665f1b7..000000000 --- a/libs/jwst/src/workspace/plugins/indexing/register.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::{ - path::PathBuf, - rc::Rc, - sync::{atomic::AtomicU32, Arc}, -}; - -use tantivy::{ - query::QueryParser, - schema::{IndexRecordOption, Schema, TextFieldIndexing, TextOptions, STORED, STRING}, - Index, -}; - -use super::*; - -#[derive(Debug)] -enum IndexingStorageKind { - /// Store index in memory (default) - Ram, - /// Store index in a specific directory - #[allow(dead_code)] - PersistedDirectory(PathBuf), -} - -impl Default for IndexingStorageKind { - fn default() -> Self { - Self::Ram - } -} - -#[derive(Default, Debug)] -pub struct IndexingPluginRegister { - storage_kind: IndexingStorageKind, -} - -impl IndexingPluginRegister { - #[allow(dead_code)] - pub fn ram() -> Self { - Self { - storage_kind: IndexingStorageKind::Ram, - } - } - - #[allow(dead_code)] - pub fn persisted_directory(path: PathBuf) -> Self { - Self { - storage_kind: IndexingStorageKind::PersistedDirectory(path), - } - } -} - -impl PluginRegister for IndexingPluginRegister { - type Plugin = IndexingPluginImpl; - fn setup(self, ws: &mut Workspace) -> Result> { - let search_index = ws.metadata().search_index; - let options = TextOptions::default().set_indexing_options( - TextFieldIndexing::default() - .set_tokenizer(GRAM_TOKENIZER) - .set_index_option(IndexRecordOption::WithFreqsAndPositions), - ); - - let mut schema_builder = Schema::builder(); - schema_builder.add_text_field("block_id", STRING | STORED); - search_index.iter().for_each(|field_name| { - schema_builder.add_text_field(field_name.as_str(), options.clone()); - }); - let schema = schema_builder.build(); - - let index_dir: Box = match &self.storage_kind { - IndexingStorageKind::Ram => Box::new(tantivy::directory::RamDirectory::create()), - IndexingStorageKind::PersistedDirectory(dir) => Box::new(tantivy::directory::MmapDirectory::open(dir)?), - }; - - let index = Rc::new({ - let index = Index::open_or_create(index_dir, schema.clone())?; - tokenizers_register(index.tokenizers()); - index - }); - - let mut fields = vec![]; - search_index.iter().for_each(|field_name| { - let body = schema.get_field(field_name.as_str()).unwrap(); - fields.push(body); - }); - - let queue_reindex = Arc::new(AtomicU32::new( - // require an initial re-index by setting the default above 0 - 1, - )); - - let queue_reindex_clone = queue_reindex.clone(); - ws.observe(move |_txn, _e| { - // upd.update - // let u = yrs::Update::decode_v1(&e.update).unwrap(); - // let _items = u - // .as_items() - // .into_iter() - // .map(|i| format!("\n {i:?}")) - // .collect::(); - // for item in u.as_items() { - // item.id; - // } - queue_reindex_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - }); - - Ok(IndexingPluginImpl { - schema, - query_parser: QueryParser::for_index(&index, fields), - index, - queue_reindex, - search_index, - }) - } -} diff --git a/libs/jwst/src/workspace/plugins/indexing/tokenizer.rs b/libs/jwst/src/workspace/plugins/indexing/tokenizer.rs deleted file mode 100644 index 01e6bb0d0..000000000 --- a/libs/jwst/src/workspace/plugins/indexing/tokenizer.rs +++ /dev/null @@ -1,7 +0,0 @@ -use tantivy::tokenizer::{NgramTokenizer, TokenizerManager}; - -pub const GRAM_TOKENIZER: &str = "gram"; - -pub fn tokenizers_register(tokenizers: &TokenizerManager) { - tokenizers.register(GRAM_TOKENIZER, NgramTokenizer::new(1, 10, false)); -} diff --git a/libs/jwst/src/workspace/plugins/mod.rs b/libs/jwst/src/workspace/plugins/mod.rs deleted file mode 100644 index e82f8bb99..000000000 --- a/libs/jwst/src/workspace/plugins/mod.rs +++ /dev/null @@ -1,81 +0,0 @@ -#[cfg(feature = "workspace-search")] -mod indexing; -mod plugin; - -#[cfg(feature = "workspace-search")] -use indexing::IndexingPluginImpl; -#[cfg(feature = "workspace-search")] -pub use indexing::{SearchResult, SearchResults}; -pub(super) use plugin::{PluginImpl, PluginMap, PluginRegister}; - -use super::*; - -/// Setup a [WorkspacePlugin] and insert it into the [Workspace]. -/// See [plugins]. -fn insert_plugin( - mut workspace: Workspace, - config: impl PluginRegister, -) -> Result> { - let plugin = config.setup(&mut workspace)?; - workspace.plugins.insert_plugin(plugin)?; - - Ok(workspace) -} - -/// Setup plugin: [indexing] -pub(crate) fn setup_plugin(workspace: Workspace) -> Workspace { - // default plugins - if cfg!(feature = "workspace-search") { - // Set up indexing plugin - insert_plugin(workspace, indexing::IndexingPluginRegister::default()).expect("Failed to setup search plugin") - } else { - workspace - } -} - -impl Workspace { - /// Allow the plugin to run any necessary updates it could have flagged via - /// observers. See [plugins]. - fn update_plugin(&self) -> Result<(), Box> { - self.plugins.update_plugin::

(self) - } - - /// See [plugins]. - fn with_plugin(&self, cb: impl Fn(&P) -> T) -> Option { - self.plugins.with_plugin::(cb) - } - - #[cfg(feature = "workspace-search")] - pub fn search>(&self, query: S) -> Result> { - // refresh index if doc has update - self.update_plugin::()?; - - let query = query.as_ref(); - - self.with_plugin::>>(|search_plugin| { - search_plugin.search(query) - }) - .expect("text search was set up by default") - } - - #[cfg(feature = "workspace-search")] - pub fn search_result(&self, query: String) -> String { - match self.search(query) { - Ok(list) => serde_json::to_string(&list).unwrap(), - Err(_) => "[]".to_string(), - } - } - - pub fn set_search_index(&self, fields: Vec) -> JwstResult { - let fields = fields.iter().filter(|f| !f.is_empty()).cloned().collect::>(); - if fields.is_empty() { - error!("search index cannot be empty"); - return Ok(false); - } - - let value = serde_json::to_string(&fields).map_err(|_| JwstError::WorkspaceReIndex)?; - self.retry_with_trx(|mut trx| trx.set_metadata(constants::metadata::SEARCH_INDEX, value), 10)??; - setup_plugin(self.clone()); - Ok(true) - } -} diff --git a/libs/jwst/src/workspace/plugins/plugin.rs b/libs/jwst/src/workspace/plugins/plugin.rs deleted file mode 100644 index 667b350bc..000000000 --- a/libs/jwst/src/workspace/plugins/plugin.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Plugins are an internal experimental interface for extending the -//! [Workspace]. - -use std::sync::{Arc, RwLock}; - -use type_map::TypeMap; - -use super::*; - -/// A configuration from which a [WorkspacePlugin] can be created from. -pub(crate) trait PluginRegister { - type Plugin: PluginImpl; - // Do we need self? - fn setup(self, ws: &mut Workspace) -> Result>; - // Do we need a clean-up thing? - // -> Box; -} - -/// A workspace plugin which comes from a corresponding -/// [WorkspacePluginConfig::setup]. In that setup call, the plugin will have -/// initial access to the whole [Workspace], and will be able to add listeners -/// to changes to blocks in the [Workspace]. -pub(crate) trait PluginImpl: 'static { - /// IDEA 1/10: - /// This update is called sometime between when we know changes have been - /// made to the workspace and the time when we will get the plugin to - /// query its data (e.g. search()) - fn on_update(&mut self, _ws: &Workspace) -> Result<(), Box> { - // Default implementation for a WorkspacePlugin update does nothing. - Ok(()) - } -} - -#[derive(Default, Clone)] -pub(crate) struct PluginMap { - /// We store plugins into the TypeMap, so that their ownership is tied to - /// [Workspace]. This enables us to properly manage lifetimes of - /// observers which will subscribe into events that the [Workspace] - /// experiences, like block updates. - map: Arc>, -} - -impl PluginMap { - pub(crate) fn insert_plugin(&self, plugin: P) -> Result<&Self, Box> { - self.map.write().unwrap().insert(plugin); - Ok(self) - } - - pub(crate) fn with_plugin(&self, cb: impl Fn(&P) -> T) -> Option { - let map = self.map.read().unwrap(); - let plugin = map.get::

(); - plugin.map(cb) - } - - pub(crate) fn update_plugin(&self, ws: &Workspace) -> Result<(), Box> { - let mut map = self.map.write().unwrap(); - let plugin = map.get_mut::

().ok_or("Plugin not found")?; - - plugin.on_update(ws)?; - - Ok(()) - } -} diff --git a/libs/jwst/src/workspace/sync.rs b/libs/jwst/src/workspace/sync.rs deleted file mode 100644 index 116cc7dea..000000000 --- a/libs/jwst/src/workspace/sync.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{ - panic::{catch_unwind, AssertUnwindSafe}, - thread::sleep, - time::Duration, -}; - -use jwst_codec::{write_sync_message, DocMessage, SyncMessage, SyncMessageScanner}; -use yrs::{ - updates::{decoder::Decode, encoder::Encode}, - ReadTxn, StateVector, Transact, Update, -}; - -use super::*; -use crate::RETRY_NUM; - -impl Workspace { - pub fn sync_migration(&self) -> JwstResult> { - let mut retry = RETRY_NUM; - let trx = loop { - match self.doc.try_transact() { - Ok(trx) => break trx, - Err(e) => { - if retry > 0 { - retry -= 1; - sleep(Duration::from_micros(10)); - } else { - return Err(JwstError::DocTransaction(e.to_string())); - } - } - } - }; - Ok(trx.encode_state_as_update_v1(&StateVector::default())?) - } - - pub async fn sync_init_message(&self) -> JwstResult> { - let (sv, awareness_update) = { - let mut retry = RETRY_NUM; - let trx = loop { - if let Ok(trx) = self.doc.try_transact() { - break trx; - } else if retry > 0 { - retry -= 1; - tokio::time::sleep(Duration::from_micros(10)).await; - } else { - return Err(JwstError::SyncInitTransaction); - } - }; - let sv: StateVector = trx.state_vector(); - let awareness = self.awareness.read().await; - (sv, SyncMessage::Awareness(awareness.get_states().clone())) - }; - - let mut buffer = Vec::new(); - write_sync_message(&mut buffer, &SyncMessage::Doc(DocMessage::Step1(sv.encode_v1()?)))?; - write_sync_message(&mut buffer, &awareness_update)?; - - Ok(buffer) - } - - pub async fn sync_messages(&mut self, buffers: Vec>) -> Vec> { - let mut awareness = vec![]; - let mut content = vec![]; - - for buffer in buffers { - let (awareness_msg, content_msg): (Vec<_>, Vec<_>) = SyncMessageScanner::new(&buffer) - .flatten() - .partition(|msg| matches!(msg, SyncMessage::Awareness(_) | SyncMessage::AwarenessQuery)); - awareness.extend(awareness_msg); - content.extend(content_msg); - } - - let mut result = vec![]; - - result.extend(self.sync_awareness(awareness).await); - result.extend(self.sync_content(content)); - - result - } - - async fn sync_awareness(&mut self, msgs: Vec) -> Vec> { - let mut result = vec![]; - if !msgs.is_empty() { - let mut awareness = self.awareness.write().await; - for msg in msgs { - match msg { - SyncMessage::AwarenessQuery => { - let mut buffer = Vec::new(); - if let Err(e) = - write_sync_message(&mut buffer, &SyncMessage::Awareness(awareness.get_states().clone())) - { - warn!("failed to encode awareness update: {:?}", e); - } else { - result.push(buffer); - } - } - SyncMessage::Awareness(update) => awareness.apply_update(update), - _ => {} - } - } - } - result - } - - fn sync_content(&mut self, msg: Vec) -> Vec> { - let mut result = vec![]; - if !msg.is_empty() { - let doc = self.doc(); - if let Err(e) = catch_unwind(AssertUnwindSafe(|| { - let mut retry = RETRY_NUM; - let mut trx = loop { - if let Ok(trx) = doc.try_transact_mut() { - break trx; - } else if retry > 0 { - retry -= 1; - sleep(Duration::from_micros(10)); - } else { - return; - } - }; - for msg in msg { - if let Some(msg) = { - trace!("processing message: {:?}", msg); - match msg { - SyncMessage::Doc(msg) => match msg { - DocMessage::Step1(sv) => StateVector::decode_v1(&sv).ok().and_then(|sv| { - trx.encode_state_as_update_v1(&sv) - .map(|update| SyncMessage::Doc(DocMessage::Step2(update))) - .ok() - }), - DocMessage::Step2(update) => { - if let Ok(update) = Update::decode_v1(&update) { - trx.apply_update(update); - } - None - } - DocMessage::Update(update) => { - if let Ok(update) = Update::decode_v1(&update) { - trx.apply_update(update); - trx.commit(); - if cfg!(debug_assertions) { - trace!("changed_parent_types: {:?}", trx.changed_parent_types()); - trace!("before_state: {:?}", trx.before_state()); - trace!("after_state: {:?}", trx.after_state()); - } - trx.encode_update_v1() - .map(|update| SyncMessage::Doc(DocMessage::Update(update))) - .ok() - } else { - None - } - } - }, - _ => None, - } - } { - let mut buffer = Vec::new(); - if let Err(e) = write_sync_message(&mut buffer, &msg) { - warn!("failed to encode message: {:?}", e); - } else { - result.push(buffer); - } - } - } - })) { - warn!("failed to apply update: {:?}", e); - } - #[cfg(feature = "workspace-auto-subscribe")] - self.try_subscribe_all_blocks(); - } - - result - } - - #[cfg(feature = "workspace-auto-subscribe")] - fn try_subscribe_all_blocks(&mut self) { - if let Some(block_observer_config) = self.block_observer_config.clone() { - // costing approximately 1ms per 500 blocks - if let Err(e) = self.retry_with_trx( - |mut t| { - t.get_blocks().blocks(&t.trx, |blocks| { - blocks.for_each(|mut block| { - let block_observer_config = block_observer_config.clone(); - block.subscribe(block_observer_config); - }) - }); - }, - RETRY_NUM, - ) { - error!("subscribe synchronized block callback failed: {}", e); - } - } - } -} diff --git a/libs/jwst/src/workspace/transaction.rs b/libs/jwst/src/workspace/transaction.rs deleted file mode 100644 index 5285dfa21..000000000 --- a/libs/jwst/src/workspace/transaction.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::{cmp::max, thread::sleep, time::Duration}; - -use lib0::any::Any; -use yrs::{Map, ReadTxn, Transact, TransactionMut}; - -use super::*; -use crate::{utils::JS_INT_RANGE, RETRY_NUM}; - -pub struct WorkspaceTransaction<'a> { - pub ws: &'a Workspace, - pub trx: TransactionMut<'a>, -} - -const RESERVE_SPACE: [&str; 2] = [constants::space::META, constants::space::UPDATED]; - -impl WorkspaceTransaction<'_> { - pub fn get_space>(&mut self, space_id: S) -> Space { - let pages = self.ws.pages(&mut self.trx).unwrap(); - Space::new( - &mut self.trx, - self.ws.doc(), - Pages::new(pages), - self.ws.id(), - space_id, - self.ws.block_observer_config.clone(), - ) - } - - pub fn get_exists_space>(&self, space_id: S) -> Option { - Space::from_exists( - &self.trx, - self.ws.doc(), - self.ws.id(), - space_id, - self.ws.block_observer_config.clone(), - ) - } - - /// The compatibility interface for keck/jni/swift, this api was outdated. - pub fn get_blocks(&mut self) -> Space { - self.get_space("blocks") - } - - #[inline] - pub fn spaces(&self, cb: impl FnOnce(Box + '_>) -> R) -> R { - let keys = self.trx.store().root_keys(); - let iterator = keys.iter().filter_map(|key| { - if key.starts_with("space:") && !RESERVE_SPACE.contains(&key.as_str()) { - Space::from_exists( - &self.trx, - self.ws.doc(), - self.ws.id(), - &key[6..], - self.ws.block_observer_config.clone(), - ) - } else { - None - } - }); - - cb(Box::new(iterator)) - } - - pub fn set_metadata(&mut self, key: &str, value: impl Into) -> JwstResult<()> { - info!("set metadata: {}", key); - let key = key.to_string(); - match value.into() { - Any::Bool(bool) => { - self.ws.metadata.insert(&mut self.trx, key, bool)?; - } - Any::String(text) => { - self.ws.metadata.insert(&mut self.trx, key, text.to_string())?; - } - Any::Number(number) => { - self.ws.metadata.insert(&mut self.trx, key, number)?; - } - Any::BigInt(number) => { - if JS_INT_RANGE.contains(&number) { - self.ws.metadata.insert(&mut self.trx, key, number as f64)?; - } else { - self.ws.metadata.insert(&mut self.trx, key, number)?; - } - } - Any::Null | Any::Undefined => { - self.ws.metadata.remove(&mut self.trx, &key); - } - Any::Buffer(_) | Any::Array(_) | Any::Map(_) => {} - } - - Ok(()) - } - - pub fn commit(&mut self) { - self.trx.commit(); - } -} - -impl Workspace { - pub fn with_trx(&self, f: impl FnOnce(WorkspaceTransaction) -> T) -> T { - self.retry_with_trx(f, RETRY_NUM).unwrap() - } - - pub fn try_with_trx(&self, f: impl FnOnce(WorkspaceTransaction) -> T) -> Option { - self.retry_with_trx(f, RETRY_NUM).ok() - } - - pub fn retry_with_trx(&self, f: impl FnOnce(WorkspaceTransaction) -> T, mut retry: i32) -> JwstResult { - retry = max(RETRY_NUM, retry); - let trx = loop { - match self.doc.try_transact_mut() { - Ok(trx) => break trx, - Err(e) => { - if retry > 0 { - retry -= 1; - sleep(Duration::from_micros(10)); - } else { - info!("retry_with_trx error: {}", e); - return Err(JwstError::DocTransaction(e.to_string())); - } - } - } - }; - - Ok(f(WorkspaceTransaction { trx, ws: self })) - } -} diff --git a/libs/jwst/src/workspace/workspace.rs b/libs/jwst/src/workspace/workspace.rs deleted file mode 100644 index 1d40cb4b9..000000000 --- a/libs/jwst/src/workspace/workspace.rs +++ /dev/null @@ -1,574 +0,0 @@ -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -use jwst_codec::Awareness; -use serde::{ser::SerializeMap, Serialize, Serializer}; -use tokio::sync::RwLock; -use yrs::{ - types::{map::MapEvent, ToJson}, - updates::decoder::Decode, - Doc, Map, MapRef, Subscription, Transact, TransactionMut, Update, UpdateSubscription, -}; - -use super::{ - block_observer::BlockObserverConfig, - plugins::{setup_plugin, PluginMap}, -}; - -pub type MapSubscription = Subscription>; - -pub struct Workspace { - workspace_id: String, - pub(super) awareness: Arc>, - pub(super) doc: Doc, - // TODO: Unreasonable subscription mechanism, needs refactoring - pub(super) sub: Arc>>, - pub(crate) updated: MapRef, - pub(crate) metadata: MapRef, - /// We store plugins so that their ownership is tied to [Workspace]. - /// This enables us to properly manage lifetimes of observers which will - /// subscribe into events that the [Workspace] experiences, like block - /// updates. - /// - /// Public just for the crate as we experiment with the plugins interface. - /// See [super::plugins]. - pub(super) plugins: PluginMap, - pub(super) block_observer_config: Option>, -} - -unsafe impl Send for Workspace {} -unsafe impl Sync for Workspace {} - -impl Workspace { - pub fn new>(id: S) -> Self { - let doc = Doc::new(); - Self::from_doc(doc, id) - } - - pub fn from_binary(binary: &[u8], workspace_id: &str) -> Self { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(binary).unwrap()); - Self::from_doc(doc, workspace_id) - } - - pub fn from_doc>(doc: Doc, workspace_id: S) -> Workspace { - let updated = doc.get_or_insert_map("space:updated"); - let metadata = doc.get_or_insert_map("space:meta"); - - setup_plugin(Self { - workspace_id: workspace_id.as_ref().to_string(), - awareness: Arc::new(RwLock::new(Awareness::new(doc.client_id()))), - doc, - sub: Arc::default(), - updated, - metadata, - plugins: Default::default(), - block_observer_config: Some(Arc::new(BlockObserverConfig::new(workspace_id.as_ref().to_string()))), - }) - } - - fn from_raw>( - workspace_id: S, - awareness: Arc>, - doc: Doc, - sub: Arc>>, - updated: MapRef, - metadata: MapRef, - plugins: PluginMap, - block_observer_config: Option>, - ) -> Workspace { - let block_observer_config = if block_observer_config.is_some() { - block_observer_config - } else { - Some(Arc::new(BlockObserverConfig::new(workspace_id.as_ref().to_string()))) - }; - Self { - workspace_id: workspace_id.as_ref().to_string(), - awareness, - doc, - sub, - updated, - metadata, - plugins, - block_observer_config, - } - } - - pub fn is_empty(&self) -> bool { - let doc = self.doc(); - let trx = doc.transact(); - self.updated.len(&trx) == 0 - } - - pub fn id(&self) -> String { - self.workspace_id.clone() - } - - pub fn doc_guid(&self) -> &str { - self.doc.guid() - } - - pub fn client_id(&self) -> u64 { - self.doc.client_id() - } - - pub fn doc(&self) -> Doc { - self.doc.clone() - } -} - -impl Serialize for Workspace { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(None)?; - - for space in self.with_trx(|t| t.spaces(|spaces| spaces.collect::>())) { - map.serialize_entry(&format!("space:{}", space.space_id()), &space)?; - } - - let trx = self.doc.transact(); - map.serialize_entry("space:meta", &self.metadata.to_json(&trx))?; - map.serialize_entry("space:updated", &self.updated.to_json(&trx))?; - - map.end() - } -} - -impl Clone for Workspace { - fn clone(&self) -> Self { - Self::from_raw( - &self.workspace_id, - self.awareness.clone(), - self.doc.clone(), - self.sub.clone(), - self.updated.clone(), - self.metadata.clone(), - self.plugins.clone(), - self.block_observer_config.clone(), - ) - } -} - -#[cfg(test)] -mod test { - use std::{collections::HashSet, thread::sleep, time::Duration}; - - use tracing::info; - use yrs::{updates::decoder::Decode, Doc, Map, ReadTxn, StateVector, Update}; - - use super::{super::super::Block, *}; - - #[test] - fn doc_load_test() { - let workspace = Workspace::new("test"); - workspace.with_trx(|mut t| { - let space = t.get_space("test"); - - let block = space.create(&mut t.trx, "test", "text").unwrap(); - - block.set(&mut t.trx, "test", "test").unwrap(); - }); - - let doc = workspace.doc(); - - let new_doc = { - let update = doc.transact().encode_state_as_update_v1(&StateVector::default()); - let doc = Doc::default(); - { - let mut trx = doc.transact_mut(); - match update.and_then(|update| Update::decode_v1(&update)) { - Ok(update) => trx.apply_update(update), - Err(err) => info!("failed to decode update: {:?}", err), - } - trx.commit(); - } - doc - }; - - assert_json_diff::assert_json_eq!( - doc.get_or_insert_map("space:meta").to_json(&doc.transact()), - new_doc.get_or_insert_map("space:meta").to_json(&doc.transact()) - ); - - assert_json_diff::assert_json_eq!( - doc.get_or_insert_map("space:updated").to_json(&doc.transact()), - new_doc.get_or_insert_map("space:updated").to_json(&doc.transact()) - ); - } - - #[test] - fn block_observe_callback_triggered_by_set() { - let workspace = Workspace::new("test"); - workspace.set_callback(Arc::new(Box::new(|_workspace_id, mut block_ids| { - block_ids.sort(); - assert_eq!(block_ids, vec!["block1".to_string(), "block2".to_string()]) - }))); - - let (block1, block2) = workspace.with_trx(|mut t| { - let space = t.get_space("test"); - let block1 = space.create(&mut t.trx, "block1", "text").unwrap(); - let block2 = space.create(&mut t.trx, "block2", "text").unwrap(); - (block1, block2) - }); - - workspace.with_trx(|mut trx| { - block1.set(&mut trx.trx, "key1", "value1").unwrap(); - block1.set(&mut trx.trx, "key2", "value2").unwrap(); - block2.set(&mut trx.trx, "key1", "value1").unwrap(); - block2.set(&mut trx.trx, "key2", "value2").unwrap(); - }); - sleep(Duration::from_millis(300)); - } - - #[test] - fn block_observe_callback_triggered_by_get() { - let workspace = Workspace::new("test"); - workspace.set_callback(Arc::new(Box::new(|_workspace_id, block_ids| { - assert_eq!(block_ids, vec!["block1".to_string()]); - }))); - - let block = workspace.with_trx(|mut t| { - let space = t.get_space("blocks"); - let block = space.create(&mut t.trx, "block1", "text").unwrap(); - block - }); - - drop(block); - - let block = workspace.with_trx(|mut trx| trx.get_blocks().get(&trx.trx, "block1".to_string()).unwrap()); - - workspace.with_trx(|mut trx| { - block.set(&mut trx.trx, "key1", "value1").unwrap(); - }); - - sleep(Duration::from_millis(300)); - } - - #[test] - fn manually_retrieve_modified_blocks() { - let workspace = Workspace::new("test"); - assert_eq!(workspace.retrieve_modified_blocks(), None); - workspace.set_tracking_block_changes(true); - - let (block1, block2) = workspace.with_trx(|mut t| { - let space = t.get_space("test"); - let block1 = space.create(&mut t.trx, "block1", "text").unwrap(); - let block2 = space.create(&mut t.trx, "block2", "text").unwrap(); - (block1, block2) - }); - - let modified_blocks = workspace.retrieve_modified_blocks().unwrap(); - assert!(modified_blocks.is_empty()); - - workspace.with_trx(|mut trx| { - block1.set(&mut trx.trx, "key1", "value1").unwrap(); - block1.set(&mut trx.trx, "key2", "value2").unwrap(); - block2.set(&mut trx.trx, "key1", "value1").unwrap(); - block2.set(&mut trx.trx, "key2", "value2").unwrap(); - }); - - sleep(Duration::from_millis(100)); - let modified_blocks = workspace.retrieve_modified_blocks().unwrap(); - - let mut expected: HashSet = HashSet::new(); - expected.insert("block1".to_string()); - expected.insert("block2".to_string()); - assert_eq!(modified_blocks, expected); - - let modified_blocks = workspace.retrieve_modified_blocks().unwrap(); - assert!(modified_blocks.is_empty()); - } - - #[test] - fn workspace() { - let workspace = Workspace::new("test"); - - workspace.with_trx(|t| { - assert_eq!(workspace.id(), "test"); - assert_eq!(workspace.updated.len(&t.trx), 0); - }); - - workspace.with_trx(|mut t| { - let space = t.get_space("test"); - - let block = space.create(&mut t.trx, "block", "text").unwrap(); - - assert_eq!(space.blocks.len(&t.trx), 1); - assert_eq!(workspace.updated.len(&t.trx), 1); - assert_eq!(block.block_id(), "block"); - assert_eq!(block.flavour(&t.trx), "text"); - - assert_eq!( - space.get(&t.trx, "block").map(|b| b.block_id()), - Some("block".to_owned()) - ); - - assert!(space.exists(&t.trx, "block")); - - assert!(space.remove(&mut t.trx, "block")); - - assert_eq!(space.blocks.len(&t.trx), 0); - assert_eq!(workspace.updated.len(&t.trx), 0); - assert_eq!(space.get(&t.trx, "block"), None); - assert!(!space.exists(&t.trx, "block")); - }); - - workspace.with_trx(|mut t| { - let space = t.get_space("test"); - - Block::new(&mut t.trx, &space, "test", "test", 1).unwrap(); - let vec = space.get_blocks_by_flavour(&t.trx, "test"); - assert_eq!(vec.len(), 1); - }); - - let doc = Doc::with_client_id(123); - let workspace = Workspace::from_doc(doc, "test"); - assert_eq!(workspace.client_id(), 123); - } - - #[test] - fn workspace_struct() { - use assert_json_diff::assert_json_include; - - let workspace = Workspace::new("workspace"); - - workspace.with_trx(|mut t| { - let space = t.get_space("space1"); - space.create(&mut t.trx, "block1", "text").unwrap(); - - let space = t.get_space("space2"); - space.create(&mut t.trx, "block2", "text").unwrap(); - }); - - assert_json_include!( - actual: serde_json::to_value(&workspace).unwrap(), - expected: serde_json::json!({ - "space:space1": { - "block1": { - "sys:children": [], - "sys:flavour": "text", - } - }, - "space:space2": { - "block2": { - "sys:children": [], - "sys:flavour": "text", - } - }, - "space:updated": { - "block1": [[]], - "block2": [[]], - }, - "space:meta": {} - }) - ); - } - - #[test] - fn scan_doc() { - let doc = Doc::new(); - let map = doc.get_or_insert_map("test"); - map.insert(&mut doc.transact_mut(), "test", "aaa").unwrap(); - - let data = doc - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap(); - - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&data).unwrap()); - - assert_eq!(doc.transact().store().root_keys(), vec!["test"]); - } - - #[test] - fn test_same_ymap_id_same_source_merge() { - let update = { - let doc = Doc::new(); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let _block = space.create(&mut t.trx, "test", "test1").unwrap(); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - let update1 = { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap()); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let new_block = space.create(&mut t.trx, "test1", "test1").unwrap(); - let block = space.get(&mut t.trx, "test").unwrap(); - block.insert_children_at(&mut t.trx, &new_block, 0).unwrap(); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - let update2 = { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap()); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let new_block = space.create(&mut t.trx, "test2", "test2").unwrap(); - let block = space.get(&mut t.trx, "test").unwrap(); - block.insert_children_at(&mut t.trx, &new_block, 0).unwrap(); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - - { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update1).unwrap()); - doc.transact_mut().apply_update(Update::decode_v1(&update2).unwrap()); - - let ws = Workspace::from_doc(doc, "test"); - let block = ws.with_trx(|mut t| { - let space = t.get_space("space"); - space.get(&t.trx, "test").unwrap() - }); - println!("{:?}", serde_json::to_string_pretty(&block).unwrap()); - - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let block = space.get(&t.trx, "test").unwrap(); - let mut children = block.children(&t.trx); - children.sort(); - assert_eq!(children, vec!["test1".to_owned(), "test2".to_owned()]); - }); - } - { - let merged_update = yrs::merge_updates_v1(&[&update1, &update2]).unwrap(); - let doc = Doc::new(); - doc.transact_mut() - .apply_update(Update::decode_v1(&merged_update).unwrap()); - - let ws = Workspace::from_doc(doc, "test"); - let block = ws.with_trx(|mut t| { - let space = t.get_space("space"); - space.get(&t.trx, "test").unwrap() - }); - println!("{:?}", serde_json::to_string_pretty(&block).unwrap()); - - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let block = space.get(&t.trx, "test").unwrap(); - let mut children = block.children(&t.trx); - children.sort(); - assert_eq!(children, vec!["test1".to_owned(), "test2".to_owned()]); - // assert_eq!(block.get(&t.trx, "test1").unwrap().to_string(), - // "test1"); assert_eq!(block.get(&t.trx, - // "test2").unwrap().to_string(), "test2"); - }); - } - } - - #[test] - fn test_same_ymap_id_different_source_merge() { - let update = { - let doc = Doc::new(); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - t.get_space("space"); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - let update1 = { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap()); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let new_block = space.create(&mut t.trx, "test1", "test1").unwrap(); - let block = space.create(&mut t.trx, "test", "test1").unwrap(); - block.insert_children_at(&mut t.trx, &new_block, 0).unwrap(); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - let update2 = { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap()); - let ws = Workspace::from_doc(doc, "test"); - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let new_block = space.create(&mut t.trx, "test2", "test2").unwrap(); - let block = space.create(&mut t.trx, "test", "test1").unwrap(); - block.insert_children_at(&mut t.trx, &new_block, 0).unwrap(); - }); - - ws.doc() - .transact() - .encode_state_as_update_v1(&StateVector::default()) - .unwrap() - }; - - { - let doc = Doc::new(); - doc.transact_mut().apply_update(Update::decode_v1(&update1).unwrap()); - doc.transact_mut().apply_update(Update::decode_v1(&update2).unwrap()); - - let ws = Workspace::from_doc(doc, "test"); - let block = ws.with_trx(|mut t| { - let space = t.get_space("space"); - space.get(&t.trx, "test").unwrap() - }); - println!("{:?}", serde_json::to_string_pretty(&block).unwrap()); - - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let block = space.get(&t.trx, "test").unwrap(); - let mut children = block.children(&t.trx); - children.sort(); - assert_ne!(children, vec!["test1".to_owned(), "test2".to_owned()]); - }); - } - { - let merged_update = yrs::merge_updates_v1(&[&update1, &update2]).unwrap(); - let doc = Doc::new(); - doc.transact_mut() - .apply_update(Update::decode_v1(&merged_update).unwrap()); - - let ws = Workspace::from_doc(doc, "test"); - let block = ws.with_trx(|mut t| { - let space = t.get_space("space"); - space.get(&t.trx, "test").unwrap() - }); - println!("{:?}", serde_json::to_string_pretty(&block).unwrap()); - - ws.with_trx(|mut t| { - let space = t.get_space("space"); - let block = space.get(&t.trx, "test").unwrap(); - let mut children = block.children(&t.trx); - children.sort(); - assert_ne!(children, vec!["test1".to_owned(), "test2".to_owned()]); - // assert_eq!(block.get(&t.trx, "test1").unwrap().to_string(), - // "test1"); assert_eq!(block.get(&t.trx, - // "test2").unwrap().to_string(), "test2"); - }); - } - } -}