From f1f53e38577aafb935460c932083b5264e8a8fe6 Mon Sep 17 00:00:00 2001 From: Sebastian Moser Date: Sat, 14 Mar 2026 15:33:27 +0100 Subject: [PATCH] cli login works and hello-world spacetime module --- Cargo.lock | 310 +++++++++++++++- Cargo.toml | 1 + deno.json | 3 +- deno.lock | 11 +- packages/mize/flake.lock => flake.lock | 0 packages/mize/flake.nix => flake.nix | 81 ++++- packages/mize/lib.nix => lib.nix | 0 md | 55 ++- packages/mize/mize.nix => mize.nix | 0 packages/marts/Cargo.toml | 3 +- packages/marts/c2vi/dashboard.rs | 9 + packages/marts/c2vi/mod.rs | 70 ++-- packages/marts/deno.json | 7 +- packages/marts/index.ts | 6 +- packages/marts/lib.rs | 8 +- packages/marts/package.json | 10 +- packages/marts/task/obsidian-otask-parse.ts | 384 ++++++++++---------- packages/mize/src/core/config.rs | 8 +- packages/mize/src/core/instance/mod.rs | 12 +- packages/ppc/Cargo.toml | 4 +- packages/ppc/auth/client.rs | 221 +++++++++++ packages/ppc/lib.rs | 5 + packages/ppc/platform/obsidian/main.ts | 8 - packages/ppc/platform/obsidian/main_test.ts | 6 - packages/ppc/platform/os/main.rs | 1 + packages/ppc/platform/os/server.rs | 60 ++- packages/spacetime-module-ppc/Cargo.toml | 13 + packages/spacetime-module-ppc/src/lib.rs | 36 ++ 28 files changed, 1054 insertions(+), 278 deletions(-) rename packages/mize/flake.lock => flake.lock (100%) rename packages/mize/flake.nix => flake.nix (76%) rename packages/mize/lib.nix => lib.nix (100%) rename packages/mize/mize.nix => mize.nix (100%) create mode 100644 packages/marts/c2vi/dashboard.rs create mode 100644 packages/ppc/auth/client.rs delete mode 100644 packages/ppc/platform/obsidian/main.ts delete mode 100644 packages/ppc/platform/obsidian/main_test.ts create mode 100644 packages/spacetime-module-ppc/Cargo.toml create mode 100644 packages/spacetime-module-ppc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 18b7ab4..ee9de95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + [[package]] name = "ar_archive_writer" version = "0.5.1" @@ -153,6 +162,18 @@ dependencies = [ "object", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "ascii" version = "1.1.0" @@ -612,6 +633,20 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.4", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "block" version = "0.1.6" @@ -664,6 +699,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -1094,6 +1135,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "content_disposition" version = "0.4.0" @@ -1471,6 +1518,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "decorum" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281759d3c8a14f5c3f0c49363be56810fcd7f910422f97f2db850c2920fde5cf" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "deno_ast" version = "0.52.0" @@ -1895,7 +1952,7 @@ dependencies = [ "dioxus-cli-config", "http 1.4.0", "infer", - "jni", + "jni 0.21.1", "js-sys", "ndk", "ndk-context", @@ -2018,7 +2075,7 @@ dependencies = [ "generational-box 0.7.3", "global-hotkey", "infer", - "jni", + "jni 0.21.1", "lazy-js-bundle", "libc", "muda", @@ -2820,6 +2877,18 @@ dependencies = [ "cfg-if 1.0.4", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "enumset" version = "1.1.10" @@ -2857,6 +2926,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +dependencies = [ + "serde", +] + [[package]] name = "euclid" version = "0.22.13" @@ -3934,6 +4012,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "hyper" version = "0.14.32" @@ -4349,6 +4433,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -4402,19 +4495,68 @@ dependencies = [ "cesu8", "cfg-if 1.0.4", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295dc9997acda1562fdf8d299f56063c936443b60f078e63a5d8d3c34ef2642b" +dependencies = [ + "cfg-if 1.0.4", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c3d1da60c95c98847b26b9d45f4360fee718b31de746df016d9cd6de916a7ef" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -4742,6 +4884,7 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "dioxus", "flume 0.12.0", "futures", "mize", @@ -5013,7 +5156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.11.0", - "jni-sys", + "jni-sys 0.3.0", "log", "ndk-sys", "num_enum", @@ -5033,7 +5176,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.0", ] [[package]] @@ -5059,6 +5202,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -5835,6 +5984,7 @@ dependencies = [ "axum 0.8.8", "axum-extra 0.10.3", "axum-oidc", + "base64 0.22.1", "clap", "dioxus", "dioxus-ssr 0.5.6", @@ -5855,6 +6005,7 @@ dependencies = [ "tracing", "url", "wasm-bindgen", + "webbrowser", ] [[package]] @@ -5996,6 +6147,14 @@ dependencies = [ "psl-types", ] +[[package]] +name = "quickstart_chat" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "quinn" version = "0.11.9" @@ -6644,7 +6803,7 @@ checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls 0.23.37", @@ -6807,6 +6966,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "second-stack" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4904c83c6e51f1b9b08bfa5a86f35a51798e8307186e6f5513852210a219c0bb" + [[package]] name = "security-framework" version = "3.7.0" @@ -7155,6 +7320,22 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -7293,6 +7474,113 @@ dependencies = [ "url", ] +[[package]] +name = "spacetimedb" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83af98280374b59796296f512f7855fd19e19240a358bb7706a54f1c4e4e7dff" +dependencies = [ + "anyhow", + "bytemuck", + "bytes", + "derive_more 0.99.20", + "getrandom 0.2.17", + "http 1.4.0", + "log", + "rand 0.8.5", + "scoped-tls", + "serde_json", + "spacetimedb-bindings-macro", + "spacetimedb-bindings-sys", + "spacetimedb-lib", + "spacetimedb-primitives", +] + +[[package]] +name = "spacetimedb-bindings-macro" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d8dbedaa26e2e59d886a19bd2b784356d8d008274046f735c9185f9943bd912" +dependencies = [ + "heck 0.4.1", + "humantime", + "proc-macro2", + "quote", + "spacetimedb-primitives", + "syn 2.0.117", +] + +[[package]] +name = "spacetimedb-bindings-sys" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0d30e1655731b1564b7094a6213ef6c354ea920e3c04424e55ad377a1bc3e2" +dependencies = [ + "spacetimedb-primitives", +] + +[[package]] +name = "spacetimedb-lib" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7284d48bcfddc7c091756c6d5d3cc9a847b95778c636fdd03b9538d74a57c7" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "blake3", + "chrono", + "derive_more 0.99.20", + "enum-as-inner", + "hex", + "itertools 0.12.1", + "log", + "spacetimedb-bindings-macro", + "spacetimedb-primitives", + "spacetimedb-sats", + "thiserror 1.0.69", +] + +[[package]] +name = "spacetimedb-primitives" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2dc124963dd166ecde2b4d8f0e5ed4cfd27a20d7f6bb9e84d7eff46cc520b5" +dependencies = [ + "bitflags 2.11.0", + "either", + "enum-as-inner", + "itertools 0.12.1", + "nohash-hasher", +] + +[[package]] +name = "spacetimedb-sats" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df06586ffc862efd795425854cc2469c561aca5b95f5df2212aa29ef14a56f8a" +dependencies = [ + "anyhow", + "arrayvec", + "bitflags 2.11.0", + "bytemuck", + "bytes", + "chrono", + "decorum", + "derive_more 0.99.20", + "enum-as-inner", + "ethnum", + "hex", + "itertools 0.12.1", + "rand 0.9.2", + "second-stack", + "sha3", + "smallvec", + "spacetimedb-bindings-macro", + "spacetimedb-primitives", + "thiserror 1.0.69", + "uuid", +] + [[package]] name = "spin" version = "0.9.8" @@ -7996,7 +8284,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -9223,12 +9511,12 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ "core-foundation 0.10.1", - "jni", + "jni 0.22.3", "log", "ndk-context", "objc2", @@ -10050,7 +10338,7 @@ dependencies = [ "html5ever", "http 1.4.0", "javascriptcore-rs", - "jni", + "jni 0.21.1", "kuchikiki", "libc", "ndk", diff --git a/Cargo.toml b/Cargo.toml index 924b079..c9081f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "packages/marts", "packages/mize", "packages/ppc", + "packages/spacetime-module-ppc", ] resolver = "2" diff --git a/deno.json b/deno.json index 93cd9ed..4acf790 100644 --- a/deno.json +++ b/deno.json @@ -3,6 +3,5 @@ "./packages/ppc/platform/obsidian", "./packages/marts", "./packages/mize" - ], - "imports": {} + ] } diff --git a/deno.lock b/deno.lock index f7e8a94..fe804e1 100644 --- a/deno.lock +++ b/deno.lock @@ -8,11 +8,12 @@ "jsr:@std/path@^1.1.1": "1.1.4", "npm:@flowershow/remark-wiki-link@^3.4.0": "3.4.0", "npm:builtin-modules@5": "5.0.0", - "npm:esbuild@*": "0.25.12", + "npm:esbuild@*": "0.27.3", "npm:esbuild@~0.25.5": "0.25.12", "npm:esbuild@~0.27.3": "0.27.3", "npm:mdast@3": "3.0.0", - "npm:obsidian@^1.12.2": "1.12.2_@codemirror+state@6.5.0_@codemirror+view@6.38.6", + "npm:obsidian@^1.12.2": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6", + "npm:obsidian@^1.12.3": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6", "npm:remark-frontmatter@5": "5.0.0", "npm:remark-gfm@^4.0.1": "4.0.1", "npm:remark-parse@11": "11.0.0", @@ -876,8 +877,8 @@ "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "obsidian@1.12.2_@codemirror+state@6.5.0_@codemirror+view@6.38.6": { - "integrity": "sha512-DGAzpt6vo+sMDET/o8Zj26Bj2hbHrFsNUU8TtnzCyerO/OHksMFQnU9QEnPHVsOMstt/WnHXfC56j2r1syObWg==", + "obsidian@1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6": { + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", "dependencies": [ "@codemirror/state", "@codemirror/view", @@ -1001,7 +1002,7 @@ "npm:builtin-modules@5", "npm:esbuild@~0.27.3", "npm:mdast@3", - "npm:obsidian@^1.12.2", + "npm:obsidian@^1.12.3", "npm:remark-frontmatter@5", "npm:remark-gfm@^4.0.1", "npm:remark-parse@11", diff --git a/packages/mize/flake.lock b/flake.lock similarity index 100% rename from packages/mize/flake.lock rename to flake.lock diff --git a/packages/mize/flake.nix b/flake.nix similarity index 76% rename from packages/mize/flake.nix rename to flake.nix index 71ba838..2b5a3f8 100644 --- a/packages/mize/flake.nix +++ b/flake.nix @@ -212,7 +212,86 @@ in { }; - default = (mizeLib.buildMizeForSystem system).modules.mize.devShell; + #default = (mizeLib.buildMizeForSystem system).modules.mize.devShell; + default = pkgs.mkShell { + + MIZE_BUILD_CONFIG = let + settingsFormat = pkgs.formats.toml { }; + in settingsFormat.generate "mize-build-config.toml" { + config = { + namespace = "mize.buildtime.ns"; + module_url = "c2vi.dev"; + }; + }; + shellHook = '' + mize_project_root=$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel) + export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml + ''; + #_shell_type = "rust"; + nativeBuildInputs = [ + pkgs.wasm-pack + pkgs.dioxus-cli + pkgs.spacetimedb + pkgs.deno + pkgs.pkg-config + pkgs.gobject-introspection + pkgs.gobject-introspection.dev + + # thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes + (pkgs.buildWasmBindgenCli rec { + src = pkgs.fetchCrate { + pname = "wasm-bindgen-cli"; + version = "0.2.114"; + hash = "sha256-xrCym+rFY6EUQFWyWl6OPA+LtftpUAE5pIaElAIVqW0="; + }; + + cargoDeps = pkgs.rustPlatform.fetchCargoVendor { + inherit src; + inherit (src) pname version; + hash = "sha256-Z8+dUXPQq7S+Q7DWNr2Y9d8GMuEdSnq00quUR0wDNPM="; + }; + }) + (fenix.packages.${system}.combine [ + fenix.packages.${system}.latest.toolchain + fenix.packages.${system}.targets.wasm32-unknown-unknown.latest.toolchain + ]) + ]; + + buildInputs = with pkgs; [ + at-spi2-atk + atkmm + cairo + gdk-pixbuf + glib + gtk3 + harfbuzz + librsvg + libsoup_3 + pango + webkitgtk_4_1 + openssl + wasm-bindgen-cli + lld_20 + xdotool + ]; + + LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [ + xdotool + webkitgtk_4_1 + pango + openssl + at-spi2-atk + atkmm + cairo + gdk-pixbuf + glib + gtk3 + harfbuzz + librsvg + libsoup_3 + ]; + + }; }; }) // { diff --git a/packages/mize/lib.nix b/lib.nix similarity index 100% rename from packages/mize/lib.nix rename to lib.nix diff --git a/md b/md index 9aa3d56..23a4bcc 100755 --- a/md +++ b/md @@ -1,5 +1,56 @@ #!/bin/sh +set -e -if [[ "$1" == "dob" ]]; then +MIZE_SRC="/home/me/work/mize" # hardcoded for now +RSYNC_EXCLUDES="--exclude=.git --exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg" + +function main { + if [[ "$1" == "dlu" ]]; then + deploy_lush + fi -fi + if [[ "$1" == "dma" ]]; then + deploy_lush + fi + + if [[ "$1" == "dmo" ]]; then + deploy_main_obsidian + fi +} + + +function deploy_lush { + echo "c2vi: Deploying to lush..." + + # Copy files to lush + rsync -r -u -v --delete --mkpath $RSYNC_EXCLUDES $MIZE_SRC/* lush:here/mize + + # compile + ssh lush nix develop /home/me/here/mize --impure -v -L -c sh -c "cargo build --release --manifest-path /home/me/here/mize/packages/ppc/Cargo.toml" + + # restart service + ssh lush "sudo systemctl restart ppc" +} + +function deploy_main_website { + echo "c2vi: Deploying website to ppc-hosting..." + + # Copy files to lush + rsync -r -u -v --delete --mkpath $RSYNC_EXCLUDES $MIZE_SRC/* ppc-hosting:ppc-src + + # compile + ssh ppc-hosting nix develop /root/host/ppc-src --impure -v -L -c sh -c "cargo build --release --manifest-path /root/host/ppc-src/packages/ppc/Cargo.toml" + + # restart service + ssh ppc-hosting "sudo systemctl restart ppc" +} + +function deploy_main_obsidian { + echo "c2vi: Deploying to local main obsidian vault..." + + deno run --config $MIZE_SRC/packages/ppc/platform/obsidian/deno.json build + + cp -r $MIZE_SRC/packages/ppc/platform/obsidian/dist/* ~/work/things/.obsidian/plugins/ppc/ +} + +main "$@" diff --git a/packages/mize/mize.nix b/mize.nix similarity index 100% rename from packages/mize/mize.nix rename to mize.nix diff --git a/packages/marts/Cargo.toml b/packages/marts/Cargo.toml index 706bfa4..5e9768e 100644 --- a/packages/marts/Cargo.toml +++ b/packages/marts/Cargo.toml @@ -18,10 +18,11 @@ reqwest = { version = "0.13.2", features = ["blocking", "json"] } serde = "1.0.228" serde_json = "1.0.149" rusqlite = { version = "0.31", features = ["bundled"], optional = true } +dioxus = { version = "0.7.1", features = ["router", "fullstack", "web", "desktop", "mobile", "server", "ssr"], optional = true } [features] default = ["target-os"] -target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite" ] +target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite", "dioxus" ] target-wasm = [ "mize/target-wasm" ] clap = ["dep:clap"] diff --git a/packages/marts/c2vi/dashboard.rs b/packages/marts/c2vi/dashboard.rs new file mode 100644 index 0000000..2275afc --- /dev/null +++ b/packages/marts/c2vi/dashboard.rs @@ -0,0 +1,9 @@ +use dioxus::prelude::*; + +pub fn main() -> Element { + rsx! { + div { + h1 { "my dashbaord" } + } + } +} diff --git a/packages/marts/c2vi/mod.rs b/packages/marts/c2vi/mod.rs index 7f15a00..1994000 100644 --- a/packages/marts/c2vi/mod.rs +++ b/packages/marts/c2vi/mod.rs @@ -10,6 +10,8 @@ use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult}; use crate::cli::CliPart; use crate::habitica::Habitica; +mod dashboard; + #[mize_part] #[derive(Default)] pub struct C2vi { @@ -40,7 +42,9 @@ impl C2vi { } pub fn c2vi(mize: &mut Mize) -> MizeResult<()> { - let storage_path = mize.get_config("c2vi.local_storage_path")?.to_string(); + //let storage_path = mize.get_config("c2vi.local_storage_path")?.as_ + let storage_path = "/home/me/work/app-data/ppc"; + println!("storage_path: {}", storage_path); let db_path = std::path::PathBuf::from(&storage_path).join("data.db"); let conn = Connection::open(&db_path) .map_err(|e| mize_err!("Failed to open C2vi database at {:?}: {}", db_path, e))?; @@ -84,6 +88,10 @@ fn setup_tables(conn: &Connection) -> MizeResult<()> { } fn register_subcommands(cli: &mut CliPart) { + cli.subcommand( + Command::new("c2vi-dash").about("Run the c2vi dashboard gui"), + |_matches, mut mize| Ok(dioxus::launch(dashboard::main)), + ); cli.subcommand( Command::new("bed").about("Command to run when c2vi goes to bed"), |_matches, mut mize| { @@ -184,10 +192,7 @@ fn register_subcommands(cli: &mut CliPart) { .map_err(|e| mize_err!("DB error: {}", e))?; let rows = stmt .query_map([], |row| { - Ok(( - row.get::<_, String>(0)?, - row.get::<_, String>(1)?, - )) + Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) }) .map_err(|e| mize_err!("DB error: {}", e))?; for row in rows { @@ -206,15 +211,17 @@ fn register_subcommands(cli: &mut CliPart) { |matches, mut mize| { let id = matches.get_one::("id").unwrap(); let mut hab = mize.get_part_native::("habitica")?; - let data = hab.api_request( - reqwest::Method::GET, - format!("tasks/{}", id), - json!({}), - )?; + let data = hab.api_request(reqwest::Method::GET, format!("tasks/{}", id), json!({}))?; if let Some(history) = data.get("history") { - println!("{}", serde_json::to_string_pretty(history).unwrap_or_default()); + println!( + "{}", + serde_json::to_string_pretty(history).unwrap_or_default() + ); } else { - println!("{}", serde_json::to_string_pretty(&data).unwrap_or_default()); + println!( + "{}", + serde_json::to_string_pretty(&data).unwrap_or_default() + ); } Ok(()) }, @@ -250,9 +257,7 @@ fn get_or_create_log(conn: &Connection, date: &str) -> MizeResult { let mut stmt = conn .prepare("SELECT data FROM habitica_log WHERE date = ?1") .map_err(|e| mize_err!("DB error: {}", e))?; - let result: Option = stmt - .query_row(params![date], |row| row.get(0)) - .ok(); + let result: Option = stmt.query_row(params![date], |row| row.get(0)).ok(); match result { Some(data_str) => { serde_json::from_str(&data_str).map_err(|e| mize_err!("JSON parse error: {}", e)) @@ -394,7 +399,10 @@ fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> { if save_for_yesterday { let existing_todos = data["todos_done"].as_array().unwrap(); let todo_id = todo["id"].as_str().unwrap_or(""); - if existing_todos.iter().any(|t| t["id"].as_str() == Some(todo_id)) { + if existing_todos + .iter() + .any(|t| t["id"].as_str() == Some(todo_id)) + { continue; } } @@ -419,10 +427,7 @@ fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> { // If saving for yesterday but todo was completed today, also record it if save_for_yesterday && date_completed == today_str { println!("todo-done: {}", todo["text"].as_str().unwrap_or("")); - data["todos_done"] - .as_array_mut() - .unwrap() - .push(todo_entry); + data["todos_done"].as_array_mut().unwrap().push(todo_entry); } } } @@ -441,7 +446,9 @@ fn skip_habitica_todo(mize: &mut Mize, num: usize) -> MizeResult<()> { hab.get_tasks("todos")? }; - let arr = todos.as_array().ok_or_else(|| mize_err!("Expected array of todos"))?; + let arr = todos + .as_array() + .ok_or_else(|| mize_err!("Expected array of todos"))?; // Delete and log each skipped todo for i in 0..num { @@ -517,10 +524,8 @@ se 21:40 read bed 22:10"; - let tmp_file = std::env::temp_dir().join(format!( - "habitica-tasks-{}.md", - Utc::now().timestamp() - )); + let tmp_file = + std::env::temp_dir().join(format!("habitica-tasks-{}.md", Utc::now().timestamp())); std::fs::write(&tmp_file, initial_content) .map_err(|e| mize_err!("Failed to write temp file: {}", e))?; @@ -531,8 +536,8 @@ bed 22:10"; .map_err(|e| mize_err!("Failed to run nvim: {}", e))?; // Read back and parse - let list_str = - std::fs::read_to_string(&tmp_file).map_err(|e| mize_err!("Failed to read temp file: {}", e))?; + let list_str = std::fs::read_to_string(&tmp_file) + .map_err(|e| mize_err!("Failed to read temp file: {}", e))?; let _ = std::fs::remove_file(&tmp_file); let tasks: Vec<&str> = list_str @@ -577,17 +582,18 @@ fn dump_habitica_logs(mize: &mut Mize) -> MizeResult<()> { .map_err(|e| mize_err!("DB error: {}", e))?; let rows = stmt .query_map([], |row| { - Ok(( - row.get::<_, String>(0)?, - row.get::<_, String>(1)?, - )) + Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) }) .map_err(|e| mize_err!("DB error: {}", e))?; for row in rows { let (date, data_str) = row.map_err(|e| mize_err!("DB error: {}", e))?; let data: Value = serde_json::from_str(&data_str).unwrap_or(json!({"parse_error": data_str})); - println!("{}: {}", date, serde_json::to_string_pretty(&data).unwrap_or_default()); + println!( + "{}: {}", + date, + serde_json::to_string_pretty(&data).unwrap_or_default() + ); } Ok(()) }) diff --git a/packages/marts/deno.json b/packages/marts/deno.json index 78d27a1..f7f9070 100644 --- a/packages/marts/deno.json +++ b/packages/marts/deno.json @@ -2,10 +2,7 @@ "name": "@ppc/marts", "imports": {}, "exports": { - ".": "./index.ts", - "stub_deno": "./js/stub_deno.ts" + ".": "./index.ts" }, - "tasks": { - "build": "deno bundle -o ./deno_dist/habitica.js ./habitica.ts && deno -A ./esbuild.config.mjs" - } + "tasks": {} } diff --git a/packages/marts/index.ts b/packages/marts/index.ts index ab4fe46..d5c1e84 100644 --- a/packages/marts/index.ts +++ b/packages/marts/index.ts @@ -1,2 +1,4 @@ -export { task_obsidian_otask_parse } from "./task/obsidian-otask-parse"; -export { c2vi_obsidian_canvas_patch } from "./c2vi/obsidian-canvas-patch"; +export * from "./task/obsidian-otask-parse"; +export * from "./c2vi/obsidian-canvas-patch"; + +export * from "./js/stub_deno"; diff --git a/packages/marts/lib.rs b/packages/marts/lib.rs index 9ac7b0c..cf7e1a2 100644 --- a/packages/marts/lib.rs +++ b/packages/marts/lib.rs @@ -16,7 +16,7 @@ pub mod c2vi; #[cfg(feature = "target-os")] pub use c2vi::*; -#[cfg(feature = "target-os")] -pub mod js; -#[cfg(feature = "target-os")] -pub use js::*; +//#[cfg(feature = "target-os")] +//pub mod js; +//#[cfg(feature = "target-os")] +//pub use js::*; diff --git a/packages/marts/package.json b/packages/marts/package.json index 10b393f..b3e1fab 100644 --- a/packages/marts/package.json +++ b/packages/marts/package.json @@ -1,21 +1,15 @@ { - "name": "marts", + "name": "@ppc/marts", "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "keywords": [], "author": "", "license": "ISC", - "packageManager": "pnpm@10.15.1", "dependencies": { "@flowershow/remark-wiki-link": "^3.4.0", "builtin-modules": "^5.0.0", "esbuild": "^0.27.3", "mdast": "^3.0.0", - "obsidian": "^1.12.2", + "obsidian": "^1.12.3", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", diff --git a/packages/marts/task/obsidian-otask-parse.ts b/packages/marts/task/obsidian-otask-parse.ts index 24b0b24..ffe6547 100644 --- a/packages/marts/task/obsidian-otask-parse.ts +++ b/packages/marts/task/obsidian-otask-parse.ts @@ -1,4 +1,3 @@ -import { id, i, init, InstaQLEntity } from "@instantdb/react"; import remarkParse from "remark-parse"; import remarkStringify from "remark-stringify"; import { unified } from "unified"; @@ -7,6 +6,7 @@ import { Root, Content, Heading, List, ListItem, Text } from "mdast"; import remarkFrontmatter from "remark-frontmatter"; import remarkGfm from "remark-gfm"; import wikiLinkPlugin from "@flowershow/remark-wiki-link"; +import { TFile } from "obsidian"; const OTASK_PROP_NAMES = ["short", "outcome", "priority"]; const ACTION_PROP_NAMES = []; @@ -77,7 +77,7 @@ async function full_parse() { render_sub0_file(OTASK_LIST); } -let OTASK_PARSE_QUEUE = []; +let OTASK_PARSE_QUEUE: any = []; let OTASK_LIST = []; let DBG = false; @@ -201,130 +201,37 @@ async function replaceHeading( } async function otasks_from_list_item(listItem, otask_path, priority = 0) { - // the main otask this listItem is about - let otask = { - description: [], - actions: [], - path: [], - priority, - }; - dbg("SystemC2: otasks_from_list_item", listItem, otask_path); + try { + // the main otask this listItem is about + let otask = { + description: [], + actions: [], + path: [], + priority, + }; + dbg("SystemC2: otasks_from_list_item", listItem, otask_path); - if (!listItem.children[0].type === "paragraph") { - dbg( - "listItem[0] of a otask item is not a paragraph... the otask:", - listItem, - ); - return null; - } - let longName = nodeToString(listItem.children[0]); - - dbg("longName:", longName); - - // check for [[anotherotask]], call otasks_from_file, return those - const match = longName.match(/\[\[([^\]]+)\]\]/); - if (match) { - const file = app.metadataCache.getFirstLinkpathDest(match[1], ""); - if (file) { - const metadata = app.metadataCache.getFileCache(file); - - if (metadata?.frontmatter?.tags?.includes("t/otask")) { - queue_otasks_from_file(file, otask_path); - return null; - } else { - dbg( - "SystemC2: listItem with Link to '", - match[1], - "', which is not an otask", - longName, - ); - } + if (!listItem.children[0].type === "paragraph") { + dbg( + "listItem[0] of a otask item is not a paragraph... the otask:", + listItem, + ); + return null; } - } + let longName = nodeToString(listItem.children[0]); - //check for [x] and remove from name - if (longName.startsWith("[ ") || longName.startsWith("[x")) { - longName = longName.slice(4); - } + dbg("longName:", longName); - //check for "ot:" and remove from name - if (longName.startsWith("ot:")) { - longName = longName.slice(4); - } - - otask.longName = longName; - otask.short = longName; - otask.path = otask_path; - let new_otask_path = [...otask_path, otask.short]; - - // go through sub list items - // - if it's a prop for the otask... handle that - // - if it's an "ac:" handle that - // - if it's "ot: " handle as another otask -> recursive call - // - if it's "[ ]" handle as another otask -> recursive call - // - if it's a link to a otask file... handle as another otask -> call otasks_from_file() - // - else add it as description item list to otask - if (!listItem.children || listItem.children.length < 2) { - return otask; - } - - // add content as text to otask - if (otask.longName == "hosting updates") { - console.log("listItem... ", listItem.children[1]); - let hii = REMARK.stringify(listItem.children[1]); - console.log("text...", hii); - } - let content_text = REMARK.stringify(listItem.children[1]); - otask.content = content_text; - - for (const subListItem of listItem.children[1].children) { - let text = nodeToString(subListItem.children[0]); - - // check prop - if (check_otask_prop_from_list_item(subListItem, otask)) { - new_otask_path = [...otask_path, otask.short]; // have to redo it here, in case short changed from aprop - continue; // don't do any other processing on this sub-listItem - } - - // remove "[ ]" - if (text.startsWith("[ ") || text.startsWith("[x")) { - text = text.slice(4); - } - - // check ac - if (text.startsWith("ac:")) { - otask.actions = [ - ...otask.actions, - action_from_list_item(subListItem, new_otask_path, otask.priority), - ]; - - continue; // don't do any other processing on this sub-listItem - } - - // check ot - if (text.startsWith("ot:")) { - queue_otasks_from_list_item(subListItem, new_otask_path, otask.priority); - - continue; // don't do any other processing on this sub-listItem - } - - // check [ ] - if (text.startsWith("[ ] ") || text.startsWith("[x] ")) { - queue_otasks_from_list_item(subListItem, new_otask_path, otask.priority); - - continue; // don't do any other processing on this sub-listItem - } - - // check link to otask file - const match = text.match(/\[\[([^\]]+)\]\]/); + // check for [[anotherotask]], call otasks_from_file, return those + const match = longName.match(/\[\[([^\]]+)\]\]/); if (match) { const file = app.metadataCache.getFirstLinkpathDest(match[1], ""); if (file) { const metadata = app.metadataCache.getFileCache(file); if (metadata?.frontmatter?.tags?.includes("t/otask")) { - queue_otasks_from_file(file, new_otask_path, otask.priority); - continue; + queue_otasks_from_file(file, otask_path); + return null; } else { dbg( "SystemC2: listItem with Link to '", @@ -333,83 +240,192 @@ async function otasks_from_list_item(listItem, otask_path, priority = 0) { longName, ); } + } + } + //check for [x] and remove from name + if (longName.startsWith("[ ") || longName.startsWith("[x")) { + longName = longName.slice(4); + } + + //check for "ot:" and remove from name + if (longName.startsWith("ot:")) { + longName = longName.slice(4); + } + + otask.longName = longName; + otask.short = longName; + otask.path = otask_path; + let new_otask_path = [...otask_path, otask.short]; + + // go through sub list items + // - if it's a prop for the otask... handle that + // - if it's an "ac:" handle that + // - if it's "ot: " handle as another otask -> recursive call + // - if it's "[ ]" handle as another otask -> recursive call + // - if it's a link to a otask file... handle as another otask -> call otasks_from_file() + // - else add it as description item list to otask + if (!listItem.children || listItem.children.length < 2) { + return otask; + } + + // add content as text to otask + if (otask.longName == "hosting updates") { + console.log("listItem... ", listItem.children[1]); + let hii = REMARK.stringify(listItem.children[1]); + console.log("text...", hii); + } + let content_text = REMARK.stringify(listItem.children[1]); + otask.content = content_text; + + for (const subListItem of listItem.children[1].children) { + let text = nodeToString(subListItem.children[0]); + + // check prop + if (check_otask_prop_from_list_item(subListItem, otask)) { + new_otask_path = [...otask_path, otask.short]; // have to redo it here, in case short changed from aprop + continue; // don't do any other processing on this sub-listItem + } + + // remove "[ ]" + if (text.startsWith("[ ") || text.startsWith("[x")) { + text = text.slice(4); + } + + // check ac + if (text.startsWith("ac:")) { + otask.actions = [ + ...otask.actions, + action_from_list_item(subListItem, new_otask_path, otask.priority), + ]; + + continue; // don't do any other processing on this sub-listItem + } + + // check ot + if (text.startsWith("ot:")) { + queue_otasks_from_list_item( + subListItem, + new_otask_path, + otask.priority, + ); + + continue; // don't do any other processing on this sub-listItem + } + + // check [ ] + if (text.startsWith("[ ] ") || text.startsWith("[x] ")) { + queue_otasks_from_list_item( + subListItem, + new_otask_path, + otask.priority, + ); + + continue; // don't do any other processing on this sub-listItem + } + + // check link to otask file + const match = text.match(/\[\[([^\]]+)\]\]/); + if (match) { + const file = app.metadataCache.getFirstLinkpathDest(match[1], ""); + if (file) { + const metadata = app.metadataCache.getFileCache(file); + + if (metadata?.frontmatter?.tags?.includes("t/otask")) { + queue_otasks_from_file(file, new_otask_path, otask.priority); + continue; + } else { + dbg( + "SystemC2: listItem with Link to '", + match[1], + "', which is not an otask", + longName, + ); + } + + continue; // don't do any other processing on this sub-listItem + } + } + + // add as description + const desc = REMARK.stringify(subListItem); + otask.description.push(desc); + } + + return otask; + } catch (error) { + console.log("failed to parse list item:", listItem); + } +} + +async function otasks_from_file(file: TFile, otask_path, priority = 0) { + try { + dbg("SystemC2: otasks_from_file", file, otask_path); + + // the main otask this file is about + let otask = { + description: [], + actions: [], + path: [], + priority, + is_file: true, + }; + + otask.longName = file.basename; + otask.short = otask.longName; + otask.path = otask_path; + let new_otask_path = [...otask_path, otask.short]; + + const content = await app.vault.read(file); + + // parse listItems at the top... for props + const topListItems = getListItemsBeforeFirstHeading(content); + for (const topListItem of topListItems) { + const text = nodeToString(topListItem); + + // check for prop + if (check_otask_prop_from_list_item(topListItem, otask)) { + new_otask_path = [...otask_path, otask.short]; continue; // don't do any other processing on this sub-listItem } } - // add as description - const desc = REMARK.stringify(subListItem); - otask.description.push(desc); - } + // parse all the sub sections + const SUB_COUNT = 10; + for (let sub_number = 0; sub_number < SUB_COUNT; sub_number++) { + let listItems = await getListUnderHeading(content, "sub" + sub_number); - return otask; -} - -async function otasks_from_file(file: TFile, otask_path, priority = 0) { - dbg("SystemC2: otasks_from_file", file, otask_path); - - // the main otask this file is about - let otask = { - description: [], - actions: [], - path: [], - priority, - is_file: true, - }; - - otask.longName = file.basename; - otask.short = otask.longName; - otask.path = otask_path; - let new_otask_path = [...otask_path, otask.short]; - - const content = await app.vault.read(file); - - // parse listItems at the top... for props - const topListItems = getListItemsBeforeFirstHeading(content); - for (const topListItem of topListItems) { - const text = nodeToString(topListItem); - - // check for prop - if (check_otask_prop_from_list_item(topListItem, otask)) { - new_otask_path = [...otask_path, otask.short]; - continue; // don't do any other processing on this sub-listItem - } - } - - // parse all the sub sections - const SUB_COUNT = 10; - for (let sub_number = 0; sub_number < SUB_COUNT; sub_number++) { - let listItems = await getListUnderHeading(content, "sub" + sub_number); - - // "sub" without a number should have priority 5 - if (sub_number == 5) { - listItems = listItems.concat(await getListUnderHeading(content, "sub")); - } - - //dbg(`sub section ${sub_number} has items:`, listItems) - - for (const listItem of listItems) { - // check if listItem is an action... - let text = nodeToString(listItem.children[0]); - if ( - text.startsWith("ac:") || - text.startsWith("[ ] ac:") || - text.startsWith("[x] ac:") - ) { - const action = action_from_list_item(listItem, otask_path); - otask.actions.push(action); + // "sub" without a number should have priority 5 + if (sub_number == 5) { + listItems = listItems.concat(await getListUnderHeading(content, "sub")); } - queue_otasks_from_list_item( - listItem, - new_otask_path, - otask.priority + sub_number, - ); - } - } + //dbg(`sub section ${sub_number} has items:`, listItems) - return otask; + for (const listItem of listItems) { + // check if listItem is an action... + let text = nodeToString(listItem.children[0]); + if ( + text.startsWith("ac:") || + text.startsWith("[ ] ac:") || + text.startsWith("[x] ac:") + ) { + const action = action_from_list_item(listItem, otask_path); + otask.actions.push(action); + } + + queue_otasks_from_list_item( + listItem, + new_otask_path, + otask.priority + sub_number, + ); + } + } + + return otask; + } catch (error) { + console.log("failed to parse file: ", file.path); + } } function action_from_list_item(listItem, otask_path) { diff --git a/packages/mize/src/core/config.rs b/packages/mize/src/core/config.rs index bb33bd1..e6ad279 100644 --- a/packages/mize/src/core/config.rs +++ b/packages/mize/src/core/config.rs @@ -53,9 +53,13 @@ pub fn gather_config(mize: &mut Mize) -> MizeResult<()> { let mut data = ItemData::from_toml(content.as_str())?; println!("config data: {data}"); for path in data.get_paths_recursive()? { - let conf_name = path.replace("/", "."); + let mut conf_name = path.replace("/", "."); + if conf_name.starts_with(".") { + conf_name.remove(0); + } println!("adding config {conf_name} from config file {config_file_path}"); - let val = data.get_path(path.split("/").collect::>())?; + println!("path: {:?}", path); + let val = data.get_path(path.split("/").filter(|s| s != &"").collect::>())?; match config_opts.get_mut(&conf_name) { Some(opt) => { opt.val = Some(val); diff --git a/packages/mize/src/core/instance/mod.rs b/packages/mize/src/core/instance/mod.rs index 9fe0337..1e7c9a8 100644 --- a/packages/mize/src/core/instance/mod.rs +++ b/packages/mize/src/core/instance/mod.rs @@ -707,6 +707,7 @@ impl Mize { #[cfg(feature = "target-os")] let handle = thread::spawn(move || to_spawn()); + #[cfg(feature = "target-os")] threads_inner.push((*next_thread_id, name.to_owned(), Some(handle))); #[cfg(feature = "target-wasm ")] @@ -905,9 +906,10 @@ impl Mize { } pub fn run(&mut self) -> MizeResult<()> { - let mut parts = self.parts.lock().unwrap(); - for part in parts.values_mut() { - part.as_deref_mut().unwrap().run(&mut self.clone())?; + let part_names = self.part_names(); + for part_name in part_names { + let mut part = self.get_part(part_name)?; + part.run(&mut self.clone())?; } let mut threads_inner = self.threads.lock().unwrap(); for (id, name, handle) in threads_inner.drain(..) { @@ -922,8 +924,12 @@ impl Mize { &mut self, name: &str, ) -> MizeResult> { + println!("Getting part native hiiiiiii"); let mut dyn_guard = self.get_part(name)?; + println!("Getting part native hiiiiiii two"); let part = dyn_guard.part.take().unwrap(); + println!("Getting part native hiiiiiii three"); + drop(dyn_guard); let concrete_part = part.into_any().downcast::().unwrap(); Ok(MizePartGuard { mize: self.clone(), diff --git a/packages/ppc/Cargo.toml b/packages/ppc/Cargo.toml index 69b3f60..475a0eb 100644 --- a/packages/ppc/Cargo.toml +++ b/packages/ppc/Cargo.toml @@ -29,6 +29,8 @@ tracing = { version = "0.1.44", optional = true } tower = { version = "0.5", optional = true } tower-sessions = { version = "0.14.0", optional = true } tower-http = { version = "0.6.8", features = ["trace"] } +webbrowser = { version = "1.2.0", optional = true } +base64 = { version = "0.22.1", optional = true } [[bin]] name = "ppc" @@ -41,7 +43,7 @@ name = "ppc" [features] default = ["target-os"] -target-os = [ "clap", "mize/target-os", "marts/target-os", "server", "tracing" ] +target-os = [ "clap", "mize/target-os", "marts/target-os", "server", "tracing", "webbrowser", "base64" ] target-obsidian = [ "js-sys", "wasm-bindgen", "mize/target-wasm", "marts/target-wasm" ] server = [ "axum", "axum-extra", "openidconnect", "serde", "serde_json", "reqwest", "rand", "url", "time", "tokio", "dioxus", "dioxus-ssr", "html-escape", "axum-oidc", "tower", "tower-sessions" ] diff --git a/packages/ppc/auth/client.rs b/packages/ppc/auth/client.rs new file mode 100644 index 0000000..1513f6c --- /dev/null +++ b/packages/ppc/auth/client.rs @@ -0,0 +1,221 @@ +use base64::{ + Engine, + alphabet::STANDARD, + engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}, +}; +use clap::Command; +use mize::{Mize, MizeError, MizePart, MizeResult, mize_err, mize_part}; +use openidconnect::{ + AuthorizationCode, ClientId, CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, + PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, + core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + io::{self, Write}, + path::PathBuf, +}; + +#[derive(Default)] +#[mize_part] +pub struct ClientAuth { + mize: Mize, + secrets: Option, +} + +#[derive(Default, Serialize, Deserialize)] +pub struct ClientSecrets { + id_token: String, + access_token: String, + refresh_token: String, +} + +impl MizePart for ClientAuth { + fn name(&self) -> &'static str { + "client_auth" + } +} + +impl ClientAuth { + pub fn id_token(&self) -> MizeResult<&str> { + if self.secrets.is_none() { + return Err(mize_err!("Client not logged in")); + } + Ok(&self.secrets.as_ref().unwrap().id_token) + } + + pub fn access_token(&self) -> MizeResult<&str> { + if self.secrets.is_none() { + return Err(mize_err!("Client not logged in")); + } + Ok(&self.secrets.as_ref().unwrap().access_token) + } + + pub fn refresh_token(&self) -> MizeResult<&str> { + if self.secrets.is_none() { + return Err(mize_err!("Client not logged in")); + } + Ok(&self.secrets.as_ref().unwrap().refresh_token) + } +} + +pub fn client_auth(mize: &mut Mize) -> MizeResult<()> { + let mize_clone = mize.clone(); + + let secrets = match read_secrets(mize) { + Ok(secrets) => Some(secrets), + Err(_) => None, + }; + + let mut cli = mize.get_part_native::("cli")?; + cli.subcommand(Command::new("login"), |_, mut mize| { + let secrets = mize.spawn_async_blocking("client_auth", auth_flow(mize_clone))?; + println!("hereeeeee oneeeeeeeeee"); + let mut auth = mize.get_part_native::("client_auth")?; + println!("hereeeeee two"); + write_secrets(&mut mize, &secrets)?; + auth.secrets = Some(secrets); + Ok(()) + }); + + mize.register_part(Box::new(ClientAuth { + secrets, + mize: mize.clone(), + })) +} + +fn read_secrets(mize: &mut Mize) -> MizeResult { + let mize_dir = mize.get_config("data_dir")?.value_string()?; + let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json"); + + let secrets_string = std::fs::read_to_string(secrets_file_path)?; + let secrets: ClientSecrets = serde_json::from_str(&secrets_string)?; + Ok(secrets) +} + +fn write_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> { + let mize_dir = mize.get_config("data_dir")?.value_string()?; + let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json"); + + let secrets_string = serde_json::to_string(secrets)?; + std::fs::write(secrets_file_path, secrets_string)?; + + Ok(()) +} + +#[derive(Serialize, Deserialize)] +struct Combined<'a> { + code: &'a str, + state: &'a str, +} + +async fn auth_flow(mut mize: Mize) -> MizeResult { + // ===== CONFIG ===== + // Replace these with your issuer, client id and the public redirect URL served by the axum app. + + let issuer = mize.get_config("auth.client.issuer")?.value_string()?; + let client_id = mize.get_config("auth.client.client_id")?.value_string()?; + let redirect_url = mize + .get_config("auth.client.redirect_url")? + .value_string()?; + + let issuer = IssuerUrl::new(issuer)?; + let client_id = ClientId::new(client_id); + let redirect_uri = RedirectUrl::new(redirect_url)?; + + // Discover provider metadata + let provider_metadata = + CoreProviderMetadata::discover_async(issuer, &openidconnect::reqwest::Client::new()) + .await?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + client_id, + None, // no client secret for public clients + ) + .set_redirect_uri(redirect_uri); + + // ===== PKCE + state + nonce ===== + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + let csrf_token = CsrfToken::new_random(); + let csrf_token_clone = csrf_token.clone(); + let nonce = Nonce::new_random(); + + // Build the authorization URL - include code_challenge and state + let (auth_url, _csrf_token_returned, _nonce_returned) = client + .authorize_url( + CoreAuthenticationFlow::AuthorizationCode, + move || csrf_token_clone.clone(), + move || nonce.clone(), + ) + .set_pkce_challenge(pkce_challenge) + .add_scope(Scope::new("openid".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + + println!("Opening browser to authenticate..."); + println!( + "If your browser does not open automatically, visit:\n\n{}\n", + auth_url + ); + + // Try to open browser (best-effort) + if let Err(e) = webbrowser::open(auth_url.as_str()) { + eprintln!("Failed to open browser automatically: {}", e); + } + + println!("When the authorization completes, the web page will show the code (and state)."); + print!("Paste login string here: "); + io::stdout().flush()?; + + // Read user input + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + let input = input.trim(); + + if input.is_empty() { + return Err(mize_err!("No login string provided")); + } + + let base64_engine = GeneralPurpose::new( + &STANDARD, + GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::RequireNone), + ); + + let json_bytes = base64_engine.decode(input)?; + let json_str = String::from_utf8(json_bytes)?; + let combined: Combined = serde_json::from_str(json_str.as_str())?; + + if combined.state != csrf_token.secret() { + return Err(mize_err!( + "State mismatch: pasted state does not match expected value" + )); + } else { + println!("State is valid"); + }; + + // Perform token exchange + let token_response = client + .exchange_code(AuthorizationCode::new(combined.code.to_string()))? + .set_pkce_verifier(PkceCodeVerifier::new(pkce_verifier.secret().to_string())) + .request_async(&openidconnect::reqwest::Client::new()) + .await?; + + let id_token = token_response + .extra_fields() + .id_token() + .ok_or(mize_err!("no id_token"))?; + + //let refresh = token_response.refresh_token(); + + println!("\n=== Token response ==="); + println!("Access token: {:?}", token_response.access_token().secret()); + println!("ID token: {}", id_token.to_string()); + //println!("Refresh token: {}", refresh.secret()); + + Ok(ClientSecrets { + id_token: id_token.to_string(), + access_token: token_response.access_token().secret().to_string(), + refresh_token: "".to_string(), + }) +} diff --git a/packages/ppc/lib.rs b/packages/ppc/lib.rs index db636ca..7e68b20 100644 --- a/packages/ppc/lib.rs +++ b/packages/ppc/lib.rs @@ -16,3 +16,8 @@ mod website; pub use platform::os::server::server; #[cfg(feature = "target-os")] pub use platform::os::ui; + +pub mod auth { + pub mod client; +} +pub use auth::client::client_auth; diff --git a/packages/ppc/platform/obsidian/main.ts b/packages/ppc/platform/obsidian/main.ts deleted file mode 100644 index 292ce5f..0000000 --- a/packages/ppc/platform/obsidian/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function add(a: number, b: number): number { - return a + b; -} - -// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts -if (import.meta.main) { - console.log("Add 2 + 3 =", add(2, 3)); -} diff --git a/packages/ppc/platform/obsidian/main_test.ts b/packages/ppc/platform/obsidian/main_test.ts deleted file mode 100644 index 3d981e9..0000000 --- a/packages/ppc/platform/obsidian/main_test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { add } from "./main.ts"; - -Deno.test(function addTest() { - assertEquals(add(2, 3), 5); -}); diff --git a/packages/ppc/platform/os/main.rs b/packages/ppc/platform/os/main.rs index 4e1c4a5..9afab57 100644 --- a/packages/ppc/platform/os/main.rs +++ b/packages/ppc/platform/os/main.rs @@ -22,6 +22,7 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> { marts::habitica(mize)?; marts::c2vi(mize)?; ppc::server(mize)?; + ppc::client_auth(mize)?; let mut cli = mize.get_part_native::("cli")?; diff --git a/packages/ppc/platform/os/server.rs b/packages/ppc/platform/os/server.rs index 079c920..0b8fd26 100644 --- a/packages/ppc/platform/os/server.rs +++ b/packages/ppc/platform/os/server.rs @@ -12,6 +12,11 @@ use axum_oidc::{ EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer, OidcRpInitiatedLogout, error::MiddlewareError, }; +use base64::{ + Engine, + alphabet::STANDARD, + engine::{GeneralPurpose, GeneralPurposeConfig}, +}; use clap::Command; use dioxus::prelude::*; use mize::Mize; @@ -99,7 +104,8 @@ pub async fn start_server(mize: &mut Mize) -> MizeResult<()> { .layer(session_layer) .layer(tower_http::trace::TraceLayer::new_for_http()) // not authenticated routes - .route("/", get(ppc_main_page)); + .route("/", get(ppc_main_page)) + .route("/oidc_display_code_and_state", get(display_code_and_state)); tracing::info!("Running on http://localhost:3000"); @@ -136,3 +142,55 @@ async fn maybe_authenticated( async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse { logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000")) } + +// src/web_callback.rs +use axum::{extract::Query, response::Html}; +use html_escape::encode_text; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Debug, Deserialize)] +pub struct CallbackParams { + pub code: Option, + pub state: Option, + pub error: Option, +} + +#[derive(Serialize, Deserialize)] +struct Combined<'a> { + code: &'a str, + state: &'a str, +} + +async fn display_code_and_state(Query(params): Query) -> Html { + // Safe fallbacks + let code = params.code.clone().unwrap_or_default(); + let state = params.state.clone().unwrap_or_default(); + let error = params.error.clone().unwrap_or_default(); + + // Create JSON object and base64 encode it + let combined = Combined { + code: &code, + state: &state, + }; + // serde_json::to_string should succeed for simple strings; fallback to empty JSON on error + let json = serde_json::to_string(&combined) + .unwrap_or_else(|_| "{\"code\":\"\",\"state\":\"\"}".into()); + let base64_engine = GeneralPurpose::new( + &STANDARD, + GeneralPurposeConfig::new().with_encode_padding(false), + ); + let b64 = base64_engine.encode(json); + + let b64_escaped = encode_text(&b64); + let error_escaped = encode_text(&error); + println!("err: {}", error_escaped); + println!("b: {}", b64_escaped); + + let html = match error_escaped.as_ref() { + "" => format!("Copy this into your client: {}", b64_escaped), + _ => format!("An Error occured: {}", error_escaped), + }; + + Html(html) +} diff --git a/packages/spacetime-module-ppc/Cargo.toml b/packages/spacetime-module-ppc/Cargo.toml new file mode 100644 index 0000000..2670d6b --- /dev/null +++ b/packages/spacetime-module-ppc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "quickstart_chat" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb = "1.11.*" +log = "0.4" diff --git a/packages/spacetime-module-ppc/src/lib.rs b/packages/spacetime-module-ppc/src/lib.rs new file mode 100644 index 0000000..d264a25 --- /dev/null +++ b/packages/spacetime-module-ppc/src/lib.rs @@ -0,0 +1,36 @@ +use log::*; +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(name = person, public)] +pub struct CoolPerson { + username: String, +} + +#[spacetimedb::reducer(init)] +pub fn init(_ctx: &ReducerContext) { + // Called when the module is initially published + info!("hiiiiiii"); +} + +#[spacetimedb::reducer(client_connected)] +pub fn identity_connected(_ctx: &ReducerContext) { + // Called everytime a new client connects +} + +#[spacetimedb::reducer(client_disconnected)] +pub fn identity_disconnected(_ctx: &ReducerContext) { + // Called everytime a client disconnects +} + +#[spacetimedb::reducer] +pub fn add(ctx: &ReducerContext, username: String) { + ctx.db.person().insert(CoolPerson { username }); +} + +#[spacetimedb::reducer] +pub fn say_hello(ctx: &ReducerContext) { + for person in ctx.db.person().iter() { + log::info!("Hello, {}!", person.username); + } + log::info!("Hello, World!"); +}